fix(ext/node): support PKCS#12 MACs other than SHA-1#34342
Conversation
The `p12` crate hardcodes SHA-1 for `verify_mac`, so PFX files using a SHA-2-family MAC (the default for OpenSSL 3) were rejected with "mac verify failure" even with the correct passphrase. Debug builds also hit a `debug_assert_eq!` panic on the same code path. Replace the call with a Deno-side MAC verifier that implements the PKCS#12 PBKDF (RFC 7292 Appendix B.2) generically over the digest and dispatches on the stored algorithm OID, supporting SHA-1, SHA-224, SHA-256, SHA-384, SHA-512, SHA-512/224, and SHA-512/256. Co-Authored-By: Divy Srivastava <[email protected]>
| fn constant_time_eq(a: &[u8], b: &[u8]) -> bool { | ||
| if a.len() != b.len() { | ||
| return false; | ||
| } | ||
| let mut diff = 0u8; | ||
| for (x, y) in a.iter().zip(b.iter()) { | ||
| diff |= x ^ y; | ||
| } | ||
| diff == 0 | ||
| } |
There was a problem hiding this comment.
Do we not have this already defined somewhere else too?
| // PKCS#12 PBKDF (RFC 7292, Appendix B.2), generic over a Digest. `v` is | ||
| // the hash function's block size in bytes (64 for SHA-1/224/256, 128 for | ||
| // SHA-384/512/512-224/512-256). `id` is the diversifier (1 = key, | ||
| // 2 = IV, 3 = MAC). | ||
| fn pkcs12_pbkdf<D: Digest + FixedOutputReset>( |
There was a problem hiding this comment.
Please add spec link and verify algorithm
…ctors - Annotate `pkcs12_pbkdf` with the RFC 7292 §B.2 URL and per-step comments so the implementation is auditable against the spec. - Note that `bmp_string` follows RFC 7292 §B.1. - Replace the local constant_time_eq with `subtle::ConstantTimeEq`, which is already imported elsewhere in this file. - Add unit tests with SHA-1 / SHA-256 / SHA-384 / SHA-512 PBKDF vectors cross-checked against an independent Python port of the RFC pseudocode (the SHA-1 case matches the upstream `p12` crate's known-good vector). Co-Authored-By: Divy Srivastava <[email protected]>
|
Addressed both review notes in 49a6364:
|
|
CI: |
|
And |
|
|
| fn pkcs12_pbkdf<D: Digest + FixedOutputReset>( | ||
| pass: &[u8], | ||
| salt: &[u8], | ||
| iterations: u64, |
There was a problem hiding this comment.
can we cap mac_data.iterations before entering the PKCS#12 KDF? How does OSSL deal with this
`mac_data.iterations` is a u32 attacker-controlled field, so an adversarial PFX could pin the PBKDF loop for tens of seconds. OpenSSL doesn't cap here (it trusts its caller), but Deno often loads PFX from less-trusted input, so reject values above the same 600,000 limit Mozilla NSS applies to this KDF. That's well above any realistic creation default (OpenSSL uses 2048). Co-Authored-By: Divy Srivastava <[email protected]>
|
Addressed in 389bbdd: added |
The sha{1,256,384,512} PFX fixtures from #34342 were generated with
OpenSSL 3's default bag encryption (PBES2/AES-256-CBC), which the `p12`
crate cannot decrypt. The previous `op_node_validate_pfx` only checked
the MAC and never touched bag contents, so the fixtures worked; the new
`op_node_load_pfx` extracts the cert and key out of the bags and fails
on PBES2 with `ASN1Error { kind: Invalid }`. Regenerated all four with
`-keypbe PBE-SHA1-3DES -certpbe PBE-SHA1-3DES` so they stay within what
`p12` understands.
Also regenerated `localhost.pfx` as a self-signed cert. It previously
wrapped `localhost.crt`, which is signed by `Example-Root-CA`
(subject != issuer), so the new `is_self_signed` heuristic in
`verify_client_cert` correctly fell back to `UNABLE_TO_GET_ISSUER_CERT`
and the handshake test expecting `DEPTH_ZERO_SELF_SIGNED_CERT` failed.
## Summary
`tls.createSecureContext({ pfx })` rejected PFX files using a
SHA-2-family
MAC — including the default produced by OpenSSL 3 (`openssl pkcs12
-export
...` without `-legacy`/`-macalg sha1`) — with `Error: mac verify
failure`,
even when the passphrase was correct. Debug builds additionally hit a
`debug_assert_eq!` panic in the `p12` crate.
The underlying call was `p12::Pkcs12::verify_mac`, which hardcodes SHA-1
for both the PBKDF and the HMAC. This PR replaces that call with an
in-Deno verifier that:
- Implements the PKCS#12 PBKDF (RFC 7292 Appendix B.2) generically over
the `Digest` trait.
- Dispatches on the digest algorithm OID stored in `MacData`, supporting
SHA-1, SHA-224, SHA-256, SHA-384, SHA-512, SHA-512/224, and SHA-512/256.
Both the wrong-result-on-release and the panic-on-debug-build symptoms
are resolved by the same change.
Closes denoland/orchid#217
Fixes denoland#34336.
## Test plan
- [x] `localhost_{sha1,sha256,sha384,sha512}.pfx` fixtures added under
`tests/testdata/tls/`, generated from the existing `localhost`
certificate/key with `openssl pkcs12 -export -macalg <alg> -passout
pass:secret`. README updated with the regen command.
- [x] New `tls_test.ts` cases iterate over the four MAC algorithms and
assert `tls.createSecureContext({ pfx, passphrase: "secret" })`
succeeds.
- [x] Additional case asserts a wrong passphrase still rejects with
`mac verify failure`.
- [x] Verified manually against a debug build with the reproducer from
the issue: SHA-256 PFX is accepted, no panic.
---------
Co-authored-by: divybot <[email protected]>
Co-authored-by: Divy Srivastava <[email protected]>
Summary
tls.createSecureContext({ pfx })rejected PFX files using a SHA-2-familyMAC — including the default produced by OpenSSL 3 (
openssl pkcs12 -export ...without-legacy/-macalg sha1) — withError: mac verify failure,even when the passphrase was correct. Debug builds additionally hit a
debug_assert_eq!panic in thep12crate.The underlying call was
p12::Pkcs12::verify_mac, which hardcodes SHA-1for both the PBKDF and the HMAC. This PR replaces that call with an
in-Deno verifier that:
the
Digesttrait.MacData, supportingSHA-1, SHA-224, SHA-256, SHA-384, SHA-512, SHA-512/224, and SHA-512/256.
Both the wrong-result-on-release and the panic-on-debug-build symptoms
are resolved by the same change.
Closes denoland/orchid#217
Fixes #34336.
Test plan
localhost_{sha1,sha256,sha384,sha512}.pfxfixtures added undertests/testdata/tls/, generated from the existinglocalhostcertificate/key with
openssl pkcs12 -export -macalg <alg> -passout pass:secret. README updated with the regen command.tls_test.tscases iterate over the four MAC algorithms andassert
tls.createSecureContext({ pfx, passphrase: "secret" })succeeds.
mac verify failure.the issue: SHA-256 PFX is accepted, no panic.