fix(keys): accept conformant PKCS#8, SEC1, and PBES2 inputs - #85
Conversation
parsePkcs8PrivateKey accepts a five-element OneAsymmetricKey carrying both attributes [0] and publicKey [1] (RFC 5958/8410), bounding the tail by ASN.1 class instead of a count. parseSec1PrivateKey rejects a version other than 1 (RFC 5915 §3) instead of deferring to WebCrypto. PBES2 decrypt drops the >=8-byte salt check (RFC 8018 §4.1 does not require it); the encrypt path keeps the minimum.
📝 WalkthroughWalkthroughThe change tightens PKCS#8 Estimated code review effort: 3 (Moderate) | ~25 minutes Possibly related PRs
Suggested labels: Poem
🚥 Pre-merge checks | ✅ 7 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (7 passed)
Comment |
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
micro509 | fe9819c | Commit Preview URL Branch Preview URL |
Jul 24 2026, 02:42 PM |
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
Resolve CHANGELOG [Unreleased]: fold #85's key-parse-tolerance Fixed entries in above master's Changed/Fixed blocks. keys.ts and pbes2.ts auto-merged; #85's decrypt-path salt tolerance and version-tag checks coexist with master's #83 PBKDF2 prf-DEFAULT logic, and its internal bare-throw parse helpers still surface as malformed Results at the import boundary.
Track Bun's rejection of v2 Ed25519 OneAsymmetricKey values carrying attributes and publicKey. Vendor the related PKCS #9, HELD, and EdDSA/XDH RFC references used for standards work.
The class-only tail gate accepted malformed five-plus-field PKCS#8: a primitive attributes [0], a constructed publicKey [1], duplicates, reordering, and any version/public-key pairing, leaving acceptance to the WebCrypto backend. Parse the tail statefully instead: attributes [0] as a constructed SET OF at most once, an optional primitive publicKey [1] BIT STRING after it with octet-aligned content, the RFC 5958 version coupled to the public key's presence, and well-formed unknown extension additions tolerated per X.680 extensibility. A conformant v2 key still parses; the Bun import gap is upstream (oven-sh/bun#35432) and stays behind the test.failing canary.
This comment was marked as low quality.
This comment was marked as low quality.
commit: |
rfc5985.txt held RFC 5985 (HTTP-Enabled Location Delivery), a 5958 digit transposition. Replace it with RFC 5958 (Asymmetric Key Packages), and add RFC 5915 (Elliptic Curve Private Key Structure) and RFC 8018 (PKCS#5 PBES2) so the vendored set matches the OneAsymmetricKey, SEC1, and PBES2 citations.
The loop ran six malformed OneAsymmetricKey tails through one assertion; a failure named none of them. Rethrow with the case name as the message and the assertion error as the cause.
What
Three key parsers rejected inputs that OpenSSL and Node produce or that the spec allows.
PKCS#8 with both optionals (RFC 5958 §2 / RFC 8410 §7)
OneAsymmetricKeyhasattributes [0]andpublicKey [1], so a v2 key carrying both has five elements.parsePkcs8PrivateKeycapped at four, so such a key returnedmalformed. It now validates the tail structurally:attributes [0]as a constructedSET OFat most once, an optional primitivepublicKey [1]BIT STRING after it with octet-aligned content, the RFC 5958 version coupled to the public key's presence (v2iff present), and well-formed unknown extension additions tolerated per X.680 extensibility.A conformant five-element key still fails to import under Bun 1.3.14 — a Bun WebCrypto bug (oven-sh/bun#35432, fixed by oven-sh/bun#35433) that treated every trailing RFC 5958 field as seed bytes. micro509 forwards the conformant envelope unchanged rather than rewriting it; the gap is tracked by an unconditional
test.failingcanary that turns red once a Bun release carries the fix.SEC1 ECPrivateKey version (RFC 5915 §3)
version SHALL be ... one (1).parseSec1PrivateKeychecked only the tag, so version 0 or 2 passed and rejection was deferred to WebCrypto asMalformed PKCS#8 private key. It now compares the content octets.PBES2 salt length (RFC 8018 §4.1)
The eight-octet minimum is a "should" for salt selection, and the salt "need not be checked for a particular format by the party receiving the salt". The decrypt path threw
RangeErroron a shorter salt, soopenssl pkcs8 -topk8 -v2 aes-256-cbc -saltlen 4could not be decrypted. The check is removed on decode; the encrypt path keeps the>= 8minimum.Tests
TDD: a version-2 SEC1 rejects as
Malformed SEC 1; a four-byte PBES2 salt decodes. A real five-element Ed25519OneAsymmetricKeyimports (behindtest.failinguntil the Bun fix reaches a release), and a six-case matrix asserts malformed tails reject: primitiveattributes [0], constructedpublicKey [1], duplicate and reordered fields, a version/public-key mismatch, and an empty public-key BIT STRING.