@blamejs/pki 0.2.21 → 0.2.23

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.
@@ -0,0 +1,498 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Copyright (c) blamejs contributors
3
+ "use strict";
4
+ //
5
+ // @internal -- the pki.cms.encrypt implementation. The operator-facing @module pki.cms + the
6
+ // @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
+
18
+ var nodeCrypto = require("crypto");
19
+ var asn1 = require("./asn1-der");
20
+ var oid = require("./oid");
21
+ var x509 = require("./schema-x509");
22
+ var schemaCms = require("./schema-cms");
23
+ var webcrypto = require("./webcrypto");
24
+ var frameworkError = require("./framework-error");
25
+ var guard = require("./guard-all");
26
+ var C = require("./constants");
27
+ var b = asn1.build;
28
+ var subtle = webcrypto.webcrypto.subtle;
29
+ var CmsError = frameworkError.CmsError;
30
+ var WRAP_KEK_LENGTHS = schemaCms.WRAP_KEK_LENGTHS;
31
+
32
+ function O(n) { return oid.byName(n); }
33
+ function _err(code, message, cause) { return new CmsError(code, message, cause); }
34
+
35
+ // An AlgorithmIdentifier { OID } (absent params) or { OID, NULL }.
36
+ function _algId(name, shape) { return shape === "null" ? b.sequence([b.oid(O(name)), b.nullValue()]) : b.sequence([b.oid(O(name))]); }
37
+
38
+ // A certificate descriptor -> raw DER (the recipient cert is parsed for dispatch + rid; the caller
39
+ // supplies bytes, not a re-encoded parse).
40
+ function _normCertDer(cert, what) {
41
+ if (Buffer.isBuffer(cert)) return cert;
42
+ if (cert instanceof Uint8Array) return Buffer.from(cert);
43
+ 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); } }
44
+ throw _err("cms/bad-input", (what || "a certificate") + " must be a DER Buffer, Uint8Array, or PEM string");
45
+ }
46
+
47
+ // ---- content-encryption algorithms (registry, not switch) ------------------
48
+ // name -> { oid, keyBits, aead }. AEAD (GCM) -> AuthEnvelopedData; CBC -> EnvelopedData.
49
+ var CONTENT_ALGS = {
50
+ "aes-128-gcm": { oid: "aes128-GCM", keyBits: 128, aead: true },
51
+ "aes-192-gcm": { oid: "aes192-GCM", keyBits: 192, aead: true },
52
+ "aes-256-gcm": { oid: "aes256-GCM", keyBits: 256, aead: true },
53
+ "aes-128-cbc": { oid: "aes128-CBC", keyBits: 128, aead: false },
54
+ "aes-192-cbc": { oid: "aes192-CBC", keyBits: 192, aead: false },
55
+ "aes-256-cbc": { oid: "aes256-CBC", keyBits: 256, aead: false },
56
+ };
57
+
58
+ // The AES key-wrap OID for a KEK of `keyBytes` octets (16/24/32 -> aes128/192/256-wrap).
59
+ function _wrapOidForKek(keyBytes) {
60
+ if (keyBytes === 16) return "aes128-wrap";
61
+ if (keyBytes === 24) return "aes192-wrap";
62
+ if (keyBytes === 32) return "aes256-wrap";
63
+ // Coverage residual: unreachable via the API -- a KEK/CEK is always an AES key size (16/24/32);
64
+ // a defensive throw for a future caller that hands an off-size key.
65
+ throw _err("cms/bad-input", "no AES key-wrap algorithm for a " + keyBytes + "-octet key-encryption key");
66
+ }
67
+
68
+ // GCMParameters ::= SEQUENCE { aes-nonce OCTET STRING, aes-ICVlen INTEGER DEFAULT 12 } -- the
69
+ // DEFAULT 12 is OMITTED on emit (RFC 5084 sec. 3.2 / canonical DER).
70
+ function _gcmParams(nonce, icvLen) {
71
+ var kids = [b.octetString(nonce)];
72
+ if (icvLen !== 12) kids.push(b.integer(BigInt(icvLen)));
73
+ return b.sequence(kids);
74
+ }
75
+
76
+ // The keyIdentifier option selects the RecipientIdentifier form. "issuerAndSerial" is the documented
77
+ // default (its RFC name issuerAndSerialNumber is accepted too); reject anything else rather than
78
+ // silently emitting issuerAndSerialNumber, so a typo surfaces instead of a rid the caller never asked for.
79
+ function _assertKeyIdentifier(form) {
80
+ if (form != null && form !== "issuerAndSerial" && form !== "issuerAndSerialNumber" && form !== "subjectKeyIdentifier") {
81
+ throw _err("cms/bad-input", "unsupported keyIdentifier " + JSON.stringify(form) + " (use \"issuerAndSerial\" or \"subjectKeyIdentifier\")");
82
+ }
83
+ }
84
+ // The RecipientIdentifier (rid) for a parsed cert: issuerAndSerialNumber (default) or
85
+ // subjectKeyIdentifier [0] IMPLICIT. Both forms per RFC 5652 sec. 6.2.1.
86
+ function _rid(cert, form) {
87
+ _assertKeyIdentifier(form);
88
+ if (form === "subjectKeyIdentifier") {
89
+ var ski = _skiOf(cert);
90
+ if (!ski) throw _err("cms/bad-input", "keyIdentifier: \"subjectKeyIdentifier\" requires the recipient certificate to carry a subjectKeyIdentifier extension");
91
+ return { node: b.contextPrimitive(0, ski), riVersion: 2 };
92
+ }
93
+ return { node: b.sequence([b.raw(cert.issuer.bytes), b.integer(cert.serialNumber)]), riVersion: 0 };
94
+ }
95
+ // Coverage residual (the malformed-extension catch arms in _skiOf + _assertKeyUsage, and the
96
+ // unsupported-curve / low-order / unsupported-KEM-cert throws further below): these validate the
97
+ // CALLER's own recipient certificate at config time (tier-1 THROW). They fire only when a caller
98
+ // supplies a certificate whose SKI/keyUsage extension is malformed, or whose key is an unsupported
99
+ // curve / a low-order Montgomery point / an unsupported KEM -- inputs the toolkit never produces.
100
+ function _skiOf(cert) {
101
+ var exts = cert.extensions || [];
102
+ for (var i = 0; i < exts.length; i++) if (exts[i].name === "subjectKeyIdentifier" && exts[i].value != null) {
103
+ try { return asn1.read.octetString(asn1.decode(exts[i].value)); } catch (e) { throw _err("cms/bad-input", "the certificate's subjectKeyIdentifier extension is malformed", e); }
104
+ }
105
+ return null;
106
+ }
107
+
108
+ // keyUsage bit assertion (M9/M15): a recipient cert WITH a keyUsage extension MUST assert `bitName`.
109
+ var KU_BIT = { digitalSignature: 0, keyEncipherment: 2, dataEncipherment: 3, keyAgreement: 4 };
110
+ function _assertKeyUsage(cert, bitName, arm) {
111
+ var exts = cert.extensions || [];
112
+ for (var i = 0; i < exts.length; i++) {
113
+ if (exts[i].name === "keyUsage" && exts[i].value != null) {
114
+ var ku;
115
+ try { ku = asn1.read.bitString(asn1.decode(exts[i].value)); } catch (e) { throw _err("cms/bad-input", "the recipient certificate's keyUsage extension is malformed", e); }
116
+ var idx = KU_BIT[bitName], byteI = idx >> 3, mask = 0x80 >> (idx & 7);
117
+ if (byteI >= ku.bytes.length || (ku.bytes[byteI] & mask) === 0) throw _err("cms/bad-key-usage", "the " + arm + " recipient certificate's keyUsage does not assert " + bitName);
118
+ return;
119
+ }
120
+ }
121
+ }
122
+
123
+ // ---- ktri (RSA) : RSAES-OAEP, SHA-256 default (v1.5 never emitted) ---------
124
+ var OAEP_HASH = { sha256: "SHA-256", sha384: "SHA-384", sha512: "SHA-512" };
125
+ function _oaepParams(hashName) {
126
+ var hAlg = _algId(hashName, "null");
127
+ var mgf = b.sequence([b.oid(O("mgf1")), hAlg]);
128
+ // pSourceAlgorithm [2] DEFAULT pSpecifiedEmpty -- the empty-label default MUST be omitted (X.690).
129
+ return b.sequence([b.explicit(0, hAlg), b.explicit(1, mgf)]);
130
+ }
131
+ async function _buildKtri(cek, cert, opts) {
132
+ _assertKeyUsage(cert, "keyEncipherment", "ktri");
133
+ var hashName = opts.oaepHash || "sha256";
134
+ if (!OAEP_HASH[hashName]) throw _err("cms/bad-input", "unsupported oaepHash " + JSON.stringify(hashName));
135
+ var pub = await subtle.importKey("spki", cert.subjectPublicKeyInfo.bytes, { name: "RSA-OAEP", hash: OAEP_HASH[hashName] }, false, ["encrypt"]);
136
+ var encryptedKey = Buffer.from(await subtle.encrypt({ name: "RSA-OAEP" }, pub, cek));
137
+ var rid = _rid(cert, opts.keyIdentifier);
138
+ var keyEncAlg = b.sequence([b.oid(O("rsaesOaep")), _oaepParams(hashName)]);
139
+ return { tag: null, _riVersion: rid.riVersion, node: b.sequence([b.integer(BigInt(rid.riVersion)), rid.node, keyEncAlg, b.octetString(encryptedKey)]) };
140
+ }
141
+
142
+ // ---- kari (EC / X25519 / X448) : ephemeral-static ECDH ---------------------
143
+ var EC_KA = {}; // recipient-curve OID -> { curve, x963: {hash, scheme}, coordLen }
144
+ EC_KA[O("prime256v1")] = { curve: "P-256", hash: "SHA-256", scheme: "dhSinglePass-stdDH-sha256kdf-scheme" };
145
+ EC_KA[O("secp384r1")] = { curve: "P-384", hash: "SHA-384", scheme: "dhSinglePass-stdDH-sha384kdf-scheme" };
146
+ EC_KA[O("secp521r1")] = { curve: "P-521", hash: "SHA-512", scheme: "dhSinglePass-stdDH-sha512kdf-scheme" };
147
+ var MONT_KA = {}; // X25519/X448 key OID -> { name, hkdf, scheme }
148
+ MONT_KA[O("X25519")] = { name: "X25519", hkdf: "SHA-256", scheme: "dhSinglePass-stdDH-hkdf-sha256-scheme" };
149
+ MONT_KA[O("X448")] = { name: "X448", hkdf: "SHA-512", scheme: "dhSinglePass-stdDH-hkdf-sha512-scheme" };
150
+
151
+ // ECC-CMS-SharedInfo ::= SEQUENCE { keyInfo AlgorithmIdentifier (the wrap, params ABSENT),
152
+ // entityUInfo [0] EXPLICIT OCTET STRING OPTIONAL, suppPubInfo [2] EXPLICIT OCTET STRING } --
153
+ // suppPubInfo = the KEK length in BITS, 4-octet big-endian (RFC 5753 sec. 7.2). ONE builder,
154
+ // shared by encrypt + decrypt so the two sides cannot diverge.
155
+ function _eccSharedInfo(wrapName, ukm, kekBytes) {
156
+ var kids = [_algId(wrapName, "absent")];
157
+ if (ukm) kids.push(b.explicit(0, b.octetString(ukm)));
158
+ var supp = Buffer.alloc(4); supp.writeUInt32BE(kekBytes * 8, 0);
159
+ kids.push(b.explicit(2, b.octetString(supp)));
160
+ return b.sequence(kids);
161
+ }
162
+ async function _buildKari(cek, cert, opts) {
163
+ _assertKeyUsage(cert, "keyAgreement", "kari");
164
+ var keyAlg = cert.subjectPublicKeyInfo.algorithm;
165
+ var wrapName = _wrapOidForKek(cek.length);
166
+ var ukm = opts.ukm ? guard.bytes.view(opts.ukm, CmsError, "cms/bad-input", "ukm") : null;
167
+ var ecdhPub, origKeyAlgId, kek;
168
+ if (keyAlg.oid === O("ecPublicKey")) {
169
+ var curveOid = asn1.read.oid(asn1.decode(keyAlg.parameters));
170
+ var ka = EC_KA[curveOid];
171
+ if (!ka) throw _err("cms/unsupported-algorithm", "unsupported recipient EC curve for kari");
172
+ var recipPub = await subtle.importKey("spki", cert.subjectPublicKeyInfo.bytes, { name: "ECDH", namedCurve: ka.curve }, false, []);
173
+ var eph = await subtle.generateKey({ name: "ECDH", namedCurve: ka.curve }, true, ["deriveBits"]);
174
+ origKeyAlgId = { spki: Buffer.from(await subtle.exportKey("spki", eph.publicKey)), scheme: ka.scheme };
175
+ var z = Buffer.from(await subtle.deriveBits({ name: "ECDH", public: recipPub }, eph.privateKey, null));
176
+ var zKey = await subtle.importKey("raw", z, { name: "X963KDF" }, false, ["deriveBits"]);
177
+ var sharedInfo = _eccSharedInfo(wrapName, ukm, cek.length);
178
+ kek = Buffer.from(await subtle.deriveBits({ name: "X963KDF", hash: ka.hash, info: sharedInfo }, zKey, cek.length * 8));
179
+ void ecdhPub;
180
+ } else if (MONT_KA[keyAlg.oid]) {
181
+ var mka = MONT_KA[keyAlg.oid];
182
+ var rPub = await subtle.importKey("spki", cert.subjectPublicKeyInfo.bytes, { name: mka.name }, false, []);
183
+ var meph = await subtle.generateKey({ name: mka.name }, true, ["deriveBits"]);
184
+ origKeyAlgId = { spki: Buffer.from(await subtle.exportKey("spki", meph.publicKey)), scheme: mka.scheme };
185
+ var mz = Buffer.from(await subtle.deriveBits({ name: mka.name, public: rPub }, meph.privateKey, null));
186
+ if (mz.every(function (x) { return x === 0; })) throw _err("cms/bad-input", "the X25519/X448 shared secret is all-zero (low-order point)");
187
+ var mzKey = await subtle.importKey("raw", mz, { name: "HKDF" }, false, ["deriveBits"]);
188
+ // RFC 8418 sec. 2.2: when a ukm is present it is used BOTH as the HKDF salt AND as the
189
+ // ECC-CMS-SharedInfo entityUInfo (the HKDF info) -- omitting it from the info diverges the KEK
190
+ // from any conformant peer that reads the transmitted ukm.
191
+ 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));
192
+ } else {
193
+ // Coverage residual: unreachable -- _buildRecipient routes only ecPublicKey / X25519 / X448
194
+ // keys into _buildKari; a defensive throw against a future dispatch change.
195
+ throw _err("cms/unsupported-algorithm", "unsupported recipient key algorithm for kari");
196
+ }
197
+ var encryptedKey = await _aesKwWrap(kek, cek);
198
+ // originatorKey [1] IMPLICIT OriginatorPublicKey { algorithm, publicKey BIT STRING }.
199
+ var origSpki = asn1.decode(origKeyAlgId.spki);
200
+ var origPubBits = origSpki.children[1]; // BIT STRING node
201
+ var originatorKey = b.contextConstructed(1, Buffer.concat([origSpki.children[0].bytes, origPubBits.bytes]));
202
+ // KeyAgreeRecipientIdentifier CHOICE { issuerAndSerialNumber, rKeyId [0] IMPLICIT
203
+ // RecipientKeyIdentifier } -- the SKI form here wraps a SEQUENCE (rKeyId), unlike ktri's bare
204
+ // subjectKeyIdentifier [0] IMPLICIT OCTET STRING.
205
+ var ridNode;
206
+ _assertKeyIdentifier(opts.keyIdentifier);
207
+ if (opts.keyIdentifier === "subjectKeyIdentifier") {
208
+ var ski = _skiOf(cert);
209
+ if (!ski) throw _err("cms/bad-input", "keyIdentifier: \"subjectKeyIdentifier\" requires the recipient certificate to carry a subjectKeyIdentifier extension");
210
+ ridNode = b.contextConstructed(0, b.octetString(ski));
211
+ } else {
212
+ ridNode = b.sequence([b.raw(cert.issuer.bytes), b.integer(cert.serialNumber)]);
213
+ }
214
+ var rek = b.sequence([b.sequence([ridNode, b.octetString(encryptedKey)])]); // RecipientEncryptedKeys SEQ OF { rid, encKey }
215
+ var kekAlg = b.sequence([b.oid(O(origKeyAlgId.scheme)), _algId(wrapName, "absent")]);
216
+ var kariKids = [b.integer(3n), b.explicit(0, originatorKey)];
217
+ if (ukm) kariKids.push(b.explicit(1, b.octetString(ukm)));
218
+ kariKids.push(kekAlg, rek);
219
+ return { tag: 1, node: b.sequence(kariKids) };
220
+ }
221
+
222
+ // ---- kekri (symmetric KEK) : AES-KW --------------------------------------
223
+ async function _buildKekri(cek, desc) {
224
+ var kek = guard.bytes.view(desc.kek, CmsError, "cms/bad-input", "kek");
225
+ if (desc.kekId == null) throw _err("cms/bad-input", "a kek recipient needs a kekId");
226
+ var wrapName = _wrapOidForKek(kek.length);
227
+ var encryptedKey = await _aesKwWrap(kek, cek);
228
+ var kekid = b.sequence([b.octetString(guard.bytes.view(desc.kekId, CmsError, "cms/bad-input", "kekId"))]);
229
+ return { tag: 2, node: b.sequence([b.integer(4n), kekid, _algId(wrapName, "absent"), b.octetString(encryptedKey)]) };
230
+ }
231
+
232
+ // A PBKDF2 iterationCount MUST be a positive integer within the same cap the decryptor enforces -- a
233
+ // fractional / non-finite / <1 value would reach node's pbkdf2 (or BigInt() on encode) as a native
234
+ // RangeError, and an over-cap value would produce a message our own decrypt refuses (cms/iteration-limit).
235
+ function _assertIterations(n) {
236
+ if (typeof n !== "number" || !isFinite(n) || n < 1 || Math.floor(n) !== n) throw _err("cms/bad-input", "iterations must be a positive integer");
237
+ if (n > C.LIMITS.PBKDF2_MAX_ITERATIONS) throw _err("cms/bad-input", "iterations exceeds the " + C.LIMITS.PBKDF2_MAX_ITERATIONS + " cap");
238
+ return n;
239
+ }
240
+ // Bound the PBKDF2 salt to the same cap the decryptor enforces, so a message we emit is always one we
241
+ // can read back.
242
+ function _assertSalt(salt) {
243
+ if (salt.length > C.LIMITS.PBKDF2_MAX_SALT) throw _err("cms/bad-input", "salt exceeds the " + C.LIMITS.PBKDF2_MAX_SALT + "-octet cap");
244
+ return salt;
245
+ }
246
+
247
+ // ---- pwri (password) : PBKDF2 + RFC 3211 double-CBC PWRI-KEK ---------------
248
+ async function _buildPwri(cek, desc) {
249
+ var password = _passwordBytes(desc.password);
250
+ var iterations = _assertIterations(desc.iterations == null ? 600000 : desc.iterations);
251
+ var salt = desc.salt ? _assertSalt(guard.bytes.view(desc.salt, CmsError, "cms/bad-input", "salt")) : nodeCrypto.randomBytes(16);
252
+ var prf = desc.prf || "hmacWithSHA256";
253
+ var innerKeyBytes = 32; // AES-256-CBC inner
254
+ var kekKey = await subtle.importKey("raw", password, { name: "PBKDF2" }, false, ["deriveBits"]);
255
+ var kek = Buffer.from(await subtle.deriveBits({ name: "PBKDF2", hash: _prfHash(prf), salt: salt, iterations: iterations }, kekKey, innerKeyBytes * 8));
256
+ // The RFC 3211 double-CBC wrap under an inner AES-256-CBC whose IV is carried in the
257
+ // keyEncryptionAlgorithm parameter (id-alg-PWRI-KEK parameter = the inner cipher AlgorithmIdentifier).
258
+ var iv = nodeCrypto.randomBytes(16);
259
+ var encryptedKey = _pwriWrapIv(kek, cek, iv);
260
+ // PBKDF2-params as keyDerivationAlgorithm [0] IMPLICIT.
261
+ var kdfParams = _pbkdf2ParamsSeq(salt, iterations, prf);
262
+ var kdfAlg = b.contextConstructed(0, Buffer.concat([b.oid(O("pbkdf2")), kdfParams]));
263
+ var keyEncAlg = b.sequence([b.oid(O("id-alg-PWRI-KEK")), b.sequence([b.oid(O("aes256-CBC")), b.octetString(iv)])]);
264
+ return { tag: 3, node: b.sequence([b.integer(0n), kdfAlg, keyEncAlg, b.octetString(encryptedKey)]) };
265
+ }
266
+
267
+ // ---- kemri (ML-KEM ori) : RFC 9629 + 9936 ---------------------------------
268
+ var KEM_WRAP = {}; // ML-KEM OID -> wrap name (RFC 9936 sec. 2.2.1)
269
+ KEM_WRAP[O("id-ml-kem-512")] = "aes128-wrap";
270
+ KEM_WRAP[O("id-ml-kem-768")] = "aes256-wrap";
271
+ KEM_WRAP[O("id-ml-kem-1024")] = "aes256-wrap";
272
+ 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";
273
+
274
+ // CMSORIforKEMOtherInfo ::= SEQUENCE { wrap AlgorithmIdentifier, kekLength INTEGER,
275
+ // ukm [0] EXPLICIT OCTET STRING OPTIONAL } -- the RFC 9629 sec. 5 KDF info, one builder both sides.
276
+ function _kemOtherInfo(wrapName, kekBytes, ukm) {
277
+ var kids = [_algId(wrapName, "absent"), b.integer(BigInt(kekBytes))];
278
+ if (ukm) kids.push(b.explicit(0, b.octetString(ukm)));
279
+ return b.sequence(kids);
280
+ }
281
+ async function _buildKemri(cek, cert, opts) {
282
+ _assertKeyUsage(cert, "keyEncipherment", "kemri");
283
+ var keyOid = cert.subjectPublicKeyInfo.algorithm.oid;
284
+ var wcName = KEM_WC[keyOid];
285
+ if (!wcName) throw _err("cms/unsupported-algorithm", "unsupported KEM recipient key algorithm");
286
+ var wrapName = KEM_WRAP[keyOid];
287
+ var kekBytes = WRAP_KEK_LENGTHS[O(wrapName)];
288
+ var ukm = opts.ukm ? guard.bytes.view(opts.ukm, CmsError, "cms/bad-input", "ukm") : null;
289
+ var pub = await subtle.importKey("spki", cert.subjectPublicKeyInfo.bytes, { name: wcName }, false, ["encapsulateBits"]);
290
+ var kem = await subtle.encapsulateBits({ name: wcName }, pub);
291
+ var ss = Buffer.from(kem.sharedKey), kemct = Buffer.from(kem.ciphertext);
292
+ var ssKey = await subtle.importKey("raw", ss, { name: "HKDF" }, false, ["deriveBits"]);
293
+ var kek = Buffer.from(await subtle.deriveBits({ name: "HKDF", hash: "SHA-256", salt: Buffer.alloc(0), info: _kemOtherInfo(wrapName, kekBytes, ukm) }, ssKey, kekBytes * 8));
294
+ var encryptedKey = await _aesKwWrap(kek, cek);
295
+ var rid = _rid(cert, opts.keyIdentifier);
296
+ var kemriKids = [b.integer(0n), rid.node, _algId(oid.name(keyOid), "absent"), b.octetString(kemct), _algId("hkdfWithSha256", "absent"), b.integer(BigInt(kekBytes))];
297
+ if (ukm) kemriKids.push(b.explicit(0, b.octetString(ukm)));
298
+ kemriKids.push(_algId(wrapName, "absent"), b.octetString(encryptedKey));
299
+ var kemri = b.sequence(kemriKids);
300
+ return { tag: 4, node: b.sequence([b.oid(O("kem")), kemri]) };
301
+ }
302
+
303
+ // AES-KW wrap of the CEK under a raw KEK.
304
+ async function _aesKwWrap(kek, cek) {
305
+ var kekKey = await subtle.importKey("raw", kek, { name: "AES-KW" }, false, ["wrapKey"]);
306
+ var cekKey = await subtle.importKey("raw", cek, { name: "AES-CBC" }, true, ["encrypt", "decrypt"]);
307
+ return Buffer.from(await subtle.wrapKey("raw", cekKey, kekKey, { name: "AES-KW" }));
308
+ }
309
+
310
+ // RFC 3211 sec. 2.3.1 wrap formatting + double-CBC (sec. 2.3.2).
311
+ function _pwriFormat(cek) {
312
+ var count = cek.length;
313
+ // Coverage residual: for the AES CEK sizes the toolkit produces (16/24/32), `count` is always in
314
+ // range, `body` (4 + count) is never a 16-octet multiple, and `body + padLen` always reaches the
315
+ // 2-block minimum -- so the out-of-range throw and the two zero/underflow pad arms never fire.
316
+ if (count < 1 || count > 255) throw _err("cms/bad-input", "the CEK length is out of the RFC 3211 range");
317
+ var check = Buffer.from([count, cek[0] ^ 0xff, cek[1] ^ 0xff, cek[2] ^ 0xff]);
318
+ var body = Buffer.concat([check, cek]);
319
+ var blk = 16;
320
+ var padLen = body.length % blk === 0 ? 0 : blk - (body.length % blk);
321
+ if (body.length + padLen < 2 * blk) padLen += (2 * blk - (body.length + padLen));
322
+ return Buffer.concat([body, nodeCrypto.randomBytes(padLen)]);
323
+ }
324
+ function _pwriWrapIv(kek, cek, iv) {
325
+ var wk = _pwriFormat(cek);
326
+ var c1 = nodeCrypto.createCipheriv("aes-256-cbc", kek, iv); c1.setAutoPadding(false);
327
+ var pass1 = Buffer.concat([c1.update(wk), c1.final()]);
328
+ var iv2 = pass1.subarray(pass1.length - 16);
329
+ var c2 = nodeCrypto.createCipheriv("aes-256-cbc", kek, iv2); c2.setAutoPadding(false);
330
+ return Buffer.concat([c2.update(pass1), c2.final()]);
331
+ }
332
+
333
+ function _passwordBytes(p) {
334
+ if (Buffer.isBuffer(p)) return p;
335
+ if (p instanceof Uint8Array) return Buffer.from(p);
336
+ if (typeof p === "string") return Buffer.from(p, "utf8");
337
+ throw _err("cms/bad-input", "a password must be a string, Buffer, or Uint8Array");
338
+ }
339
+ var PRF_HASH = { hmacWithSHA1: "SHA-1", hmacWithSHA256: "SHA-256", hmacWithSHA384: "SHA-384", hmacWithSHA512: "SHA-512" };
340
+ function _prfHash(prf) { if (!PRF_HASH[prf]) throw _err("cms/bad-input", "unsupported pwri prf " + JSON.stringify(prf)); return PRF_HASH[prf]; }
341
+
342
+ // ---- recipient dispatch ----------------------------------------------------
343
+ async function _buildRecipient(cek, desc, opts) {
344
+ if (desc == null || typeof desc !== "object") throw _err("cms/bad-input", "each recipient must be a descriptor object");
345
+ if (desc.password != null) return _buildPwri(cek, desc);
346
+ if (desc.kek != null) return _buildKekri(cek, desc);
347
+ if (desc.cert != null) {
348
+ var cert = x509.parse(_normCertDer(desc.cert, "a recipient certificate"));
349
+ var keyOid = cert.subjectPublicKeyInfo.algorithm.oid;
350
+ if (keyOid === O("rsaEncryption") || keyOid === O("rsassaPss")) return _buildKtri(cek, cert, mergeOpts(opts, desc));
351
+ if (keyOid === O("ecPublicKey") || MONT_KA[keyOid]) return _buildKari(cek, cert, mergeOpts(opts, desc));
352
+ if (KEM_WC[keyOid]) return _buildKemri(cek, cert, mergeOpts(opts, desc));
353
+ throw _err("cms/unsupported-algorithm", "unsupported recipient certificate key algorithm " + keyOid);
354
+ }
355
+ throw _err("cms/bad-input", "a recipient needs { cert }, { password }, or { kek, kekId }");
356
+ }
357
+ function mergeOpts(opts, desc) {
358
+ return { oaepHash: desc.oaepHash || opts.oaepHash, keyIdentifier: desc.keyIdentifier || opts.keyIdentifier, ukm: desc.ukm != null ? desc.ukm : opts.ukm };
359
+ }
360
+ // RecipientInfo CHOICE: ktri untagged; kari [1], kekri [2], pwri [3], ori [4] -- all IMPLICIT,
361
+ // so the arm's SEQUENCE tag is replaced by the context tag.
362
+ function _taggedRecipient(r) {
363
+ if (r.tag == null) return r.node;
364
+ return b.contextConstructed(r.tag, r.node.subarray(_tlvHeaderLen(r.node)));
365
+ }
366
+
367
+ // EnvelopedData version (RFC 5652 sec. 6.1): any pwri/ori -> 3; else any ri v2 or a kari/kekri
368
+ // (v3/v4) -> 2; else 0. We never emit originatorInfo/unprotectedAttrs by default.
369
+ function _envelopedVersion(recips, hasUnprotected) {
370
+ var anyOri = recips.some(function (r) { return r.tag === 4 || r.tag === 3; }); // pwri(3)/ori(4)
371
+ if (anyOri) return 3;
372
+ var forcesTwo = hasUnprotected || recips.some(function (r) { return r.tag === 1 || r.tag === 2 || (r.tag == null && r._riVersion === 2); });
373
+ return forcesTwo ? 2 : 0;
374
+ }
375
+
376
+ async function encrypt(content, recipients, opts) {
377
+ opts = opts || {};
378
+ var contentBytes = guard.bytes.view(content, CmsError, "cms/bad-input", "content");
379
+ var algName = opts.contentEncryptionAlgorithm || "aes-256-gcm";
380
+ var ca = CONTENT_ALGS[algName];
381
+ if (!ca) throw _err("cms/bad-input", "unsupported contentEncryptionAlgorithm " + JSON.stringify(algName));
382
+ var contentType = opts.contentType || "data";
383
+ var cek = nodeCrypto.randomBytes(ca.keyBits / 8);
384
+
385
+ // EncryptedData: a single non-array { cek } or { password } descriptor, no RecipientInfos.
386
+ if (!Array.isArray(recipients)) return _encryptedData(contentBytes, recipients, ca, contentType, opts, cek);
387
+
388
+ if (!recipients.length) throw _err("cms/bad-input", "at least one recipient is required (RFC 5652 sec. 6.1)");
389
+ var recips = [];
390
+ for (var i = 0; i < recipients.length; i++) recips.push(await _buildRecipient(cek, recipients[i], opts));
391
+ var riNodes = recips.map(_taggedRecipient);
392
+
393
+ if (ca.aead) return _emit(_authEnvelopedData(contentBytes, cek, ca, contentType, opts, riNodes, recips), "authEnvelopedData", opts);
394
+ return _emit(_envelopedData(contentBytes, cek, ca, contentType, riNodes, recips), "envelopedData", opts);
395
+ }
396
+
397
+ function _emit(inner, ctName, opts) {
398
+ var ci = b.sequence([b.oid(O(ctName)), b.explicit(0, inner)]);
399
+ return opts.pem ? schemaCms.pemEncode(ci, "CMS") : ci;
400
+ }
401
+
402
+ function _envelopedData(contentBytes, cek, ca, contentType, riNodes, recips) {
403
+ var iv = nodeCrypto.randomBytes(16);
404
+ var enc = _cbcEncrypt(cek, iv, contentBytes, ca.keyBits);
405
+ var eci = b.sequence([b.oid(O(contentType)), b.sequence([b.oid(O(ca.oid)), b.octetString(iv)]), b.contextPrimitive(0, enc)]);
406
+ return b.sequence([b.integer(BigInt(_envelopedVersion(recips, false))), b.setOf(riNodes), eci]);
407
+ }
408
+
409
+ function _authEnvelopedData(contentBytes, cek, ca, contentType, opts, riNodes, recips) {
410
+ var nonce = nodeCrypto.randomBytes(12);
411
+ var authAttrsDer = null, aad = Buffer.alloc(0);
412
+ // RFC 5083 sec. 2.1: authAttrs MUST be present when the content type is not id-data. Emitting an
413
+ // AuthEnvelopedData without them for a non-data type produces a message our own strict parser rejects.
414
+ if (contentType !== "data" && !(opts.authAttrs && opts.authAttrs.length)) {
415
+ throw _err("cms/bad-input", "AuthEnvelopedData with a non-data contentType requires authAttrs (RFC 5083 sec. 2.1)");
416
+ }
417
+ if (opts.authAttrs && opts.authAttrs.length) {
418
+ // authAttrs are transmitted [1] IMPLICIT but MACed under the EXPLICIT SET OF tag (RFC 5083 sec. 2.2).
419
+ var setOf = b.setOf(opts.authAttrs);
420
+ aad = setOf; authAttrsDer = b.contextConstructed(1, setOf.subarray(_tlvHeaderLen(setOf)));
421
+ }
422
+ // A 16-octet (128-bit) GCM tag -- the strongest ICV and what OpenSSL emits, so the message
423
+ // interops across OpenSSL 3.5 / 4.x. The aes-ICVlen (16) is carried explicitly (RFC 5084 sec. 3.2
424
+ // omits it ONLY when it equals the DEFAULT 12); it MUST equal the mac octet length (M42).
425
+ var g = _gcmEncrypt(cek, nonce, contentBytes, aad, ca.keyBits, 16);
426
+ var eci = b.sequence([b.oid(O(contentType)), b.sequence([b.oid(O(ca.oid)), _gcmParams(nonce, 16)]), b.contextPrimitive(0, g.ct)]);
427
+ var kids = [b.integer(0n), b.setOf(riNodes), eci];
428
+ if (authAttrsDer) kids.push(authAttrsDer);
429
+ kids.push(b.octetString(g.tag));
430
+ void recips;
431
+ return b.sequence(kids);
432
+ }
433
+
434
+ function _encryptedData(contentBytes, desc, ca, contentType, opts, cek) {
435
+ if (ca.aead) throw _err("cms/bad-input", "EncryptedData supports only CBC content encryption");
436
+ var iv = nodeCrypto.randomBytes(16);
437
+ var contentAlgNode, encKey;
438
+ if (desc && desc.cek != null) {
439
+ encKey = guard.bytes.view(desc.cek, CmsError, "cms/bad-input", "cek");
440
+ if (encKey.length !== ca.keyBits / 8) throw _err("cms/bad-input", "the supplied cek length does not match " + opts.contentEncryptionAlgorithm);
441
+ contentAlgNode = b.sequence([b.oid(O(ca.oid)), b.octetString(iv)]);
442
+ } else if (desc && desc.password != null) {
443
+ return _encryptedDataPbes2(contentBytes, desc, ca, contentType, iv, opts);
444
+ } else {
445
+ throw _err("cms/bad-input", "EncryptedData needs a single { cek } or { password } descriptor");
446
+ }
447
+ var enc = _cbcEncrypt(encKey, iv, contentBytes, ca.keyBits);
448
+ var eci = b.sequence([b.oid(O(contentType)), contentAlgNode, b.contextPrimitive(0, enc)]);
449
+ var inner = b.sequence([b.integer(0n), eci]);
450
+ return _emit(inner, "encryptedData", opts);
451
+ }
452
+
453
+ function _encryptedDataPbes2(contentBytes, desc, ca, contentType, iv, opts) {
454
+ var password = _passwordBytes(desc.password);
455
+ var iterations = _assertIterations(desc.iterations == null ? 600000 : desc.iterations);
456
+ var salt = desc.salt ? _assertSalt(guard.bytes.view(desc.salt, CmsError, "cms/bad-input", "salt")) : nodeCrypto.randomBytes(16);
457
+ var prf = desc.prf || "hmacWithSHA256";
458
+ var key = nodeCrypto.pbkdf2Sync(password, salt, iterations, ca.keyBits / 8, _prfNode(prf));
459
+ var enc = _cbcEncrypt(key, iv, contentBytes, ca.keyBits);
460
+ var pbkdf2Params = _pbkdf2ParamsSeq(salt, iterations, prf);
461
+ var kdf = b.sequence([b.oid(O("pbkdf2")), pbkdf2Params]);
462
+ var encScheme = b.sequence([b.oid(O(ca.oid)), b.octetString(iv)]);
463
+ var pbes2Params = b.sequence([kdf, encScheme]);
464
+ var contentAlg = b.sequence([b.oid(O("pbes2")), pbes2Params]);
465
+ var eci = b.sequence([b.oid(O(contentType)), contentAlg, b.contextPrimitive(0, enc)]);
466
+ var inner = b.sequence([b.integer(0n), eci]);
467
+ return _emit(inner, "encryptedData", { pem: opts && opts.pem != null ? opts.pem : desc.pem });
468
+ }
469
+ var PRF_NODE = { hmacWithSHA1: "sha1", hmacWithSHA256: "sha256", hmacWithSHA384: "sha384", hmacWithSHA512: "sha512" };
470
+ function _prfNode(prf) { if (!PRF_NODE[prf]) throw _err("cms/bad-input", "unsupported prf " + JSON.stringify(prf)); return PRF_NODE[prf]; }
471
+ // PBKDF2-params { salt OCTET STRING, iterationCount, prf DEFAULT hmacWithSHA1 } -- one builder for
472
+ // pwri + PBES2. prf equal to the DEFAULT (hmacWithSHA1) MUST be omitted (X.690 / RFC 8018 App. A.2).
473
+ function _pbkdf2ParamsSeq(salt, iterations, prf) {
474
+ var kids = [b.octetString(salt), b.integer(BigInt(iterations))];
475
+ if (prf !== "hmacWithSHA1") kids.push(_algId(prf, "null"));
476
+ return b.sequence(kids);
477
+ }
478
+
479
+ // ---- content-encryption primitives ----------------------------------------
480
+ function _cbcEncrypt(key, iv, plaintext, keyBits) {
481
+ var c = nodeCrypto.createCipheriv("aes-" + keyBits + "-cbc", key, iv);
482
+ return Buffer.concat([c.update(plaintext), c.final()]);
483
+ }
484
+ function _gcmEncrypt(key, nonce, plaintext, aad, keyBits, tagLen) {
485
+ var c = nodeCrypto.createCipheriv("aes-" + keyBits + "-gcm", key, nonce, { authTagLength: tagLen });
486
+ if (aad && aad.length) c.setAAD(aad);
487
+ var ct = Buffer.concat([c.update(plaintext), c.final()]);
488
+ return { ct: ct, tag: c.getAuthTag() };
489
+ }
490
+
491
+ // The length of a DER TLV's tag+length header (so the SET OF re-tag drops the tag byte(s)).
492
+ function _tlvHeaderLen(der) {
493
+ var lenByte = der[1];
494
+ if (lenByte < 0x80) return 2;
495
+ return 2 + (lenByte & 0x7f);
496
+ }
497
+
498
+ module.exports = { encrypt: encrypt };