Skip to content

fix(ext/node): support PKCS#12 MACs other than SHA-1#34342

Merged
littledivy merged 5 commits into
mainfrom
orch/divybot-217
May 26, 2026
Merged

fix(ext/node): support PKCS#12 MACs other than SHA-1#34342
littledivy merged 5 commits into
mainfrom
orch/divybot-217

Conversation

@divybot

@divybot divybot commented May 25, 2026

Copy link
Copy Markdown
Contributor

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 #34336.

Test plan

  • 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.
  • New tls_test.ts cases iterate over the four MAC algorithms and
    assert tls.createSecureContext({ pfx, passphrase: "secret" })
    succeeds.
  • Additional case asserts a wrong passphrase still rejects with
    mac verify failure.
  • Verified manually against a debug build with the reproducer from
    the issue: SHA-256 PFX is accepted, no panic.

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]>
@divybot divybot changed the title https://github.com/denoland/deno/issues/34336 fix(ext/node): support PKCS#12 MACs other than SHA-1 May 25, 2026
Comment thread ext/node_crypto/keys.rs Outdated
Comment on lines +4399 to +4408
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
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we not have this already defined somewhere else too?

Comment thread ext/node_crypto/keys.rs Outdated
Comment on lines +4212 to +4216
// 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>(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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]>
@divybot

divybot commented May 25, 2026

Copy link
Copy Markdown
Contributor Author

Addressed both review notes in 49a6364:

  • L4408 (constant_time_eq): swapped for subtle::ConstantTimeEq, which this file already imports elsewhere.
  • L4216 (spec link + verify): added the RFC 7292 §B.2 URL with per-step comments tying the code back to the spec, and unit tests with SHA-1/256/384/512 vectors. The SHA-1 vector matches the one shipped with the upstream p12 crate; the SHA-2 vectors come from an independent Python port of the RFC pseudocode (the same port reproduces the SHA-1 vector exactly).

@divybot

divybot commented May 25, 2026

Copy link
Copy Markdown
Contributor Author

CI: test node_compat (2/3) release linux-x86_64 failed on parallel/test-fs-read-stream-pos.js (10s timeout, retried 4×). The same shard passed on linux-aarch64, macos-{x86_64,aarch64}, windows-{x86_64,aarch64}, and on the debug counterpart of this platform — looks like an unrelated flake. Could you re-run when you get a chance?

@divybot

divybot commented May 25, 2026

Copy link
Copy Markdown
Contributor Author

And wpt release linux-x86_64 failed on /websockets/stream/tentative/abort.any.worker.html?wss (1 unexpected failure out of 5,869) — also unrelated to PKCS#12, looks like a flaky websocket WPT test. The extra diff in the log is just reordering of "flaky" arrays inside fetch.json/websockets.json expectation files, not regressions from this branch.

@divybot

divybot commented May 25, 2026

Copy link
Copy Markdown
Contributor Author

test integration (2/2) debug windows-x86_64 failed at the "Set up playwright cache" step — "Build ffi" and "Test" never ran, so this is a runner/cache infrastructure flake (same kind as the previous two). The PFX code paths weren't exercised on this run.

Comment thread ext/node_crypto/keys.rs
fn pkcs12_pbkdf<D: Digest + FixedOutputReset>(
pass: &[u8],
salt: &[u8],
iterations: u64,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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]>
@divybot

divybot commented May 26, 2026

Copy link
Copy Markdown
Contributor Author

Addressed in 389bbdd: added PFX_MAC_ITERATIONS_CAP = 600_000 (matches Mozilla NSS) checked before entering the KDF — OpenSSL itself doesn't cap here, but since Deno often loads PFX from less-trusted input, rejecting absurd iteration counts up front bounds the worst-case work. Couldn't run cargo test locally (rusty_v8 149.2.0 binary fetch from GitHub is timing out from this box), so leaning on CI.

@littledivy
littledivy merged commit c2c9105 into main May 26, 2026
136 checks passed
@littledivy
littledivy deleted the orch/divybot-217 branch May 26, 2026 07:32
bartlomieju added a commit that referenced this pull request May 26, 2026
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.
littledivy added a commit to crowlKats/deno that referenced this pull request Jun 10, 2026
## 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]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

tls.createSecureContext({ pfx }) rejects PKCS#12 files with SHA-256 MAC (default OpenSSL 3 output) that Node accepts

2 participants