[PM-39573] mTLS client certificate authentication fails when the reverse proxy trusts only the root CA
- Dominant language
- Swift
- Stars
- 684
- Forks
- 154
- Avg merge
- 7d 2h
- Merged PRs (30d)
- 32
Description
### Steps To Reproduce
1. Set up a self-hosted server behind a reverse proxy that requires mutual TLS (client certificate authentication).
2. Configure the proxy to trust **only the root CA** — i.e. the client-CA bundle contains the root certificate but **not** the intermediate CA.
3. Issue a client certificate whose chain is `leaf → intermediate → root`, and export it as a `.p12` containing the leaf, its private key, and the intermediate.
4. In the iOS app, set the self-hosted server URL and import the `.p12` client certificate.
5. Attempt to log in / connect to the server.
### Expected Result
The app authenticates successfully, the same way a web browser and the Android app do with the identical client certificate and reverse-proxy configuration.
### Actual Result
The TLS handshake fails and the app shows:
> This is not a recognized Bitwarden server. You may need to check with your provider or update your server.
Disabling the client-certificate requirement on the proxy (or adding the intermediate CA to the proxy's client-CA bundle) lets login succeed, confirming the failure is specific to the mTLS handshake.
### Screenshots or Videos
_No response_
### Additional Context
**Root cause:** When answering the client-certificate TLS challenge, the app presents **only the leaf certificate** and not the intermediate chain (the credential is built with no additional certificates). When the reverse proxy trusts only the root CA, it cannot build `leaf → intermediate → root` from the leaf alone, so it rejects the handshake.
- Browsers and the Android app send the **entire chain** from the `.p12`, which is why the same certificate and server work there but the iOS app fails.
- This affects both API requests and vault item icon loading, since both go through the client-certificate challenge.
- **Workaround:** add the intermediate CA(s) to the reverse proxy's client-CA bundle (e.g. nginx `ssl_client_certificate`) so the server can complete the chain itself.
The misleading "not a recognized Bitwarden server" message also sends users toward checking the server URL rather than the certificate chain, which makes this harder to diagnose.
### Build Version
2026.5.1 (also affects earlier versions that support client-certificate authentication)
### What server are you connecting to?
Self-host
### Self-host Server Version
Reproduces with a reverse proxy in front of a self-hosted server (verified against Vaultwarden 1.36.0); applies to any self-host setup that terminates mTLS at a proxy trusting only the root CA.
### Environment Details
- Device: iPhone (any)
- OS Version: iOS 18+
### Issue Tracking Info
- [x] I understand that work is tracked outside of Github. A PR will be linked to this issue should one be opened to address it, but Bitwarden doesn't use fields like "assigned", "milestone", or "project" to track progress.
Contributor guide
Research direction
Trace the client-certificate TLS challenge used by API requests and vault item icon loading, starting with the .p12 credential construction described in the issue. Confirm where only the leaf is supplied and preserve the intermediate certificates from the imported chain; validate with a proxy trusting only the root CA and verify both request paths authenticate successfully.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- ios, swift
- Domain
- authentication, networking, security
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 55/100