The TLS server implementation does not validate the KeyUsage and ExtendedKeyUsage extensions of client certificates when mutually authenticated TLS is requested. This can lead to impersonation with a certificate issued to a server.
An operations engineer enables mTLS on the admin endpoint of a banking service. The setup is canonical: provide an authenticator on the ocaml-tls listener. She believes the gate is now set: only certificates issued under the corporate root, intended for client authentication, can authenticate at the admin endpoint.
An attacker inside the same corporate trust domain holds the legitimate TLS server cert for some internal HTTPS service they administer. The cert is real, signed by the corporate root, valid, not revoked. Its Extended Key Usage extension lists id-kp-serverAuth and nothing else — the cert was issued for serving HTTPS, not for authenticating clients. The X.509 RFC says exactly that distinction: a cert's role is constrained by its EKU, and a serverAuth-only cert is not authorised to act as a client.
The attacker presents that cert to the bank's ocaml-tls mTLS listener. The hook fires, the chain validates against the corporate root. The ocaml-tls library's does neither check that the presented certificate includes digitalSignature in its keyUsage, neither looks that clientAuth is present in the ExtendedKeyUsage. The handshake completes. The attacker is now authenticated as a client at the bank's admin endpoint, holding a cert the corporate CA never authorised for client identity. Every server cert under the same trust store is an mTLS client cert at this listener — with no further coordination, no compromise of the CA, and no compromise of any legitimate client.
The catastrophic deployment shape is the operator who puts Mozilla's public-PKI trust store on the mTLS-server side. There, every public TLS server certificate in the world — every Let's Encrypt cert, every DigiCert cert, anything an attacker can buy or freely issue for any domain they control — becomes an mTLS client cert at the listener, provided the application authorises identities by chain-validity alone rather than by post-validation CN/SAN gating. That branch reaches CVSS 9.1; the realistic dominant case is the corporate-internal-CA shape at 7.4.
Three actors: Operator (deploys ocaml-tls with mTLS for access control), Attacker (holds a CA-signed TLS server cert under a CA the operator's mTLS listener trusts), Server (the ocaml-tls listener serving the admin endpoint).
Corporate CA (in ocaml-tls trust store; also issues the Operator's internal HTTPS server certs)
│
├──▶ Operator's internal HTTPS service
│ cert: EKU={serverAuth}, used legitimately as TLS server
│
└──▶ Attacker (admins one of those internal HTTPS services)
cert: EKU={serverAuth} (legitimate)
key: in attacker's possession (legitimately)
Attacker presents own server cert as a client cert to:
Operator's ocaml-tls mTLS listener → HTTP 200 OK
Sequence:
authenticator = Some X509.Authenticator.chain_of_trust <internal root CA>.Certificate message.validateCerts (handshake_server.ml) or answerClientCertificate (handshake_server13.ml) call validateChain, which passes.CertificateVerify arrives, validates. Handshake completes.Attack properties:
{serverAuth} only, chained to a CA the listener trusts for client auth. The realistic corporate-internal-CA case: the attacker holds a legitimate server cert under the same CA; AC:H. The catastrophic public-PKI-overlap case: any public CA-signed server cert; AC:L, score 9.1.Affected: any ocaml-tls deployment that
authenticator = Some ..., ANDEKU = {serverAuth} only — which is essentially every CA, since serverAuth-only is the dominant TLS server cert shape.Realistic exposure shapes:
Not in scope:
authenticator = None (no mTLS).ocaml-tls consumer relying on the TLS layer alone for identity is exposed.{
"cwe": [
"CWE-295"
],
"osv": "https://github.com/ocaml/security-advisories/tree/generated-osv/2026/OSEC-2026-07.json",
"human_link": "https://github.com/ocaml/security-advisories/tree/main/advisories/2026/OSEC-2026-07.md"
}