@blamejs/pki 0.6.13 → 0.6.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/CHANGELOG.md +22 -1
  2. package/README.md +1 -1
  3. package/index.js +1 -1
  4. package/lib/acme.js +59 -815
  5. package/lib/asn1-der.js +7 -245
  6. package/lib/attrcert-sign.js +12 -260
  7. package/lib/attrcert-verify.js +1 -7
  8. package/lib/byte-reader.js +0 -8
  9. package/lib/byte-writer.js +0 -11
  10. package/lib/cbor-det.js +0 -54
  11. package/lib/cmc-build.js +15 -357
  12. package/lib/cmc-verify.js +6 -239
  13. package/lib/cmp-build.js +41 -317
  14. package/lib/cmp-session.js +73 -760
  15. package/lib/cmp-verify.js +32 -488
  16. package/lib/cms-compress.js +3 -32
  17. package/lib/cms-decrypt.js +29 -294
  18. package/lib/cms-encrypt.js +14 -234
  19. package/lib/cms-sign.js +29 -324
  20. package/lib/cms-verify.js +9 -500
  21. package/lib/composite-kem.js +2 -102
  22. package/lib/composite-sig.js +1 -77
  23. package/lib/constants.js +0 -249
  24. package/lib/crl-sign.js +9 -237
  25. package/lib/crl-verify.js +1 -10
  26. package/lib/crmf-sign.js +19 -198
  27. package/lib/crmf-verify.js +1 -12
  28. package/lib/csr-sign.js +3 -34
  29. package/lib/csr-verify.js +1 -12
  30. package/lib/ct.js +28 -253
  31. package/lib/edwards-point.js +10 -44
  32. package/lib/est.js +28 -764
  33. package/lib/framework-error.js +4 -182
  34. package/lib/guard-all.js +1 -58
  35. package/lib/guard-async.js +1 -36
  36. package/lib/guard-bytes.js +6 -650
  37. package/lib/guard-compress.js +5 -102
  38. package/lib/guard-crypto.js +1 -40
  39. package/lib/guard-der.js +2 -93
  40. package/lib/guard-encoding.js +2 -60
  41. package/lib/guard-header.js +1 -36
  42. package/lib/guard-identifier.js +7 -531
  43. package/lib/guard-intrinsic.js +1 -150
  44. package/lib/guard-json.js +10 -73
  45. package/lib/guard-limits.js +1 -77
  46. package/lib/guard-list.js +1 -49
  47. package/lib/guard-name.js +3 -209
  48. package/lib/guard-parsed.js +7 -335
  49. package/lib/guard-range.js +1 -71
  50. package/lib/guard-secret.js +1 -102
  51. package/lib/guard-text.js +1 -64
  52. package/lib/guard-time.js +1 -97
  53. package/lib/hpke.js +3 -105
  54. package/lib/http-digest.js +14 -142
  55. package/lib/http-retry-after.js +1 -47
  56. package/lib/http-transport.js +21 -131
  57. package/lib/inspect.js +30 -284
  58. package/lib/ip-utils.js +6 -47
  59. package/lib/jose.js +7 -142
  60. package/lib/key.js +2 -67
  61. package/lib/lint.js +9 -194
  62. package/lib/merkle.js +1 -27
  63. package/lib/mime.js +9 -82
  64. package/lib/ocsp-request-verify.js +1 -14
  65. package/lib/ocsp-verify.js +1 -82
  66. package/lib/ocsp.js +11 -169
  67. package/lib/oid.js +5 -337
  68. package/lib/path-validate.js +103 -1492
  69. package/lib/pbes2.js +2 -89
  70. package/lib/pkcs12-build.js +31 -379
  71. package/lib/pki-build.js +64 -290
  72. package/lib/rc2.js +2 -18
  73. package/lib/rfc3339.js +4 -27
  74. package/lib/scep.js +59 -233
  75. package/lib/schema-all.js +0 -122
  76. package/lib/schema-attrcert.js +4 -146
  77. package/lib/schema-c509.js +153 -802
  78. package/lib/schema-cmc.js +5 -220
  79. package/lib/schema-cmp.js +2 -223
  80. package/lib/schema-cms.js +13 -427
  81. package/lib/schema-crl.js +5 -68
  82. package/lib/schema-crmf.js +6 -167
  83. package/lib/schema-csr.js +1 -54
  84. package/lib/schema-csrattrs.js +2 -87
  85. package/lib/schema-engine.js +0 -152
  86. package/lib/schema-ocsp.js +0 -117
  87. package/lib/schema-pkcs12.js +2 -93
  88. package/lib/schema-pkcs8.js +3 -35
  89. package/lib/schema-pkix.js +24 -655
  90. package/lib/schema-smime.js +0 -54
  91. package/lib/schema-tsp.js +3 -99
  92. package/lib/schema-x509.js +4 -63
  93. package/lib/shbs.js +11 -95
  94. package/lib/sign-scheme.js +2 -97
  95. package/lib/sigstore.js +9 -169
  96. package/lib/sleep.js +2 -8
  97. package/lib/smime.js +15 -408
  98. package/lib/tls-cert-compress.js +0 -62
  99. package/lib/trust.js +19 -185
  100. package/lib/tsp-sign.js +16 -270
  101. package/lib/validator-all.js +1 -30
  102. package/lib/validator-attcert.js +2 -46
  103. package/lib/validator-cose.js +2 -153
  104. package/lib/validator-keydesc.js +1 -37
  105. package/lib/validator-sig.js +1 -46
  106. package/lib/validator-tls.js +1 -88
  107. package/lib/validator-tpm.js +19 -122
  108. package/lib/verify-seam.js +1 -24
  109. package/lib/webauthn-mds.js +3 -400
  110. package/lib/webauthn.js +12 -833
  111. package/lib/webcrypto.js +5 -485
  112. package/lib/x509-sign.js +4 -108
  113. package/package.json +1 -1
  114. package/sbom.cdx.json +6 -6
@@ -1,23 +1,12 @@
1
1
  // SPDX-License-Identifier: Apache-2.0
2
2
  // Copyright (c) blamejs contributors
3
3
  "use strict";
4
- //
5
- // @internal -- the pki.cms.encrypt implementation. The operator-facing @module pki.cms + the
4
+ // @internal
6
5
  // @primitive pki.cms.encrypt block live in cms-verify.js, which re-exports this function (the
7
- // cms-sign.js model).
8
- //
9
- // CMS EnvelopedData / AuthEnvelopedData / EncryptedData production (RFC 5652/5083/5084/3560/5753/
10
- // 8418/9629/9936/3211/8018), the producing side of pki.cms.decrypt. It is the crypto layer over
11
- // the shipped strict parser (schema-cms.js): one fresh content-encryption key is wrapped for
12
- // every recipient (RFC 5652 sec. 6.1), each RecipientInfo arm dispatched off the recipient's key
13
- // material (RSA -> ktri OAEP; EC -> kari stdDH; X25519/X448 -> kari HKDF; ML-KEM -> ori/KEMRI;
14
- // password -> pwri; symmetric KEK -> kekri) through the OID-keyed registry, never a hardcoded
15
- // switch. AEAD (AES-GCM) content is the default and yields AuthEnvelopedData; CBC yields
16
- // EnvelopedData. PKCS#1 v1.5 is never emitted.
17
6
 
18
7
  var nodeCrypto = require("crypto");
19
8
  var asn1 = require("./asn1-der");
