Skip to content

[PM-39577] Pm 39573/mtls client certificate chain - #2833

Open
jalenfran wants to merge 3 commits into
bitwarden:mainfrom
jalenfran:PM-39573/mtls-client-certificate-chain
Open

[PM-39577] Pm 39573/mtls client certificate chain#2833
jalenfran wants to merge 3 commits into
bitwarden:mainfrom
jalenfran:PM-39573/mtls-client-certificate-chain

Conversation

@jalenfran

@jalenfran jalenfran commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

🎟️ Tracking

📔 Objective

Fixes mutual TLS (client certificate) authentication failing against a self-hosted server when the reverse proxy is configured to trust only the root CA.

Root cause: When answering a client-certificate TLS challenge, the app presented only the leaf certificate (the credential was built with no additional certificates). A reverse proxy that trusts only the root CA 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 PKCS#12 file, which is why the same certificate and server work there but the iOS app fails with "This is not a recognized Bitwarden server." The failure affected both API requests and vault item icon loading, since both go through the client-certificate challenge.

Fix:

  • On import, extract the intermediate certificates (excluding the leaf) from the PKCS#12 and persist them in the keychain alongside the identity, keyed by the certificate fingerprint.
  • Include those intermediates in the URLCredential presented for client-certificate challenges, for both API requests (CertificateHTTPClient) and vault icon loading (CipherIconImageLoader).

Backward compatibility: The chain is best-effort. A certificate with no stored intermediates still presents the leaf alone, so existing single-certificate setups are unaffected and already-imported certificates keep working with no re-login or re-import required. Users on a root-only proxy gain the fix automatically the next time they re-import their certificate.

Testing:

  • Unit tests cover keychain storage of the chain, chain extraction on import (and the invalid-password path), credential resolution, and the error/degrade branches (missing identity, keychain failure, invalid/legacy data).
  • Manually verified end-to-end against a self-hosted server behind an nginx proxy configured to trust only the root CA: a full-chain client certificate connects and logs in, while a leaf-only certificate fails (negative control), confirming the intermediate chain is what makes the difference.

Add keychain storage for the intermediate certificates associated with a
client certificate, keyed by the certificate fingerprint. This backs the
mTLS handshake change that presents the full certificate chain to servers
that trust only the root CA.
When answering a client-certificate TLS challenge, the app presented only
the leaf certificate. A reverse proxy that trusts only the root CA cannot
build leaf -> intermediate -> root from the leaf alone and rejects the
handshake, even though browsers and the Android app succeed with the same
certificate by sending the full chain from the PKCS#12 file.

Extract the intermediate certificates when importing a certificate, persist
them alongside the identity, and include them in the URLCredential used for
both API requests and vault icon loading. The chain is best-effort: a
certificate without stored intermediates still presents the leaf alone, so
existing single-certificate setups are unaffected.
@jalenfran
jalenfran requested review from a team and matt-livefront as code owners June 25, 2026 16:31
@bitwarden-bot

Copy link
Copy Markdown
Collaborator

Thank you for your contribution! We've added this to our internal tracking system for review.
ID: PM-39577
Link: https://bitwarden.atlassian.net/browse/PM-39577

Details on our contribution process can be found here: https://contributing.bitwarden.com/contributing/pull-requests/community-pr-process.

@bitwarden-bot bitwarden-bot changed the title Pm 39573/mtls client certificate chain [PM-39577] Pm 39573/mtls client certificate chain Jun 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants