@blamejs/pki 0.2.22 → 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 };
package/lib/cms-verify.js CHANGED
@@ -35,6 +35,8 @@ var webcrypto = require("./webcrypto");
35
35
  var subtle = webcrypto.webcrypto.subtle;
36
36
  var edwardsPoint = require("./edwards-point");
37
37
  var cmsSign = require("./cms-sign");
38
+ var cmsEncrypt = require("./cms-encrypt");
39
+ var cmsDecrypt = require("./cms-decrypt");
38
40
  var signScheme = require("./sign-scheme");
39
41
  var MLDSA_SUITABLE_DIGEST = signScheme.MLDSA_SUITABLE_DIGEST; // shared sign/verify digest-strength policy (RFC 9882 sec. 3.3)
40
42
  var SLHDSA_BY_OID = signScheme.SLHDSA_BY_OID; // shared SLH-DSA set -> { wc, digest } (RFC 9814 sec. 4 pinned digest)
@@ -588,4 +590,64 @@ function _addCert(out, der) {
588
590
  */
589
591
  var sign = cmsSign.sign;
590
592
 
591
- module.exports = { verify: verify, sign: sign };
593
+ /**
594
+ * @primitive pki.cms.encrypt
595
+ * @signature pki.cms.encrypt(content, recipients, opts?) -> Promise<Buffer | string>
596
+ * @since 0.2.23
597
+ * @status experimental
598
+ * @spec RFC 5652, RFC 5083, RFC 5084, RFC 3560, RFC 5753, RFC 8418, RFC 9629, RFC 9936, RFC 3211, RFC 8018
599
+ * @related pki.cms.decrypt, pki.schema.cms.parse
600
+ *
601
+ * Encrypt content as a CMS EnvelopedData (CBC content), AuthEnvelopedData (AEAD content -- the
602
+ * default), or EncryptedData. `recipients` is an ARRAY of recipient descriptors for the enveloped
603
+ * family, each wrapping the SAME fresh content-encryption key: `{ cert }` auto-dispatches off the
604
+ * recipient certificate's public-key algorithm -- RSA yields a ktri with RSAES-OAEP-SHA256 (PKCS#1
605
+ * v1.5 is NEVER emitted); EC P-256/384/521 a kari with ephemeral-static ECDH and the X9.63 KDF;
606
+ * X25519/X448 a kari per RFC 8418 (HKDF); ML-KEM-512/768/1024 an ori/KEMRecipientInfo per RFC
607
+ * 9629 + 9936. `{ password }` yields a pwri (PBKDF2 + RFC 3211 PWRI-KEK); `{ kek, kekId }` a kekri
608
+ * (AES key wrap). For EncryptedData (no RecipientInfos), pass a single non-array `{ cek }` or
609
+ * `{ password }` descriptor. The default AES-256-GCM content encryption produces an
610
+ * authenticated AuthEnvelopedData; a CBC choice produces an unauthenticated EnvelopedData.
611
+ * Malformed input throws a typed `CmsError`.
612
+ *
613
+ * @opts contentEncryptionAlgorithm `"aes-256-gcm"` (default, AuthEnvelopedData) / `"aes-128-gcm"` / `"aes-256-cbc"` / `"aes-128-cbc"` (EnvelopedData).
614
+ * @opts contentType The encapsulated content type (an OID name). Default `data`.
615
+ * @opts oaepHash The RSAES-OAEP hash for ktri recipients: `"sha256"` (default) / `"sha384"` / `"sha512"`.
616
+ * @opts keyIdentifier The recipient identifier form: `"issuerAndSerial"` (default) or `"subjectKeyIdentifier"`.
617
+ * @opts ukm User keying material (a Buffer) for kari / kemri recipients.
618
+ * @opts authAttrs Authenticated attributes (SET OF Attribute) for AuthEnvelopedData.
619
+ * @opts pem Return a PEM string instead of a DER Buffer.
620
+ * @example
621
+ * var env = await pki.cms.encrypt(Buffer.from("secret"), [{ cert: recipientCertDer }]);
622
+ */
623
+ var encrypt = cmsEncrypt.encrypt;
624
+
625
+ /**
626
+ * @primitive pki.cms.decrypt
627
+ * @signature pki.cms.decrypt(input, keyMaterial, opts?) -> Promise<{ content, contentType, contentTypeName, recipientType, recipientIndex, contentEncryptionAlgorithm, authenticated }>
628
+ * @since 0.2.23
629
+ * @status experimental
630
+ * @spec RFC 5652, RFC 5083, RFC 5084, RFC 3560, RFC 5753, RFC 8418, RFC 9629, RFC 9936, RFC 3211, RFC 8018, RFC 3218
631
+ * @related pki.cms.encrypt, pki.schema.cms.parse
632
+ *
633
+ * Decrypt a CMS EnvelopedData / AuthEnvelopedData / EncryptedData (DER Buffer or PEM). It selects
634
+ * the recipient the key material targets, acquires the content-encryption key through the matching
635
+ * arm (ktri OAEP or PKCS#1 v1.5 decrypt-only; kari ECDH / X25519 / X448; kekri; pwri; ori/ML-KEM),
636
+ * and decrypts + authenticates the content. `keyMaterial` is `{ key, cert }` (the recipient
637
+ * private key + its certificate, which drives recipient matching), `{ password }`, `{ kek, kekId? }`,
638
+ * or `{ cek }` (EncryptedData raw-key mode). Fail-closed and oracle-free: every secret-dependent
639
+ * failure -- a bad key-wrap, a padding fault, a GCM tag mismatch, a PWRI check-byte mismatch --
640
+ * collapses to the SINGLE uniform `cms/decrypt-failed` verdict (Bleichenbacher / EFAIL oracle
641
+ * freedom), and the PKCS#1 v1.5 arm applies the RFC 3218 implicit-rejection countermeasure so its
642
+ * failure is indistinguishable. `authenticated` is true only for AuthEnvelopedData; a CBC
643
+ * EnvelopedData surfaces `authenticated: false` (the EFAIL caveat in the verdict itself).
644
+ *
645
+ * @opts recipientIndex Explicitly select the recipient by index (overrides key-material matching).
646
+ * @opts maxIterations Lower the PBKDF2 iteration cap (a DoS bound; downward only).
647
+ * @example
648
+ * var res = await pki.cms.decrypt(envDer, { key: recipientKeyPkcs8, cert: recipientCertDer });
649
+ * res.content; // the recovered plaintext Buffer
650
+ */
651
+ var decrypt = cmsDecrypt.decrypt;
652
+
653
+ module.exports = { verify: verify, sign: sign, encrypt: encrypt, decrypt: decrypt };
package/lib/constants.js CHANGED
@@ -227,6 +227,12 @@ var LIMITS = {
227
227
  // enforced BEFORE parsing so a hostile document is refused up front.
228
228
  JSON_MAX_BYTES: BYTES.mib(1),
229
229
  JSON_MAX_DEPTH: 32,
230
+ // PBKDF2 iteration ceiling on the DECRYPT side (RFC 8018 pwri / PBES2). An attacker-supplied
231
+ // iterationCount is attacker-controlled work; a message above this is refused before any
232
+ // derivation (typed cms/iteration-limit), and a caller may cap it lower via opts.maxIterations.
233
+ // 10M clears the authoring default (600k) with generous headroom for high-security policies.
234
+ PBKDF2_MAX_ITERATIONS: 10000000,
235
+ PBKDF2_MAX_SALT: 1024,
230
236
  // ACME challenge token entropy floor (RFC 8555 sec. 8, errata 6950): >= 128
231
237
  // bits of base64url is >= 22 characters. A shorter token is refused before use.
232
238
  ACME_TOKEN_MIN_CHARS: 22,
package/lib/oid.js CHANGED
@@ -147,7 +147,7 @@ var FAMILIES = {
147
147
 
148
148
  // PKCS#1 RSA public-key + RSASSA signature algorithms.
149
149
  rsa: { base: [1, 2, 840, 113549, 1, 1], of: {
150
- rsaEncryption: 1, rsaesOaep: 7, mgf1: 8, rsassaPss: 10,
150
+ rsaEncryption: 1, rsaesOaep: 7, mgf1: 8, pSpecified: 9, rsassaPss: 10,
151
151
  sha256WithRSAEncryption: 11,
152
152
  sha384WithRSAEncryption: 12, sha512WithRSAEncryption: 13 } },
153
153
 
@@ -215,9 +215,26 @@ var FAMILIES = {
215
215
  // CEK-HKDF content-encryption wrapper (RFC 9709) a KEMRecipientInfo names, plus
216
216
  // the RSA-KEM SPKI algorithm (RFC 9690). Parameters are absent for the HKDFs.
217
217
  smimeAlg: { base: [1, 2, 840, 113549, 1, 9, 16, 3], of: {
218
+ "id-alg-PWRI-KEK": 9,
218
219
  "id-rsa-kem": 14, "id-alg-hss-lms-hashsig": 17,
220
+ // RFC 8418 X25519/X448 key-agreement schemes (HKDF-based).
221
+ "dhSinglePass-stdDH-hkdf-sha256-scheme": 19, "dhSinglePass-stdDH-hkdf-sha384-scheme": 20,
222
+ "dhSinglePass-stdDH-hkdf-sha512-scheme": 21,
219
223
  hkdfWithSha256: 28, hkdfWithSha384: 29, hkdfWithSha512: 30, cekHkdfSha256: 31 } },
220
224
 
225
+ // RFC 5753 ephemeral-static ECDH key-agreement schemes -- the keyEncryptionAlgorithm OID of a
226
+ // kari, whose PARAMETER is the KeyWrapAlgorithm (so these are NOT params-absent). The X9.63 KDF
227
+ // variants: stdDH (SECG arc 1.3.132.1.11) + cofactorDH (1.3.132.1.14) for SHA-224/256/384/512,
228
+ // and the SHA-1 KDF pair on the ANSI-X9.63 arc (the OpenSSL default).
229
+ secgStdDH: { base: [1, 3, 132, 1, 11], of: {
230
+ "dhSinglePass-stdDH-sha224kdf-scheme": 0, "dhSinglePass-stdDH-sha256kdf-scheme": 1,
231
+ "dhSinglePass-stdDH-sha384kdf-scheme": 2, "dhSinglePass-stdDH-sha512kdf-scheme": 3 } },
232
+ secgCofactorDH: { base: [1, 3, 132, 1, 14], of: {
233
+ "dhSinglePass-cofactorDH-sha224kdf-scheme": 0, "dhSinglePass-cofactorDH-sha256kdf-scheme": 1,
234
+ "dhSinglePass-cofactorDH-sha384kdf-scheme": 2, "dhSinglePass-cofactorDH-sha512kdf-scheme": 3 } },
235
+ x963Schemes: { base: [1, 3, 133, 16, 840, 63, 0], of: {
236
+ "dhSinglePass-stdDH-sha1kdf-scheme": 2, "dhSinglePass-cofactorDH-sha1kdf-scheme": 3 } },
237
+
221
238
  // PKIX algorithms arc -- the stateful hash-based signature algorithm
222
239
  // identifiers (RFC 9802 sec. 4). HSS/LMS additionally has the SMIME
223
240
  // id-alg-hss-lms-hashsig OID above (RFC 9708 / RFC 9802 share it).
package/lib/schema-cms.js CHANGED
@@ -1203,4 +1203,13 @@ module.exports = {
1203
1203
  walkSignedData: walkSignedData,
1204
1204
  walkEncryptedData: walkEncryptedData,
1205
1205
  assertAttachedCiphertext: assertAttachedCiphertext,
1206
+ // The structure's own algorithm tables, exported (like the walk* helpers) as the single source
1207
+ // of truth the crypto layer (cms-encrypt / cms-decrypt) shares -- so the wrap<->KEK-length, the
1208
+ // ML-KEM ciphertext-length, and the AEAD-mode/ICV-length rules the parser enforces cannot drift
1209
+ // from what the producer/consumer emit and accept.
1210
+ WRAP_KEK_LENGTHS: WRAP_KEK_LENGTHS,
1211
+ KEM_CT_LENGTHS: KEM_CT_LENGTHS,
1212
+ AEAD_ALGS: AEAD_ALGS,
1213
+ AEAD_GCM_ICVLENS: AEAD_GCM_ICVLENS,
1214
+ AEAD_CCM_ICVLENS: AEAD_CCM_ICVLENS,
1206
1215
  };