Emit GCMParameters SEQUENCE for AES-GCM AlgorithmIdentifier (RFC 5084) - #487
Open
dotCooCoo wants to merge 1 commit into
Open
Emit GCMParameters SEQUENCE for AES-GCM AlgorithmIdentifier (RFC 5084)#487dotCooCoo wants to merge 1 commit into
dotCooCoo wants to merge 1 commit into
Conversation
dotCooCoo
force-pushed
the
feat/aes-gcm-parameters
branch
2 times, most recently
from
August 20, 2026 13:21
977a700 to
1f38dbb
Compare
dotCooCoo
force-pushed
the
feat/aes-gcm-parameters
branch
from
August 20, 2026 13:37
1f38dbb to
6ffa05a
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
When constructing a PKCS#12 PFX or CMS EnvelopedData with
contentEncryptionAlgorithm: { name: "AES-GCM", ... }, pkijs emits the AlgorithmIdentifier withparametersas a bareOCTET STRINGcarrying the IV. RFC 5084 §3.2 requires the parameter value to be aGCMParametersSEQUENCE instead:A bare
OCTET STRINGis not aGCMParameters, so what pkijs writes today is notwhat a reader implementing RFC 5084 expects to parse. The same-shape bug exists
independently on the CMS EnvelopedData path, tracked separately as #287.
Scope
Two emission sites, same root cause, both fixed:
CryptoEngine.encryptEncryptedContentInfo(used by PKCS#12PKCS8ShroudedKeyBagandAuthenticatedSafe) — refs PKCS#12: AES-GCM PBES2 emits AlgorithmIdentifier without GCMParameters — openssl import fails #486EnvelopedData.encrypt(CMS path) — refs GCMParams in EnvelopedData required to be OCTET STRING instead of SEQUENCE #287This corrects the encoding. It does not, on its own, make either payload
interoperable with OpenSSL, and neither issue is closed by it:
say —
openssl pkcs8 -v2 aes-256-gcmanswersAEAD ciphers not supported, andimporting a PFX built this way still ends in
PKCS5_v2_PBE_keyivgen_ex: cipher parameter error.AuthEnvelopedDatacontent type, which pkijs does not implement.
openssl cms -encrypt -aes-128-gcmemitsAuthEnvelopedData, notEnvelopedData.Both remainders are the same shape: AES-GCM in a container with no
macfieldto carry the ICV. That is a larger change than this one and is left to the
issues.
Changes
src/GCMParams.ts— RFC 5084 §3.2GCMParametersschema class, modeled onPBKDF2Paramstest/gcmParams.spec.ts— 28 testssrc/CryptoEngine/CryptoEngine.ts— detect AES-GCM, generate a 12-octet nonce (the size RFC 5084 recommends), emitGCMParams.toSchema()on encrypt, and read the parameters back throughGCMParams.fromAlgorithmParamson decryptsrc/EnvelopedData.ts— same pattern for the CMS pathsrc/index.ts— alphabetical re-export ofGCMParamsGCMParams.fromAlgorithmParamsis where both paths read an AES-GCMAlgorithmIdentifier: it returns the nonce, and the tag length where theparameters state one. Keeping it in one place means the ICV default and the
compatibility shape below cannot drift apart between the PBES2 and CMS callers.
Value constraints
aes-ICVlenreaches WebCrypto as a tag length, and theAlgorithmIdentifiercarrying it is not authenticated, so the value is checked rather than trusted.
GCMParamsrejects anaes-ICVlenoutside RFC 5084'sINTEGER (12 | 13 | 14 | 15 | 16)on both parse and emit, rejects a zero-lengthaes-nonce, and rejects an AES-GCMAlgorithmIdentifierwhose parameters fieldis absent, which §3.2 requires to be present. Without the range check a blob
declaring
aes-ICVlen 4authenticates under a 32-bit tag.The
GCMParametersASN.1 type name is capitalized per RFC 5084, but the JS class follows the*Paramsconvention established byPBKDF2Params,PBES2Params,RSAESOAEPParams,RSASSAPSSParamsin this package. Comments and ASN.1 blocks still reference the RFC type nameGCMParameters.Backward compatibility
Where the parameters are a bare
OCTET STRINGrather than a GCMParametersSEQUENCE, it is read as a raw IV and WebCrypto keeps its own 128-bit tag length,
so pkijs still opens the AES-GCM blobs it produced before this change. That shape
is matched on the
OCTET STRINGitself, so a GCMParameters that fails to parseraises its schema error instead of being taken for a legacy blob.
Tests
Schema:
PBES2 path (#486):
CMS EnvelopedData path (#287):
Both
normalizes a lower-case algorithm namecases cover the same gap on the twopaths:
getOIDByAlgorithmmatches onalgorithm.name.toUpperCase(), soaes-gcmresolves to the AES-GCM OID. The parameter encoding follows that normalization,
otherwise a case variant emits the AES-GCM OID beside a bare OCTET STRING.
encodes to the DER an RFC 5084 reader expectspins the two shapes the encoderemits as literal hex, so a change to the encoder cannot quietly agree with a
matching change to the decoder and still fail a third-party reader.
src/GCMParams.tsis at 100% line, branch and function coverage, and the changedregions of the other two files carry no uncovered line or branch. Full suite:
1764 passing, 57 skipped. Lint clean.
tsc --noEmitclean.The adjacent TODO
encryptEncryptedContentInfocarries a note directly above the line this changes:// TODO Should we reuse iv from parameters.contentEncryptionAlgorithm or use it's length for ivBuffer?It is left in place. Both readings resolve to "no", and the change that would
settle the question properly is a breaking one.
Reusing the caller's
iv.ContentEncryptionAesGcmParamsisAesGcmParams & AesDerivedKeyParams, andAesGcmParamsdeclaresivasrequired, so every caller already supplies a value that the method discards.
Whatever satisfies the type is in there today: a zero buffer, a constant, a value
shared across calls. Honoring the field would turn those into fixed GCM nonces at
upgrade, and a repeated key-nonce pair under GCM exposes the hash subkey and
admits forgery, not only a plaintext XOR. Nothing collides at present because the
key differs per call, from a fresh 64-byte PBKDF2 salt here and a generated
content-encryption key in
EnvelopedData.encrypt. That makes nonce hygiene aproperty of salt and key generation rather than of the nonce, and it holds only
until a caller can supply either one.
Taking the length from that field. A 96-bit nonce is the size RFC 5084
recommends for AES-GCM. This sizes the buffer from the algorithm, 12 bytes for
GCM and 16 otherwise, in place of the flat 16 that preceded it. Reading the
length from the caller would put the shorter cases back.
What remains is that
ivis required and ignored. Making it optional changes thepublished types, so it is not attempted here.
Refs #486
Refs #287