20
- var pkiBuild = require("./pki-build"); // reqDenseArray: reject a sparse/nullish caller array as a typed cms/bad-input
9
+ var pkiBuild = require("./pki-build");
21
10
  var oid = require("./oid");
22
11
  var x509 = require("./schema-x509");
23
12
  var schemaCms = require("./schema-cms");
@@ -36,19 +25,14 @@ var WRAP_KEK_LENGTHS = schemaCms.WRAP_KEK_LENGTHS;
36
25
  function O(n) { return oid.byName(n); }
37
26
  function _err(code, message, cause) { return new CmsError(code, message, cause); }
38
27
 
39
- // An AlgorithmIdentifier { OID } (absent params) or { OID, NULL }.
40
28
  function _algId(name, shape) { return shape === "null" ? b.sequence([b.oid(O(name)), b.nullValue()]) : b.sequence([b.oid(O(name))]); }
41
29
 
42
- // A certificate descriptor -> raw DER (the recipient cert is parsed for dispatch + rid; the caller
43
- // supplies bytes, not a re-encoded parse).
44
30
  function _normCertDer(cert, what) {
45
31
  if (guard.bytes.isByteSource(cert)) return guard.bytes.snapshotSource(cert, CmsError, "cms/bad-input", what || "a certificate");
46
32
  if (typeof cert === "string") { try { return x509.pemDecode(cert); } catch (e) { throw _err("cms/bad-input", (what || "a certificate") + " PEM could not be decoded", e); } }
47
33
  throw _err("cms/bad-input", (what || "a certificate") + " must be a DER Buffer, Uint8Array, or PEM string");
48
34
  }
49
35
 
