fix(node_tls): reject unsupported certificate versions - #36248
fix(node_tls): reject unsupported certificate versions#36248nathanwhit wants to merge 2 commits into
Conversation
|
One concern about lenient mode ( Removing
Net effect: a The strict-mode tightening in
The new test only exercises strict mode, so this lenient-mode path is currently uncovered either way. |
Building the chain from issuer/subject names alone accepted any v1 certificate that merely named a trusted CA as its issuer, and the handshake-signature callbacks then asserted validity unconditionally, so the peer never had to prove it held the private key. Instead, walk the chain ourselves (webpki rejects v1 certs at parse time) and verify every hop's signature with the issuer's public key, using the crypto provider's signature algorithms. For the handshake signature, read the SubjectPublicKeyInfo out of the certificate and verify against that rather than asserting. Name matching is now only used to pick a Node/OpenSSL error code, never to accept a chain. This keeps OpenSSL's acceptance of v1 certs with a genuinely signed chain - including in `rejectUnauthorized: false` mode, where the connection still comes up with the error reported via `authorizationError`.
|
Good catch — that was a real regression, and CI agreed: 36 node_compat tests were I went with a third option rather than either of the two you listed, because So instead of asserting, the callbacks now read the Net effect in lenient mode: the connection comes up, While chasing the rest of the CI failures I also had to walk back the strict-mode That turned out to close a worse hole than the one I originally set out to fix. One limitation I left alone and noted in a comment: validity periods still aren't |
Summary
Verify X.509v1 certificate chains cryptographically instead of trusting
issuer/subject name matching.
every hop's signature with the issuer's public key, using the crypto
provider's signature algorithms
SubjectPublicKeyInforead out ofthe certificate instead of asserting validity unconditionally
never to accept a chain
rejectUnauthorized: falseWhy
Two fallback paths treated an unparseable certificate as a successful
verification:
verify_server_certaccepted a v1 chain wheneververify_chain_structurefound an issuer/subject path to a trusted root. Names are not signatures, so
any v1 cert that merely named a trusted CA as its issuer was accepted.
filter_unsupported_cert_versionturnedUnsupportedCertVersion/BadEncodingfrom the TLS 1.2/1.3 signature callbacks intoHandshakeSignatureValid::assertion(), so the peer never had to prove it heldthe private key for the certificate it presented.
Together those let anyone MITM a connection by presenting a self-issued v1
certificate naming a CA in the client's trust store.
OpenSSL (and therefore Node) does accept v1 certificates, so simply rejecting
them is not an option — several upstream fixtures are v1 and ~36 node_compat
tests depend on them working. Doing the signature checks ourselves keeps that
compatibility while making acceptance mean what it says.
Validity periods are still not checked for v1 chains (webpki can't read them out
of a v1 cert either), so an expired v1 chain verifies where OpenSSL would report
CERT_HAS_EXPIRED. That is pre-existing and called out in a comment.Tests
New fixtures in
tests/testdata/tls(documented in its README):localhost_v1is a genuine v1 leaf signed by
RootCA;localhost_v1_forgednamesRootCAasits issuer but was signed by an impostor key, so it only fails if signatures are
actually verified.
cargo test unit_node::tls_test— v1 cert with the right CA connects and isauthorized; the forged one fails with
UNABLE_TO_VERIFY_LEAF_SIGNATURE;rejectUnauthorized: falseconnects withauthorizationErrorsetcargo test -p node_compat_tests— the 36 TLS/HTTP2/HTTPS tests that theprevious revision of this PR broke all pass again