You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
PKI.js 3.4.0 has no class for the CMS authenticated-enveloped-data content type
(id-ct-authEnvelopedData, 1.2.840.113549.1.9.16.1.23, RFC 5083). As a result:
PKI.js produces AES-GCM inside EnvelopedData (1.2.840.113549.1.7.3), which OpenSSL
refuses outright.
So AES-GCM in PKI.js is currently a closed loop: it only round-trips against itself. This is not a
theoretical concern — OpenSSL's cms code has an explicit guard against AEAD ciphers in EnvelopedData (see the experiment in §3).
I would like AuthEnvelopedData to be a first-class class alongside EnvelopedData.
Object.keys(pkijs).filter(k=>/auth/i.test(k))// ["AuthenticatedSafe", "AuthorityKeyIdentifier", "id_AuthorityInfoAccess", "id_AuthorityKeyIdentifier"]Object.keys(pkijs).filter(k=>/gcm/i.test(k))// []// ContentInfo has no constant for the authEnvelopedData OID:// DATA=1.2.840.113549.1.7.1, SIGNED_DATA=…1.7.2, ENVELOPED_DATA=…1.7.3, ENCRYPTED_DATA=…1.7.6
2. Current behavior
2.1 Direction A — reading OpenSSL AES-GCM output fails
Producer (a KEK recipient is used only to keep the fixture free of certificates and private keys; -recip cert.pem yields the same id-smime-ct-authEnvelopedData container — see @gnarea's -aes-128-gcm -recip sample in #287. -pwri_password cannot be combined with GCM at all:
OpenSSL fails with unsupported kek algorithm:crypto/cms/cms_pwri.c:75, since the PWRI key-wrap
cipher follows the -aes-* option and must not be AEAD):
constci=pkijs.ContentInfo.fromBER(der);// ci.contentType === "1.2.840.113549.1.9.16.1.23" // parses, but nothing can consume itnewpkijs.EnvelopedData({schema: ci.content});// Error: Object's schema was not verified against input data for EnvelopedData
The schema failure is correct and expected — AuthEnvelopedData is a different SEQUENCE
(authAttrs [1] and mac are absent from EnvelopedData). There is simply no class to use here.
2.2 Direction B — PKI.js AES-GCM output is rejected
The relevant code paths are src/EnvelopedData.ts:798-836 (encrypt) and :1540-1566 (decrypt).
The decrypt path reads the IV as contentEncryptionAlgorithm.algorithmParams.valueBlock.valueHex
(line 1548) and calls crypto.decrypt({ name, iv }, key, wholeBuffer) with no additionalData and
no tagLength, which only works because Web Crypto's AES-GCM accepts ciphertext || tag
concatenated — i.e. the current behavior is a side effect of the Web Crypto calling convention, not
a CMS encoding decision.
3. Why fixing the GCMParameters encoding alone is not sufficient
#287 and PR #487 address the OCTET STRING vs SEQUENCE encoding of the AES-GCM AlgorithmIdentifier parameters. That fix is necessary but provably insufficient for interop.
Experiment: I patched the PKI.js output byte-for-byte, replacing the bare OCTET STRING with a
conformant GCMParameters SEQUENCE ({ OCTET STRING(12), INTEGER 16 }) and left everything else
alone. OpenSSL now parses the parameters correctly (parameter: SEQUENCE: in -cmsout -print) and
then refuses on the container instead:
Error decrypting CMS structure
…:CMS routines:ossl_cms_EncryptedContent_init_bio:cipher aead in enveloped data:crypto/cms/cms_enc.c:108:
cipher aead in enveloped data is an explicit guard: an AEAD cipher inside EnvelopedData is
refused regardless of how its parameters are encoded. Interop requires the AuthEnvelopedData
container, which is exactly what is missing. (Note RFC 5084 itself does not spell out a prohibition
— it simply profiles AES-GCM/AES-CCM for the authenticated-enveloped-data content type — but
implementations enforce the separation, as above.)
4. Requested feature
AuthEnvelopedData class (RFC 5083 §2), mirroring the existing EnvelopedData API surface
so the recipient-management code can be shared:
ContentInfo.AUTH_ENVELOPED_DATA = "1.2.840.113549.1.9.16.1.23" plus an id_ContentType_AuthEnvelopedData export, matching the existing constants.
AAD handling per RFC 5083 §2.1: when authAttrs is present its DER encoding is used as the
AAD input with the universal SET OF tag substituted for the IMPLICIT [1] tag; when absent,
"zero bits of input are provided for the AAD input".
Optionally, route EnvelopedData.encrypt() away from AEAD algorithms — either throw with a
pointer to AuthEnvelopedData, or keep the current behavior behind an explicit
backward-compatibility flag. Silently emitting a structure no other implementation accepts is
the worst of the three options.
Proposed API sketch
Deliberately isomorphic to EnvelopedData so existing user code is a near drop-in:
constaed=newpkijs.AuthEnvelopedData();aed.addRecipientByCertificate(cert,{oaepHashAlgorithm: "SHA-256"});// oraed.addRecipientByPreDefinedData(kek,{ keyIdentifier, keyEncryptionAlgorithm },1);// authAttrs is optional; when supplied it is DER-encoded as SET OF for the AADawaitaed.encrypt({name: "AES-GCM",length: 256,tagLength: 128,nonceLength: 12},plaintext,/* authAttrs?: Attribute[] */);constci=newpkijs.ContentInfo({contentType: pkijs.ContentInfo.AUTH_ENVELOPED_DATA,content: aed.toSchema(),});// readingconstparsed=newpkijs.AuthEnvelopedData({schema: ci.content});constplain=awaitparsed.decrypt(0,{preDefinedData: kek});// or { recipientPrivateKey }
Implementation notes
The cryptographic work is already available; no new primitive is needed.
Web Crypto's AES-GCM already exposes additionalData and tagLength, so the AAD and the ICV
length are directly expressible.
subtle.encrypt returns ciphertext || tag. Producing AuthEnvelopedData therefore only
requires splitting the trailing aes-ICVlen bytes into mac, and re-concatenating them
before subtle.decrypt. That is the entire delta over the existing GCM code path.
Nonce should default to 12 octets (RFC 5084 recommendation), not the 16 currently hardcoded at src/EnvelopedData.ts:800.
aes-ICVlen has DEFAULT 12, so a conformant encoder must omit the INTEGER when it is 12 and
readers must apply the default when it is absent. (OpenSSL uses 16 and encodes it explicitly.)
Recipient handling (KeyTransRecipientInfo / KeyAgreeRecipientInfo / KEKRecipientInfo / PasswordRecipientInfo) is identical to EnvelopedData; only the content-encryption step and the
outer SEQUENCE differ. Factoring the recipient logic out of EnvelopedData would let AuthEnvelopedData reuse it verbatim.
AES-CCM (also profiled by RFC 5084) has no Web Crypto support, so it can be left out of scope
or handled only on the parsing side.
5. Reproduction fixtures
Both are self-contained. KEK = 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f,
key identifier = 6b656b31 ("kek1"), plaintext = Hello, AuthEnvelopedData!\n.
A. OpenSSL AuthEnvelopedData that PKI.js 3.4.0 cannot read (186 bytes, DER, base64):
PKI.js decrypts this one successfully (EnvelopedData.decrypt(0, { preDefinedData }) → Hello, AuthEnvelopedData!\n), which is precisely the problem: the format is self-consistent and
externally unusable.
6. Relationship to existing issues
GCMParams in EnvelopedData required to be OCTET STRING instead of SEQUENCE #287 — "GCMParams in EnvelopedData required to be OCTET STRING instead of SEQUENCE" (open
since 2020-09-23). Same root area, narrower scope: it is about the AlgorithmIdentifier
parameter encoding. §3 above shows that fixing that encoding does not yield interop by itself.
This request is the follow-up to my own comment on that thread.
Note that the 2020 discussion on that thread already converged on this content type being the
fix, and a PR was invited. @rmhrisk, 2020-09-24:
It seems our allowance for GCM in the use of EnvelopedData is the root of the interop issue. I
agree the PR is about adding support for this new type.
and @YuryStrozhevsky the same day: "But PKIJS support EnvelopedData only. No support yet for the
Authenticated-Enveloped Data." That PR was never opened. I am filing this separately because GCMParams in EnvelopedData required to be OCTET STRING instead of SEQUENCE #287's title and scope are the parameter encoding, so the container gap keeps getting conflated
with it — @elkman asked for a status in 2025 and @TaaviE offered to contribute parts of it in
2026, both on that thread, both unanswered.
PR Emit GCMParameters SEQUENCE for AES-GCM AlgorithmIdentifier (RFC 5084) #487 — "Emit GCMParameters SEQUENCE for AES-GCM AlgorithmIdentifier (RFC 5084)" (open).
Complementary: it can provide item 2 of §4. It does not add the RFC 5083 container, and per §3 the
output still will not interoperate without it.
What would help most right now is a maintainer decision on three points, so that whoever implements
this (@TaaviE offered on #287, and PR #487 is already touching the adjacent code) does not have to
guess:
A separate AuthEnvelopedData class, or a mode flag on EnvelopedData?
Is refactoring the recipient-handling code out of EnvelopedData for reuse acceptable?
Which of the three options in §4 item 5 for the existing non-conformant EnvelopedData +
AES-GCM path — throw, flag, or leave as is?
The two fixtures in §5 plus the byte-splice experiment in §3 are reproducible as-is and can serve as
test vectors for a PR. I am happy to produce more (other key sizes, authAttrs present, non-default aes-ICVlen) on request.
Summary
PKI.js 3.4.0 has no class for the CMS
authenticated-enveloped-datacontent type(
id-ct-authEnvelopedData,1.2.840.113549.1.9.16.1.23, RFC 5083). As a result:measured below against OpenSSL 4.0.1; the same gap was reported for a C# stack in GCMParams in EnvelopedData required to be OCTET STRING instead of SEQUENCE #287 — and
EnvelopedData(1.2.840.113549.1.7.3), which OpenSSLrefuses outright.
So AES-GCM in PKI.js is currently a closed loop: it only round-trips against itself. This is not a
theoretical concern — OpenSSL's
cmscode has an explicit guard against AEAD ciphers inEnvelopedData(see the experiment in §3).I would like
AuthEnvelopedDatato be a first-class class alongsideEnvelopedData.1. Environment
https://cdn.jsdelivr.net/npm/pkijs@3.4.0/+esm),setEngine("browser")Confirmation that no such class exists in 3.4.0:
2. Current behavior
2.1 Direction A — reading OpenSSL AES-GCM output fails
Producer (a KEK recipient is used only to keep the fixture free of certificates and private keys;
-recip cert.pemyields the sameid-smime-ct-authEnvelopedDatacontainer — see @gnarea's-aes-128-gcm -recipsample in #287.-pwri_passwordcannot be combined with GCM at all:OpenSSL fails with
unsupported kek algorithm:crypto/cms/cms_pwri.c:75, since the PWRI key-wrapcipher follows the
-aes-*option and must not be AEAD):OpenSSL emits
id-smime-ct-authEnvelopedDatawith the ICV in the separatemacfield:PKI.js side:
The schema failure is correct and expected —
AuthEnvelopedDatais a different SEQUENCE(
authAttrs [1]andmacare absent fromEnvelopedData). There is simply no class to use here.2.2 Direction B — PKI.js AES-GCM output is rejected
Measured properties of the result:
ContentInfo.contentType1.2.840.113549.1.7.3(id-envelopedData)1.2.840.113549.1.9.16.1.23contentEncryptionAlgorithm.parametersOCTET STRING(16 bytes)GCMParameters ::= SEQUENCE { aes-nonce OCTET STRING, aes-ICVlen … }src/EnvelopedData.ts:800,// For AES we need IV 16 bytes long)encryptedContent(42 = 26 + 16)macfieldauthAttrsre-taggedSET OF, or zero bits if absentversionOpenSSL rejects it:
The relevant code paths are
src/EnvelopedData.ts:798-836(encrypt) and:1540-1566(decrypt).The decrypt path reads the IV as
contentEncryptionAlgorithm.algorithmParams.valueBlock.valueHex(line 1548) and calls
crypto.decrypt({ name, iv }, key, wholeBuffer)with noadditionalDataandno
tagLength, which only works because Web Crypto's AES-GCM acceptsciphertext || tagconcatenated — i.e. the current behavior is a side effect of the Web Crypto calling convention, not
a CMS encoding decision.
3. Why fixing the
GCMParametersencoding alone is not sufficient#287 and PR #487 address the
OCTET STRINGvsSEQUENCEencoding of the AES-GCMAlgorithmIdentifierparameters. That fix is necessary but provably insufficient for interop.Experiment: I patched the PKI.js output byte-for-byte, replacing the bare
OCTET STRINGwith aconformant
GCMParametersSEQUENCE ({ OCTET STRING(12), INTEGER 16 }) and left everything elsealone. OpenSSL now parses the parameters correctly (
parameter: SEQUENCE:in-cmsout -print) andthen refuses on the container instead:
cipher aead in enveloped datais an explicit guard: an AEAD cipher insideEnvelopedDataisrefused regardless of how its parameters are encoded. Interop requires the
AuthEnvelopedDatacontainer, which is exactly what is missing. (Note RFC 5084 itself does not spell out a prohibition
— it simply profiles AES-GCM/AES-CCM for the authenticated-enveloped-data content type — but
implementations enforce the separation, as above.)
4. Requested feature
AuthEnvelopedDataclass (RFC 5083 §2), mirroring the existingEnvelopedDataAPI surfaceso the recipient-management code can be shared:
versionMUST be 0 (RFC 5083 §2.1).GCMParams/CCMParamsAlgorithmIdentifierparameter classes (RFC 5084 §3.2):OIDs:
id-aes128-GCM2.16.840.1.101.3.4.1.6,id-aes192-GCM2.16.840.1.101.3.4.1.26,id-aes256-GCM2.16.840.1.101.3.4.1.46. If PR Emit GCMParameters SEQUENCE for AES-GCM AlgorithmIdentifier (RFC 5084) #487 lands, this item is already covered andAuthEnvelopedDatacan just consume it.ContentInfo.AUTH_ENVELOPED_DATA = "1.2.840.113549.1.9.16.1.23"plus anid_ContentType_AuthEnvelopedDataexport, matching the existing constants.AAD handling per RFC 5083 §2.1: when
authAttrsis present its DER encoding is used as theAAD input with the universal
SET OFtag substituted for the IMPLICIT[1]tag; when absent,"zero bits of input are provided for the AAD input".
Optionally, route
EnvelopedData.encrypt()away from AEAD algorithms — either throw with apointer to
AuthEnvelopedData, or keep the current behavior behind an explicitbackward-compatibility flag. Silently emitting a structure no other implementation accepts is
the worst of the three options.
Proposed API sketch
Deliberately isomorphic to
EnvelopedDataso existing user code is a near drop-in:Implementation notes
The cryptographic work is already available; no new primitive is needed.
additionalDataandtagLength, so the AAD and the ICVlength are directly expressible.
subtle.encryptreturnsciphertext || tag. ProducingAuthEnvelopedDatatherefore onlyrequires splitting the trailing
aes-ICVlenbytes intomac, and re-concatenating thembefore
subtle.decrypt. That is the entire delta over the existing GCM code path.src/EnvelopedData.ts:800.aes-ICVlenhasDEFAULT 12, so a conformant encoder must omit the INTEGER when it is 12 andreaders must apply the default when it is absent. (OpenSSL uses 16 and encodes it explicitly.)
KeyTransRecipientInfo/KeyAgreeRecipientInfo/KEKRecipientInfo/PasswordRecipientInfo) is identical toEnvelopedData; only the content-encryption step and theouter SEQUENCE differ. Factoring the recipient logic out of
EnvelopedDatawould letAuthEnvelopedDatareuse it verbatim.or handled only on the parsing side.
5. Reproduction fixtures
Both are self-contained. KEK =
000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f,key identifier =
6b656b31("kek1"), plaintext =Hello, AuthEnvelopedData!\n.A. OpenSSL
AuthEnvelopedDatathat PKI.js 3.4.0 cannot read (186 bytes, DER, base64):Round-trips under OpenSSL:
openssl cms -decrypt -inform DER -in repro-gcm.cms -secretkey "$KEK" -secretkeyid 6b656b31→
Hello, AuthEnvelopedData!B. PKI.js 3.4.0 AES-GCM output that OpenSSL rejects (192 bytes, indefinite-length BER, base64):
PKI.js decrypts this one successfully (
EnvelopedData.decrypt(0, { preDefinedData })→Hello, AuthEnvelopedData!\n), which is precisely the problem: the format is self-consistent andexternally unusable.
6. Relationship to existing issues
GCMParams in EnvelopedData required to be OCTET STRING instead of SEQUENCE #287 — "GCMParams in EnvelopedData required to be OCTET STRING instead of SEQUENCE" (open
since 2020-09-23). Same root area, narrower scope: it is about the
AlgorithmIdentifierparameter encoding. §3 above shows that fixing that encoding does not yield interop by itself.
This request is the follow-up to my own comment on that thread.
Note that the 2020 discussion on that thread already converged on this content type being the
fix, and a PR was invited. @rmhrisk, 2020-09-24:
and @YuryStrozhevsky the same day: "But PKIJS support EnvelopedData only. No support yet for the
Authenticated-Enveloped Data." That PR was never opened. I am filing this separately because
GCMParams in EnvelopedData required to be OCTET STRING instead of SEQUENCE #287's title and scope are the parameter encoding, so the container gap keeps getting conflated
with it — @elkman asked for a status in 2025 and @TaaviE offered to contribute parts of it in
2026, both on that thread, both unanswered.
PR Emit GCMParameters SEQUENCE for AES-GCM AlgorithmIdentifier (RFC 5084) #487 — "Emit GCMParameters SEQUENCE for AES-GCM AlgorithmIdentifier (RFC 5084)" (open).
Complementary: it can provide item 2 of §4. It does not add the RFC 5083 container, and per §3 the
output still will not interoperate without it.
Review rfc5751 bis and ensure PKIjs supports associated data structures #79 — "Review rfc5751 bis and ensure PKIjs supports associated data structures" (open since
2017-01-06).
AuthEnvelopedDatais the concrete missing piece behind that general request.What would help most right now is a maintainer decision on three points, so that whoever implements
this (@TaaviE offered on #287, and PR #487 is already touching the adjacent code) does not have to
guess:
AuthEnvelopedDataclass, or a mode flag onEnvelopedData?EnvelopedDatafor reuse acceptable?EnvelopedData+AES-GCM path — throw, flag, or leave as is?
The two fixtures in §5 plus the byte-splice experiment in §3 are reproducible as-is and can serve as
test vectors for a PR. I am happy to produce more (other key sizes,
authAttrspresent, non-defaultaes-ICVlen) on request.