50
- // ---- content-encryption algorithms (registry, not switch) ------------------
51
- // name -> { oid, keyBits, aead }. AEAD (GCM) -> AuthEnvelopedData; CBC -> EnvelopedData.
52
36
  var CONTENT_ALGS = {
53
37
  "aes-128-gcm": { oid: "aes128-GCM", keyBits: 128, aead: true },
54
38
  "aes-192-gcm": { oid: "aes192-GCM", keyBits: 192, aead: true },
@@ -58,34 +42,24 @@ var CONTENT_ALGS = {
58
42
  "aes-256-cbc": { oid: "aes256-CBC", keyBits: 256, aead: false },
59
43
  };
60
44
 
61
- // The AES key-wrap OID for a KEK of `keyBytes` octets (16/24/32 -> aes128/192/256-wrap).
62
45
  function _wrapOidForKek(keyBytes) {
63
46
  if (keyBytes === 16) return "aes128-wrap";
64
47
  if (keyBytes === 24) return "aes192-wrap";
65
48
  if (keyBytes === 32) return "aes256-wrap";
66
- // Coverage residual: unreachable via the API, since a KEK/CEK is always an AES key size (16/24/32);
67
- // a defensive throw for a future caller that hands an off-size key.
68
49
  throw _err("cms/bad-input", "no AES key-wrap algorithm for a " + keyBytes + "-octet key-encryption key");
69
50
  }
70
51
 
71
- // GCMParameters ::= SEQUENCE { aes-nonce OCTET STRING, aes-ICVlen INTEGER DEFAULT 12 }, where the
72
- // DEFAULT 12 is omitted on emit (RFC 5084 sec. 3.2 / canonical DER).
73
52
  function _gcmParams(nonce, icvLen) {
74
53
  var kids = [b.octetString(nonce)];
75
54
  if (icvLen !== 12) kids.push(b.integer(BigInt(icvLen)));
76
55
  return b.sequence(kids);
77
56
  }
78
57
 
79
- // The keyIdentifier option selects the RecipientIdentifier form. "issuerAndSerial" is the documented
80
- // default (its RFC name issuerAndSerialNumber is accepted too); reject anything else instead of
81
- // silently emitting issuerAndSerialNumber, so a typo surfaces instead of a rid the caller never asked for.
82
58
  function _assertKeyIdentifier(form) {
83
59
  if (form != null && form !== "issuerAndSerial" && form !== "issuerAndSerialNumber" && form !== "subjectKeyIdentifier") {
84
60
  throw _err("cms/bad-input", "unsupported keyIdentifier " + JSON.stringify(form) + " (use \"issuerAndSerial\" or \"subjectKeyIdentifier\")");
85
61
  }
86
62
  }
87
- // The RecipientIdentifier (rid) for a parsed cert: issuerAndSerialNumber (default) or
88
- // subjectKeyIdentifier [0] IMPLICIT. Both forms per RFC 5652 sec. 6.2.1.
89
63
  function _rid(cert, form) {
90
64
  _assertKeyIdentifier(form);
91
65
  if (form === "subjectKeyIdentifier") {
@@ -95,11 +69,6 @@ function _rid(cert, form) {
95
69
  }
96
70
  return { node: b.sequence([b.raw(cert.issuer.bytes), b.integer(cert.serialNumber)]), riVersion: 0 };
97
71
  }
98
- // Coverage residual (the malformed-extension catch arms in _skiOf + _assertKeyUsage, and the
99
- // unsupported-curve / low-order / unsupported-KEM-cert throws further below): these validate the
100
- // CALLER's own recipient certificate at config time (tier-1 THROW). They fire only when a caller
101
- // supplies a certificate whose SKI/keyUsage extension is malformed, or whose key is an unsupported
102
- // curve, a low-order Montgomery point, or an unsupported KEM: inputs the toolkit never produces.
103
72
  function _skiOf(cert) {
104
73
  var exts = cert.extensions || [];
105
74
  for (var i = 0; i < exts.length; i++) if (exts[i].name === "subjectKeyIdentifier" && exts[i].value != null) {
@@ -108,21 +77,15 @@ function _skiOf(cert) {
108
77
  return null;
109
78
  }
110
79
 
111
- // keyUsage bit assertion (M9/M15): a recipient cert WITH a keyUsage extension MUST assert `bitName`.
112
- // Through the shared reader, which applies the NamedBitList rules a local bit test does not: DER
113
- // drops trailing zero bits (X.690 sec. 11.2.2) and sec. 4.2.1.3 requires at least one bit set, so
114
- // reading the bits here would accept as a recipient a certificate the issuing side calls malformed.
115
80
  function _assertKeyUsage(cert, bitName, arm) {
116
81
  var ku = pkix.keyUsageOf(_KU_NS, cert, _err, "cms/bad-input", "recipient certificate's");
117
82
  if (ku && ku[bitName] !== true) throw _err("cms/bad-key-usage", "the " + arm + " recipient certificate's keyUsage does not assert " + bitName);
118
83
  }
119
84
 
120
- // ---- ktri (RSA) : RSAES-OAEP, SHA-256 default (v1.5 never emitted) ---------
121
85
  var OAEP_HASH = { sha256: "SHA-256", sha384: "SHA-384", sha512: "SHA-512" };
122
86
  function _oaepParams(hashName) {
123
87
  var hAlg = _algId(hashName, "null");
124
88
  var mgf = b.sequence([b.oid(O("mgf1")), hAlg]);
125
- // pSourceAlgorithm [2] DEFAULT pSpecifiedEmpty: the empty-label default MUST be omitted (X.690).
126
89
  return b.sequence([b.explicit(0, hAlg), b.explicit(1, mgf)]);
127
90
  }
128
91
  async function _buildKtri(cek, cert, opts) {
@@ -136,19 +99,14 @@ async function _buildKtri(cek, cert, opts) {
136
99
  return { tag: null, _riVersion: rid.riVersion, node: b.sequence([b.integer(BigInt(rid.riVersion)), rid.node, keyEncAlg, b.octetString(encryptedKey)]) };
137
100
  }
138
101
 
139
- // ---- kari (EC / X25519 / X448) : ephemeral-static ECDH ---------------------
140
- var EC_KA = {}; // recipient-curve OID -> { curve, x963: {hash, scheme}, coordLen }
102
+ var EC_KA = {};
141
103
  EC_KA[O("prime256v1")] = { curve: "P-256", hash: "SHA-256", scheme: "dhSinglePass-stdDH-sha256kdf-scheme" };
142
104
  EC_KA[O("secp384r1")] = { curve: "P-384", hash: "SHA-384", scheme: "dhSinglePass-stdDH-sha384kdf-scheme" };
143
105
  EC_KA[O("secp521r1")] = { curve: "P-521", hash: "SHA-512", scheme: "dhSinglePass-stdDH-sha512kdf-scheme" };
144
- var MONT_KA = {}; // X25519/X448 key OID -> { name, hkdf, scheme }
106
+ var MONT_KA = {};
145
107
  MONT_KA[O("X25519")] = { name: "X25519", hkdf: "SHA-256", scheme: "dhSinglePass-stdDH-hkdf-sha256-scheme" };
146
108
  MONT_KA[O("X448")] = { name: "X448", hkdf: "SHA-512", scheme: "dhSinglePass-stdDH-hkdf-sha512-scheme" };
147
109
 
148
- // ECC-CMS-SharedInfo ::= SEQUENCE { keyInfo AlgorithmIdentifier (the wrap, params ABSENT),
149
- // entityUInfo [0] EXPLICIT OCTET STRING OPTIONAL, suppPubInfo [2] EXPLICIT OCTET STRING }, where
150
- // suppPubInfo is the KEK length in bits, 4-octet big-endian (RFC 5753 sec. 7.2). One builder,
151
- // shared by encrypt + decrypt so the two sides cannot diverge.
152
110
  function _eccSharedInfo(wrapName, ukm, kekBytes) {
153
111
  var kids = [_algId(wrapName, "absent")];
154
112
  if (ukm) kids.push(b.explicit(0, b.octetString(ukm)));
@@ -161,9 +119,6 @@ async function _buildKari(cek, cert, opts) {
161
119
  var keyAlg = cert.subjectPublicKeyInfo.algorithm;
162
120
  var wrapName = _wrapOidForKek(cek.length);
163
121
  var ukm = opts.ukm ? guard.bytes.view(opts.ukm, CmsError, "cms/bad-input", "ukm") : null;
164
- // z / mz (the raw agreement secret) and the derived kek are all allocated here and all secret.
165
- // Declared together so one `finally` clears whichever the chosen branch produced, including when
166
- // a later encoding step throws (RFC 9629 sec. 7 asks the same of the KEM builder below).
167
122
  var ecdhPub, origKeyAlgId, kek, z, mz;
168
123
  try {
169
124
  if (keyAlg.oid === O("ecPublicKey")) {
@@ -186,23 +141,14 @@ async function _buildKari(cek, cert, opts) {
186
141
  mz = Buffer.from(await subtle.deriveBits({ name: mka.name, public: rPub }, meph.privateKey, null));
187
142
  if (mz.every(function (x) { return x === 0; })) throw _err("cms/bad-input", "the X25519/X448 shared secret is all-zero (low-order point)");
188
143
  var mzKey = await subtle.importKey("raw", mz, { name: "HKDF" }, false, ["deriveBits"]);
189
- // RFC 8418 sec. 2.2: when a ukm is present it is used as both the HKDF salt and the
190
- // ECC-CMS-SharedInfo entityUInfo (the HKDF info). Omitting it from the info diverges the KEK
191
- // from any conformant peer that reads the transmitted ukm.
192
144
  kek = Buffer.from(await subtle.deriveBits({ name: "HKDF", hash: mka.hkdf, salt: ukm || Buffer.alloc(0), info: _eccSharedInfo(wrapName, ukm, cek.length) }, mzKey, cek.length * 8));
193
145
  } else {
194
- // Coverage residual: unreachable, since _buildRecipient routes only ecPublicKey / X25519 / X448
195
- // keys into _buildKari; a defensive throw against a future dispatch change.
196
146
  throw _err("cms/unsupported-algorithm", "unsupported recipient key algorithm for kari");
197
147
  }
198
148
  var encryptedKey = await _aesKwWrap(kek, cek);
199
- // originatorKey [1] IMPLICIT OriginatorPublicKey { algorithm, publicKey BIT STRING }.
200
149
  var origSpki = asn1.decode(origKeyAlgId.spki);
201
- var origPubBits = origSpki.children[1]; // BIT STRING node
150
+ var origPubBits = origSpki.children[1];
202
151
  var originatorKey = b.contextConstructed(1, Buffer.concat([origSpki.children[0].bytes, origPubBits.bytes]));
203
- // KeyAgreeRecipientIdentifier CHOICE { issuerAndSerialNumber, rKeyId [0] IMPLICIT
204
- // RecipientKeyIdentifier }, where the SKI form wraps a SEQUENCE (rKeyId), unlike ktri's bare
205
- // subjectKeyIdentifier [0] IMPLICIT OCTET STRING.
206
152
  var ridNode;
207
153
  _assertKeyIdentifier(opts.keyIdentifier);
208
154
  if (opts.keyIdentifier === "subjectKeyIdentifier") {
@@ -212,21 +158,17 @@ async function _buildKari(cek, cert, opts) {
212
158
  } else {
213
159
  ridNode = b.sequence([b.raw(cert.issuer.bytes), b.integer(cert.serialNumber)]);
214
160
  }
215
- var rek = b.sequence([b.sequence([ridNode, b.octetString(encryptedKey)])]); // RecipientEncryptedKeys SEQ OF { rid, encKey }
161
+ var rek = b.sequence([b.sequence([ridNode, b.octetString(encryptedKey)])]);
216
162
  var kekAlg = b.sequence([b.oid(O(origKeyAlgId.scheme)), _algId(wrapName, "absent")]);
217
163
  var kariKids = [b.integer(3n), b.explicit(0, originatorKey)];
218
164
  if (ukm) kariKids.push(b.explicit(1, b.octetString(ukm)));
219
165
  kariKids.push(kekAlg, rek);
220
166
  return { tag: 1, node: b.sequence(kariKids) };
221
167
  } finally {
222
- // Buffer.from(arrayBuffer) is a VIEW over the engine's returned buffer, so clearing z / mz
223
- // clears that allocation too. The CEK belongs to the caller (the message, not this recipient)
224
- // and is wiped once at the end of encrypt; the ephemeral PUBLIC key is not secret.
225
168
  guard.secret.zeroizeAll([z, mz, kek], CmsError, "cms/bad-input", "the key-agreement shared secret");
226
169
  }
227
170
  }
228
171
 
229
- // ---- kekri (symmetric KEK) : AES-KW --------------------------------------
230
172
  async function _buildKekri(cek, desc) {
231
173
  var kek = guard.bytes.view(desc.kek, CmsError, "cms/bad-input", "kek");
232
174
  if (desc.kekId == null) throw _err("cms/bad-input", "a kek recipient needs a kekId");
@@ -236,33 +178,20 @@ async function _buildKekri(cek, desc) {
236
178
  return { tag: 2, node: b.sequence([b.integer(4n), kekid, _algId(wrapName, "absent"), b.octetString(encryptedKey)]) };
237
179
  }
238
180
 
239
- // A PBKDF2 iterationCount MUST be a positive integer within the same cap the decryptor enforces, so a
240
- // ---- pwri (password) : PBKDF2 + RFC 3211 double-CBC PWRI-KEK ---------------
241
181
  async function _buildPwri(cek, desc) {
242
- // A string / Uint8Array password is encoded into a buffer this toolkit allocated, a credential
243
- // copy, so it is cleared once the derivation has consumed it. A caller-supplied Buffer is
244
- // borrowed and left intact.
245
- // The option validation runs before the password is encoded: a rejected iteration count or salt
246
- // would otherwise abandon an owned credential copy on the way out.
247
182
  var iterations = pbes2.assertIterations(desc.iterations == null ? 600000 : desc.iterations, _err, "cms");
248
183
  var salt = desc.salt ? pbes2.assertSalt(guard.bytes.view(desc.salt, CmsError, "cms/bad-input", "salt"), _err, "cms") : nodeCrypto.randomBytes(16);
249
184
  var prf = desc.prf || "hmacWithSHA256";
250
- _prfHash(prf); // reject an unsupported prf HERE, before a credential copy exists to abandon
251
- var innerKeyBytes = 32; // AES-256-CBC inner
185
+ _prfHash(prf);
186
+ var innerKeyBytes = 32;
252
187
  var pwOwn = pbes2.passwordBytesOwned(desc.password, _err, "cms");
253
188
  var password = pwOwn.bytes;
254
189
  var kekKey = await subtle.importKey("raw", password, { name: "PBKDF2" }, false, ["deriveBits"]);
255
- // The derived KEK is this function's allocation and is cleared below. `password` is not: when the
256
- // caller passes a Buffer, passwordBytes hands back that very buffer, so wiping it would destroy
257
- // the caller's own memory, the one failure this rule must never cause.
258
190
  var kek = Buffer.from(await subtle.deriveBits({ name: "PBKDF2", hash: _prfHash(prf), salt: salt, iterations: iterations }, kekKey, innerKeyBytes * 8));
259
191
  if (pwOwn.owned) guard.secret.zeroize(password, CmsError, "cms/bad-input", "the password encoding");
260
192
  try {
261
- // The RFC 3211 double-CBC wrap under an inner AES-256-CBC whose IV is carried in the
262
- // keyEncryptionAlgorithm parameter (id-alg-PWRI-KEK parameter = the inner cipher AlgorithmIdentifier).
263
193
  var iv = nodeCrypto.randomBytes(16);
264
194
  var encryptedKey = _pwriWrapIv(kek, cek, iv);
265
- // PBKDF2-params as keyDerivationAlgorithm [0] IMPLICIT.
266
195
  var kdfParams = pbes2.pbkdf2ParamsSeq(salt, iterations, prf);
267
196
  var kdfAlg = b.contextConstructed(0, Buffer.concat([b.oid(O("pbkdf2")), kdfParams]));
268
197
  var keyEncAlg = b.sequence([b.oid(O("id-alg-PWRI-KEK")), b.sequence([b.oid(O("aes256-CBC")), b.octetString(iv)])]);
@@ -272,15 +201,12 @@ async function _buildPwri(cek, desc) {
272
201
  }
273
202
  }
274
203
 
275
- // ---- kemri (ML-KEM ori) : RFC 9629 + 9936 ---------------------------------
276
- var KEM_WRAP = {}; // ML-KEM OID -> wrap name (RFC 9936 sec. 2.2.1)
204
+ var KEM_WRAP = {};
277
205
  KEM_WRAP[O("id-ml-kem-512")] = "aes128-wrap";
278
206
  KEM_WRAP[O("id-ml-kem-768")] = "aes256-wrap";
279
207
  KEM_WRAP[O("id-ml-kem-1024")] = "aes256-wrap";
280
208
  var KEM_WC = {}; KEM_WC[O("id-ml-kem-512")] = "ML-KEM-512"; KEM_WC[O("id-ml-kem-768")] = "ML-KEM-768"; KEM_WC[O("id-ml-kem-1024")] = "ML-KEM-1024";
281
209
 
282
- // CMSORIforKEMOtherInfo ::= SEQUENCE { wrap AlgorithmIdentifier, kekLength INTEGER,
283
- // ukm [0] EXPLICIT OCTET STRING OPTIONAL }, the RFC 9629 sec. 5 KDF info, one builder both sides.
284
210
  function _kemOtherInfo(wrapName, kekBytes, ukm) {
285
211
  var kids = [_algId(wrapName, "absent"), b.integer(BigInt(kekBytes))];
286
212
  if (ukm) kids.push(b.explicit(0, b.octetString(ukm)));
@@ -310,46 +236,30 @@ async function _buildKemri(cek, cert, opts) {
310
236
  var kemri = b.sequence(kemriKids);
311
237
  return { tag: 4, node: b.sequence([b.oid(O("kem")), kemri]) };
312
238
  } finally {
313
- // RFC 9629 sec. 7 asks the SENDER to discard the shared secret and KEK once the recipient entry
314
- // is built, and to use a fresh secret per recipient, so this runs per call and not once at
315
- // the end of a multi-recipient message. In a `finally`, so a wrap or encoding failure does not
316
- // leave them behind. The CEK is the caller's and is wiped by no one here; kemct is public.
317
- // kem.sharedKey is the ArrayBuffer the engine returned and ss is this function's copy of it;
318
- // both hold the secret, so both are cleared. A Uint8Array view aliases the buffer's bytes, so
319
- // wiping the view wipes the buffer itself. kem.ciphertext is public and stays.
320
239
  guard.secret.zeroizeAll([ss, kek, kem.sharedKey ? new Uint8Array(kem.sharedKey) : null, kekAb ? new Uint8Array(kekAb) : null],
321
240
  CmsError, "cms/bad-input", "the KEM shared secret");
322
241
  }
323
242
  }
324
243
 
325
- // AES-KW wrap of the CEK under a raw KEK.
326
244
  async function _aesKwWrap(kek, cek) {
327
245
  var kekKey = await subtle.importKey("raw", kek, { name: "AES-KW" }, false, ["wrapKey"]);
328
246
  var cekKey = await subtle.importKey("raw", cek, { name: "AES-CBC" }, true, ["encrypt", "decrypt"]);
329
247
  return Buffer.from(await subtle.wrapKey("raw", cekKey, kekKey, { name: "AES-KW" }));
330
248
  }
331
249
 
332
- // RFC 3211 sec. 2.3.1 wrap formatting + double-CBC (sec. 2.3.2).
333
250
  function _pwriFormat(cek) {
334
251
  var count = cek.length;
335
- // Coverage residual: for the AES CEK sizes the toolkit produces (16/24/32), `count` is always in
336
- // range, `body` (4 + count) is never a 16-octet multiple, and `body + padLen` always reaches the
337
- // 2-block minimum -- so the out-of-range throw and the two zero/underflow pad arms never fire.
338
252
  if (count < 1 || count > 255) throw _err("cms/bad-input", "the CEK length is out of the RFC 3211 range");
339
253
  var check = Buffer.from([count, cek[0] ^ 0xff, cek[1] ^ 0xff, cek[2] ^ 0xff]);
340
254
  var body = Buffer.concat([check, cek]);
341
255
  var blk = 16;
342
256
  var padLen = body.length % blk === 0 ? 0 : blk - (body.length % blk);
343
257
  if (body.length + padLen < 2 * blk) padLen += (2 * blk - (body.length + padLen));
344
- // and each hold a plaintext copy of the CEK; only the padded result is returned, so
345
- // the intermediates are cleared here and never abandoned.
346
258
  var wrapped = Buffer.concat([body, nodeCrypto.randomBytes(padLen)]);
347
259
  guard.secret.zeroizeAll([check, body], CmsError, "cms/bad-input", "a PWRI formatting intermediate");
348
260
  return wrapped;
349
261
  }
350
262
  function _pwriWrapIv(kek, cek, iv) {
351
- // wk is the formatted plaintext -- a complete copy of the CEK -- and is cleared once the first
352
- // encryption pass has consumed it. pass1 is already ciphertext.
353
263
  var wk = _pwriFormat(cek);
354
264
  try {
355
265
  var c1 = nodeCrypto.createCipheriv("aes-256-cbc", kek, iv); c1.setAutoPadding(false);
@@ -365,27 +275,11 @@ function _pwriWrapIv(kek, cek, iv) {
365
275
  var PRF_HASH = { hmacWithSHA1: "SHA-1", hmacWithSHA256: "SHA-256", hmacWithSHA384: "SHA-384", hmacWithSHA512: "SHA-512" };
366
276
  function _prfHash(prf) { if (!PRF_HASH[prf]) throw _err("cms/bad-input", "unsupported pwri prf " + JSON.stringify(prf)); return PRF_HASH[prf]; }
367
277
 
368
- // ---- recipient dispatch ----------------------------------------------------
369
- // The fields a recipient descriptor carries, PER ARM. Each arm reads a disjoint set, and every
370
- // field beyond the selector is optional with a default, so an unrecognized name is missed by
371
- // nothing and the recipient is built with the shipped parameters instead: `oaepHsh` for `oaepHash`
372
- // encrypts the content key under the default SHA-256 OAEP, and a misspelled `iterations`, `salt`
373
- // or `prf` on a password recipient derives the key at the default work factor. A union table would
374
- // admit another arm's fields, which the chosen arm never reads.
375
278
  var _PWRI_KEYS = { password: 1, iterations: 1, prf: 1, salt: 1 };
376
279
  var _KEKRI_KEYS = { kek: 1, kekId: 1 };
377
- // A certificate recipient dispatches on the certificate's KEY ALGORITHM, and the arms read
378
- // different fields: ktri (RSA) reads oaepHash and never ukm; kari (ECDH / X25519 / X448) and kemri
379
- // (ML-KEM) read ukm and never oaepHash. One table across all three accepted `oaepHash` on an EC
380
- // certificate, where nothing reads it, so the content key was wrapped under the arm's own defaults
381
- // while the caller had named a hash. Checked after the key algorithm resolves, since that is what
382
- // selects the arm.
383
280
  var _KTRI_RECIPIENT_KEYS = { cert: 1, oaepHash: 1, keyIdentifier: 1 };
384
281
  var _AGREE_RECIPIENT_KEYS = { cert: 1, keyIdentifier: 1, ukm: 1 };
385
282
 
386
- // The three top-level options that are per-recipient DEFAULTS rather than message-wide settings.
387
- // Which of them any given message actually consumes depends on the recipient arms it selects, so
388
- // they are checked against the arms after those resolve, not against a fixed list.
389
283
  var _RECIPIENT_DEFAULTS = ["oaepHash", "keyIdentifier", "ukm"];
390
284
 
391
285
  function _assertRecipientKeys(desc, known, arm) {
@@ -395,9 +289,6 @@ function _assertRecipientKeys(desc, known, arm) {
395
289
  });
396
290
  }
397
291
 
398
- // A default no selected recipient reads is discarded exactly as a misspelling would be: `oaepHash`
399
- // on a message whose only recipient is a password recipient names a wrapping the message does not
400
- // perform. Run after every recipient's arm has resolved and BEFORE anything is emitted.
401
292
  function _assertDefaultsConsumed(opts, consumed) {
402
293
  for (var i = 0; i < _RECIPIENT_DEFAULTS.length; i++) {
403
294
  var name = _RECIPIENT_DEFAULTS[i];
@@ -412,9 +303,6 @@ function _assertDefaultsConsumed(opts, consumed) {
412
303
 
413
304
  async function _buildRecipient(cek, desc, opts, consumed) {
414
305
  if (desc == null || typeof desc !== "object") throw _err("cms/bad-input", "each recipient must be a descriptor object");
415
- // Checked per arm, on the same selector the dispatch below uses, so the table that runs is the
416
- // one belonging to the arm that will build. `consumed` records which top-level defaults the
417
- // selected arms read, so a default no recipient consumes can be refused once they all resolve.
418
306
  if (desc.password != null) { _assertRecipientKeys(desc, _PWRI_KEYS, "password"); return _buildPwri(cek, desc); }
419
307
  if (desc.kek != null) { _assertRecipientKeys(desc, _KEKRI_KEYS, "KEK"); return _buildKekri(cek, desc); }
420
308
  if (desc.cert != null) {
@@ -436,10 +324,6 @@ async function _buildRecipient(cek, desc, opts, consumed) {
436
324
  }
437
325
  throw _err("cms/bad-input", "a recipient needs { cert }, { password }, or { kek, kekId }");
438
326
  }
439
- // Each default is taken from the descriptor when it supplies one and from the top-level options
440
- // otherwise. That one choice is also the record of which top-level defaults the recipient CONSUMED:
441
- // an arm that does not read the name never consumes it, and neither does one whose descriptor
442
- // overrode it, so recording it from the arm's table alone would accept a value nothing read.
443
327
  function _pickDefault(desc, opts, name, known, consumed, nullish) {
444
328
  var supplied = desc[name], fromDesc = nullish ? supplied != null : !!supplied;
445
329
  if (!fromDesc && known[name] && consumed && opts[name] != null) consumed[name] = 1;
@@ -452,29 +336,18 @@ function mergeOpts(opts, desc, known, consumed) {
452
336
  ukm: _pickDefault(desc, opts, "ukm", known, consumed, true),
453
337
  };
454
338
  }
455
- // RecipientInfo CHOICE: ktri untagged; kari [1], kekri [2], pwri [3], ori [4], all IMPLICIT,
456
- // so the arm's SEQUENCE tag is replaced by the context tag.
457
339
  function _taggedRecipient(r) {
458
340
  if (r.tag == null) return r.node;
459
341
  return b.contextConstructed(r.tag, r.node.subarray(_tlvHeaderLen(r.node)));
460
342
  }
461
343
 
462
- // EnvelopedData version (RFC 5652 sec. 6.1): any pwri/ori -> 3; else any ri v2 or a kari/kekri
463
- // (v3/v4) -> 2; else 0. We never emit originatorInfo/unprotectedAttrs by default.
464
344
  function _envelopedVersion(recips, hasUnprotected) {
465
- var anyOri = recips.some(function (r) { return r.tag === 4 || r.tag === 3; }); // pwri(3)/ori(4)
345
+ var anyOri = recips.some(function (r) { return r.tag === 4 || r.tag === 3; });
466
346
  if (anyOri) return 3;
467
347
  var forcesTwo = hasUnprotected || recips.some(function (r) { return r.tag === 1 || r.tag === 2 || (r.tag == null && r._riVersion === 2); });
468
348
  return forcesTwo ? 2 : 0;
469
349
  }
470
350
 
471
- // The two exported verbs have DISJOINT option sets, so they get separate tables: one shared table
472
- // would admit every other verb's keys, which is the hole rather than the door. Each is the union of
473
- // what the verb reads THROUGH ITS DELEGATIONS -- encrypt never names oaepHash, keyIdentifier or ukm
474
- // itself, but hands opts to the per-recipient builders that do, and to _emit for pem.
475
- // Authenticated attributes live in an AuthEnvelopedData, which only an AEAD content algorithm
476
- // produces: a CBC algorithm selects EnvelopedData, which has no field for them and is not handed
477
- // opts at all. Requested there, they named a protection the message does not carry.
478
351
  var _ENCRYPT_OPTS_AEAD = {
479
352
  contentEncryptionAlgorithm: 1, contentType: 1, authAttrs: 1, oaepHash: 1, keyIdentifier: 1,
480
353
  ukm: 1, pem: 1,
@@ -482,8 +355,6 @@ var _ENCRYPT_OPTS_AEAD = {
482
355
  var _ENCRYPT_OPTS_CBC = {
483
356
  contentEncryptionAlgorithm: 1, contentType: 1, oaepHash: 1, keyIdentifier: 1, ukm: 1, pem: 1,
484
357
  };
485
- // authenticatedAttributes: false MACs the content octets directly, and the attribute set is what
486
- // carries the digest: neither the digest algorithm nor any extra attribute is read on that branch.
487
358
  var _AUTHENTICATE_OPTS_ATTRS = {
488
359
  macAlgorithm: 1, digestAlgorithm: 1, authenticatedAttributes: 1, authAttrs: 1, contentType: 1,
489
360
  keyIdentifier: 1, oaepHash: 1, ukm: 1, pem: 1,
@@ -494,12 +365,6 @@ var _AUTHENTICATE_OPTS_BARE = {
494
365
  };
495
366
 
496
367
  function encrypt(content, recipients, opts) {
497
- // The options and recipients are copied BEFORE the content is classified. Classifying reads
498
- // content[Symbol.asyncIterator] -- a caller accessor that runs in-realm -- which could otherwise
499
- // change the content type, the output encoding, or a recipient after the call has begun. With the
500
- // copies taken first, that probe cannot reach them; the streamed sign path copies the same way. The
501
- // copy runs inside guard-async's boundary, so a fault in it leaves as a rejection, and the copies
502
- // (which may hold a symmetric key or a password) are cleared on every path out.
503
368
  return guard.bytes.fixedCall(CmsError, "cms/bad-input", [
504
369
  [recipients, "recipients"], [opts, "pki.cms.encrypt options"],
505
370
  ], function (recipientsCopy, optsCopy) {
@@ -509,8 +374,6 @@ function encrypt(content, recipients, opts) {
509
374
 
510
375
  async function _encryptImpl(content, recipients, opts) {
511
376
  opts = opts || {};
512
- // The content algorithm selects the structure, so it is resolved before the door and the table
513
- // that runs is the one belonging to the structure this call will emit.
514
377
  var algName = opts.contentEncryptionAlgorithm || "aes-256-gcm";
515
378
  var ca = CONTENT_ALGS[algName];
516
379
  if (!ca) throw _err("cms/bad-input", "unsupported contentEncryptionAlgorithm " + JSON.stringify(algName));
@@ -521,19 +384,12 @@ async function _encryptImpl(content, recipients, opts) {
521
384
  ? " for a CBC content algorithm; authenticated attributes are carried only by an AuthEnvelopedData, which an AEAD algorithm produces"
522
385
  : "");
523
386
  });
524
- // An async-iterable content streams the plaintext through the cipher without buffering it whole; a byte
525
- // source takes the one-shot path. asyncStreamOf reads Symbol.asyncIterator once and wins only when the
526
- // value is not already a byte source, so a Buffer with a stray asyncIterator stays a Buffer.
527
387
  var contentStream = guard.bytes.asyncStreamOf(content);
528
388
  var contentBytes = contentStream ? null : guard.bytes.view(content, CmsError, "cms/bad-input", "content");
529
389
  var contentType = opts.contentType || "data";
530
390
  var cek = nodeCrypto.randomBytes(ca.keyBits / 8);
531
391
 
532
- // The CEK protects the content for every recipient, so unlike a per-recipient shared secret it is
533
- // cleared once, after the last recipient entry is built and the content is encrypted. Wiping it
534
- // inside the recipient loop would destroy the key the remaining recipients must be given.
535
392
  try {
536
- // EncryptedData: a single non-array { cek } or { password } descriptor, no RecipientInfos.
537
393
  if (!Array.isArray(recipients)) return await _encryptedData(contentBytes, contentStream, recipients, ca, contentType, opts, cek);
538
394
 
539
395
  if (!recipients.length) throw _err("cms/bad-input", "at least one recipient is required (RFC 5652 sec. 6.1)");
@@ -555,9 +411,6 @@ function _emit(inner, ctName, opts) {
555
411
  return opts.pem ? schemaCms.pemEncode(ci, "CMS") : ci;
556
412
  }
557
413
 
558
- // Run a streamed plaintext through the content cipher, translating the engine's malformed-streamed-content
559
- // domain into this verb's cms/bad-input via the shared guard, so pki.cms.encrypt keeps its typed CmsError
560
- // contract (the streamed sign/verify paths translate the same way).
561
414
  async function _encStream(spec, stream) {
562
415
  try {
563
416
  return await subtle.encryptStream(spec, stream);
@@ -568,9 +421,6 @@ async function _encStream(spec, stream) {
568
421
 
569
422
  async function _envelopedData(contentBytes, contentStream, cek, ca, contentType, riNodes, recips) {
570
423
  var iv = nodeCrypto.randomBytes(16);
571
- // A streamed plaintext runs through the CBC cipher incrementally (assembling only the ciphertext, whose
572
- // length the encryptedContent OCTET STRING needs); a Buffer takes the one-shot path. Both produce the
573
- // same wire bytes for the same plaintext, so the emitted DER is identical to the buffered form.
574
424
  var enc = contentStream
575
425
  ? (await _encStream({ algNode: "aes-" + ca.keyBits + "-cbc", key: cek, iv: iv }, contentStream)).ciphertext
576
426
  : pbes2.cbcEncrypt(cek, iv, contentBytes, ca.keyBits, CmsError, "cms/bad-input");
@@ -578,23 +428,7 @@ async function _envelopedData(contentBytes, contentStream, cek, ca, contentType,
578
428
  return b.sequence([b.integer(BigInt(_envelopedVersion(recips, false))), b.setOf(riNodes), eci]);
579
429
  }
580
430
 
581
- // Every authenticated attribute this module MACs -- auto-built or caller-supplied, in either content
582
- // type that carries them -- MUST be a well-formed Attribute SEQUENCE { type OBJECT IDENTIFIER,
583
- // non-empty SET OF value } with no repeated type (RFC 5652 sec. 5.3). Rejecting here means a
584
- // malformed attribute never reaches the wire; leaving it to the wire means the operator learns from
585
- // a peer's parser that the message they built is unreadable, with the MAC already computed over it.
586
- //
587
- // `forbidden` names an attribute type this particular content type must not emit, with the clause
588
- // that says so. It is a parameter rather than a constant because the same attribute is required in
589
- // one content type and discouraged in the other: AuthenticatedData MACs its own message-digest by
590
- // design (RFC 5652 sec. 9.2), while for AuthEnvelopedData that value is the unencrypted hash of a
591
- // plaintext the message exists to conceal (RFC 5083 sec. 2.1 / sec. 5).
592
431
  function _assertAuthAttrs(pairs, forbidden) {
593
- // Reject a sparse/nullish attribute array as a typed error before b.setOf reaches it as a native one.
594
- // This gate runs before the setOf on both the AuthEnveloped and AuthenticatedData paths.
595
- // Validate AND SNAPSHOT: reqDenseArray returns a fresh dense copy, and it is that copy the caller must
596
- // iterate and encode. Iterating the original `pairs` would let an indexed getter that sparsified it
597
- // during validation slip a hole into b.setOf as a native error; the returned snapshot cannot.
598
432
  var dense = pkiBuild.reqDenseArray(pairs, "authAttrs", _err, "cms/bad-input");
599
433
  var seenTypes = {};
600
434
  dense.forEach(function (p) {
@@ -614,9 +448,6 @@ function _assertAuthAttrs(pairs, forbidden) {
614
448
  return dense;
615
449
  }
616
450
 
617
- // The one attribute AuthEnvelopedData declines to emit. Its value is the unencrypted one-way hash of
618
- // the plaintext, so disclosing it enables content tracking and confirms a guessed plaintext against
619
- // a message that was encrypted to prevent exactly that.
620
451
  var AUTH_ENVELOPED_FORBIDDEN = {
621
452
  oid: O("messageDigest"),
622
453
  why: "authAttrs must not carry the message-digest attribute in an AuthEnvelopedData: its value is the unencrypted hash of the plaintext, which enables content tracking and confirmation of a guessed plaintext (RFC 5083 sec. 2.1, sec. 5)",
@@ -625,22 +456,14 @@ var AUTH_ENVELOPED_FORBIDDEN = {
625
456
  async function _authEnvelopedData(contentBytes, contentStream, cek, ca, contentType, opts, riNodes, recips) {
626
457
  var nonce = nodeCrypto.randomBytes(12);
627
458
  var authAttrsDer = null, aad = Buffer.alloc(0);
628
- // RFC 5083 sec. 2.1: authAttrs MUST be present when the content type is not id-data. Emitting an
629
- // AuthEnvelopedData without them for a non-data type produces a message our own strict parser rejects.
630
459
  if (contentType !== "data" && !(opts.authAttrs && opts.authAttrs.length)) {
631
460
  throw _err("cms/bad-input", "AuthEnvelopedData with a non-data contentType requires authAttrs (RFC 5083 sec. 2.1)");
632
461
  }
633
462
  if (opts.authAttrs && opts.authAttrs.length) {
634
463
  var denseAttrs = _assertAuthAttrs(opts.authAttrs, AUTH_ENVELOPED_FORBIDDEN);
635
- // authAttrs are transmitted [1] IMPLICIT but MACed under the EXPLICIT SET OF tag (RFC 5083 sec. 2.2).
636
464
  var setOf = b.setOf(denseAttrs);
637
465
  aad = setOf; authAttrsDer = b.contextConstructed(1, setOf.subarray(_tlvHeaderLen(setOf)));
638
466
  }
639
- // A 16-octet (128-bit) GCM tag, the strongest ICV and what OpenSSL emits, so the message
640
- // interops across OpenSSL 3.5 / 4.x. The aes-ICVlen (16) is carried explicitly (RFC 5084 sec. 3.2
641
- // omits it only when it equals the DEFAULT 12); it MUST equal the mac octet length (M42).
642
- // A streamed plaintext runs through the GCM cipher incrementally; the tag is produced at final() over the
643
- // same bytes, so the ciphertext + tag equal the one-shot form for the same plaintext and AAD.
644
467
  var g;
645
468
  if (contentStream) {
646
469
  var r = await _encStream({ algNode: "aes-" + ca.keyBits + "-gcm", key: cek, iv: nonce, aad: aad, authTagLength: 16 }, contentStream);
@@ -656,23 +479,12 @@ async function _authEnvelopedData(contentBytes, contentStream, cek, ca, contentT
656
479
  return b.sequence(kids);
657
480
  }
658
481
 
659
- // The single descriptor an EncryptedData takes, PER ARM. `cek` and `password` select the arm and
660
- // the arms read disjoint sets, so a union would admit the other arm's fields: `{ cek, iterations }`
661
- // supplies a work factor to a branch that derives nothing, and the caller's parameter is discarded
662
- // exactly as a misspelling would be. `pem` is read after both arms, so it belongs to each.
663
- // pem selects the output encoding of the whole call and is an OPTION, the third argument, on every
664
- // other verb here. A second spelling on the descriptor was read by the password arm alone, so the
665
- // cek arm accepted it and returned DER; there is now one place to ask for PEM.
666
482
  var _ENCRYPTED_DATA_CEK_KEYS = { cek: 1 };
667
483
  var _ENCRYPTED_DATA_PW_KEYS = { password: 1, iterations: 1, salt: 1, prf: 1 };
668
484
 
669
485
  async function _encryptedData(contentBytes, contentStream, desc, ca, contentType, opts, cek) {
670
486
  if (ca.aead) throw _err("cms/bad-input", "EncryptedData supports only CBC content encryption");
671
- // An EncryptedData carries no RecipientInfos at all, so nothing here reads a per-recipient
672
- // default. This branch returns before the recipient loop, so it answers for them itself.
673
487
  _assertDefaultsConsumed(opts, {});
674
- // Checked per arm, on the same selector the dispatch below uses, so the table that runs belongs
675
- // to the branch that will encrypt.
676
488
  if (desc != null && typeof desc === "object") {
677
489
  var edKeys = desc.cek != null ? _ENCRYPTED_DATA_CEK_KEYS : _ENCRYPTED_DATA_PW_KEYS;
678
490
  var edArm = desc.cek != null ? "cek" : "password";
@@ -701,16 +513,11 @@ async function _encryptedData(contentBytes, contentStream, desc, ca, contentType
701
513
  }
702
514
 
703
515
  async function _encryptedDataPbes2(contentBytes, contentStream, desc, ca, contentType, iv, opts) {
704
- // A string / Uint8Array password is encoded into a buffer this toolkit allocated, a credential
705
- // copy, so it is cleared once the derivation has consumed it. A caller-supplied Buffer is
706
- // borrowed and left intact.
707
516
  var pwOwn2 = pbes2.passwordBytesOwned(desc.password, _err, "cms");
708
517
  var password = pwOwn2.bytes;
709
518
  var iterations = pbes2.assertIterations(desc.iterations == null ? 600000 : desc.iterations, _err, "cms");
710
519
  var salt = desc.salt ? pbes2.assertSalt(guard.bytes.view(desc.salt, CmsError, "cms/bad-input", "salt"), _err, "cms") : nodeCrypto.randomBytes(16);
711
520
  var prf = desc.prf || "hmacWithSHA256";
712
- // The password-derived content key is this function's allocation; `password` may be the caller's
713
- // own buffer (passwordBytes passes a Buffer straight through) and is left alone.
714
521
  var key = nodeCrypto.pbkdf2Sync(password, salt, iterations, ca.keyBits / 8, pbes2.prfNodeByName(prf, _err, "cms"));
715
522
  if (pwOwn2.owned) guard.secret.zeroize(password, CmsError, "cms/bad-input", "the password encoding");
716
523
  try {
@@ -726,7 +533,6 @@ async function _encryptedDataPbes2(contentBytes, contentStream, desc, ca, conten
726
533
  }
727
534
  }
728
535
 
729
- // ---- content-encryption primitives ----------------------------------------
730
536
  function _gcmEncrypt(key, nonce, plaintext, aad, keyBits, tagLen) {
731
537
  var c = nodeCrypto.createCipheriv("aes-" + keyBits + "-gcm", key, nonce, { authTagLength: tagLen });
732
538
  if (aad && aad.length) c.setAAD(aad);
@@ -734,38 +540,23 @@ function _gcmEncrypt(key, nonce, plaintext, aad, keyBits, tagLen) {
734
540
  return { ct: ct, tag: c.getAuthTag() };
735
541
  }
736
542
 
737
- // The length of a DER TLV's tag+length header (so the SET OF re-tag drops the tag byte(s)).
738
543
  function _tlvHeaderLen(der) {
739
544
  var lenByte = der[1];
740
545
  if (lenByte < 0x80) return 2;
741
546
  return 2 + (lenByte & 0x7f);
742
547
  }
743
548
 
744
- // ---- pki.cms.authenticate (RFC 5652 sec. 9 AuthenticatedData) ---------------
745
- // AuthenticatedData carries CLEARTEXT content plus a MAC (no content encryption). A single fresh HMAC
746
- // key is minted, wrapped for every recipient with the EXACT RecipientInfo model EnvelopedData uses for
747
- // the CEK, and the MAC (HMAC-SHA-2) covers either the re-tagged [2] authAttrs SET OF (default) or the
748
- // eContent value octets directly. Homes here (not a new module) so the recipient-wrap helpers are
749
- // module-local; the verify half folds into pki.cms.decrypt.
750
549
 
751
- // The macAlgorithm registry: an OID name + the WebCrypto/node hashes (data-driven, not a switch).
752
550
  var MAC_ALGS = {
753
551
  "hmac-sha256": { oid: "hmacWithSHA256", wc: "SHA-256", node: "sha256" },
754
552
  "hmac-sha384": { oid: "hmacWithSHA384", wc: "SHA-384", node: "sha384" },
755
553
  "hmac-sha512": { oid: "hmacWithSHA512", wc: "SHA-512", node: "sha512" },
756
554
  };
757
- // The fresh MAC key length: 32 octets is AES-KW-compatible (16/24/32), so it wraps through every
758
- // recipient arm (kari/kekri/kemri import the wrapped key as AES-CBC) with no codec change, and a
759
- // 32-octet conveyed key is a valid HMAC key for SHA-256/384/512 (RFC 2104).
760
555
  var MAC_KEY_OCTETS = 32;
761
- // The message-digest hashes the AuthenticatedData verify path (cms-decrypt DIGEST_WC) can consume --
762
- // the producer never emits a digestAlgorithm its own verifier cannot recompute (the no-orphan rule).
763
556
  var SUPPORTED_DIGEST = { sha256: 1, sha384: 1, sha512: 1 };
764
557
 
765
558
  async function authenticate(content, recipients, opts) {
766
559
  opts = opts || {};
767
- // The attribute branch is selected once, and the door and the MAC below both act on that one
768
- // decision, so the table that runs belongs to the branch that will compute the MAC.
769
560
  var withAttrs = opts.authenticatedAttributes !== false;
770
561
  guard.identifier.assertKnownKeys(opts, withAttrs ? _AUTHENTICATE_OPTS_ATTRS : _AUTHENTICATE_OPTS_BARE,
771
562
  _err, "cms/bad-input", function (k) {
@@ -780,11 +571,8 @@ async function authenticate(content, recipients, opts) {
780
571
  if (!mac) throw _err("cms/bad-input", "unsupported macAlgorithm " + JSON.stringify(macName) + " (hmac-sha256/384/512)");
781
572
  if (!Array.isArray(recipients) || !recipients.length) throw _err("cms/bad-input", "at least one recipient is required (RFC 5652 sec. 9.1)");
782
573
  var contentType = opts.contentType || "data";
783
- // RFC 5652 sec. 9.1: authAttrs MUST be present when the eContentType is not id-data.
784
574
  if (contentType !== "data" && !withAttrs) throw _err("cms/bad-input", "AuthenticatedData with a non-data contentType requires authenticated attributes (RFC 5652 sec. 9.1)");
785
575
 
786
- // The MAC key is this path's content-encryption key: every recipient is given it, so it is cleared
787
- // once at the end and not per recipient, and only after the MAC has been computed over it.
788
576
  var macKey = nodeCrypto.randomBytes(MAC_KEY_OCTETS);
789
577
  try {
790
578
  var recips = [];
@@ -795,12 +583,8 @@ async function authenticate(content, recipients, opts) {
795
583
 
796
584
  var digestAlgTagged = null, authAttrsDer = null, preimage;
797
585
  if (withAttrs) {
798
- // Inside the branch that uses it: the attribute set is what carries the digest, so on the
799
- // other branch there is no digest to name and opts.digestAlgorithm is refused at the door.
800
586
  var digestName = opts.digestAlgorithm || mac.node;
801
587
  if (!SUPPORTED_DIGEST[digestName]) throw _err("cms/bad-input", "unsupported digestAlgorithm " + JSON.stringify(digestName) + " (sha256/384/512)");
802
- // sec. 9.2: content-type (== eContentType) + message-digest (== digest(content)) attributes,
803
- // SET-OF-sorted, MACed under the EXPLICIT SET OF tag (0x31) but transmitted [2] IMPLICIT (0xA2).
804
588
  var mdDigest = nodeCrypto.createHash(digestName).update(contentBytes).digest();
805
589
  var pairs = [
806
590
  b.sequence([b.oid(O("contentType")), b.setOf([b.oid(O(contentType))])]),
@@ -809,11 +593,11 @@ async function authenticate(content, recipients, opts) {
809
593
  if (opts.authAttrs && opts.authAttrs.length) pairs = pairs.concat(opts.authAttrs);
810
594
  pairs = _assertAuthAttrs(pairs, null);
811
595
  var setOf = b.setOf(pairs);
812
- preimage = setOf; // MAC over the 0x31 SET OF
813
- authAttrsDer = b.contextConstructed(2, setOf.subarray(_tlvHeaderLen(setOf))); // [2] IMPLICIT on the wire
814
- digestAlgTagged = b.contextConstructed(1, b.oid(O(digestName))); // [1] IMPLICIT DigestAlgorithmIdentifier
596
+ preimage = setOf;
597
+ authAttrsDer = b.contextConstructed(2, setOf.subarray(_tlvHeaderLen(setOf)));
598
+ digestAlgTagged = b.contextConstructed(1, b.oid(O(digestName)));
815
599
  } else {
816
- preimage = contentBytes; // MAC over the eContent value octets
600
+ preimage = contentBytes;
817
601
  }
818
602
 
819
603
  var hmacKey = await subtle.importKey("raw", macKey, { name: "HMAC", hash: mac.wc }, false, ["sign"]);
@@ -826,10 +610,6 @@ async function authenticate(content, recipients, opts) {
826
610
  if (authAttrsDer) kids.push(authAttrsDer);
827
611
  kids.push(b.octetString(macValue));
828
612
  var ci = b.sequence([b.oid(O("authData")), b.explicit(0, b.sequence(kids))]);
829
- // Self-verify: the emitted AuthenticatedData MUST re-parse clean through the strict parser, so a
830
- // caller-supplied authenticated attribute with a recognized type but extra RFC 5652 constraints (a
831
- // misplaced type, a signing-time whose value is not a Time) is rejected HERE at build time rather
832
- // than by the recipient's parser after the fact.
833
613
  try { schemaCms.parse(ci); } catch (e) { throw (e instanceof CmsError) ? e : _err("cms/bad-input", "the supplied authenticated attributes produced an invalid AuthenticatedData", e); }
834
614
  return opts.pem ? schemaCms.pemEncode(ci, "CMS") : ci;
835
615
  } finally {