@blamejs/pki 0.5.6 → 0.5.8
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.
- package/CHANGELOG.md +419 -378
- package/MIGRATING.md +65 -0
- package/README.md +12 -12
- package/lib/acme.js +31 -31
- package/lib/asn1-der.js +10 -10
- package/lib/attrcert-sign.js +24 -20
- package/lib/byte-reader.js +6 -6
- package/lib/byte-writer.js +5 -5
- package/lib/cbor-det.js +27 -24
- package/lib/cmc-build.js +104 -104
- package/lib/cmc-verify.js +108 -32
- package/lib/cmp-build.js +31 -26
- package/lib/cmp-session.js +74 -72
- package/lib/cmp-verify.js +72 -58
- package/lib/cms-compress.js +8 -9
- package/lib/cms-decrypt.js +92 -76
- package/lib/cms-encrypt.js +33 -34
- package/lib/cms-sign.js +100 -54
- package/lib/cms-verify.js +141 -82
- package/lib/composite-sig.js +13 -13
- package/lib/constants.js +4 -4
- package/lib/crl-sign.js +31 -25
- package/lib/crl-verify.js +7 -6
- package/lib/crmf-sign.js +19 -15
- package/lib/csr-sign.js +13 -9
- package/lib/ct.js +37 -37
- package/lib/edwards-point.js +7 -7
- package/lib/est.js +103 -59
- package/lib/framework-error.js +5 -5
- package/lib/guard-all.js +3 -3
- package/lib/guard-async.js +4 -4
- package/lib/guard-bytes.js +378 -15
- package/lib/guard-compress.js +17 -17
- package/lib/guard-crypto.js +1 -1
- package/lib/guard-encoding.js +15 -15
- package/lib/guard-header.js +3 -3
- package/lib/guard-identifier.js +16 -16
- package/lib/guard-json.js +15 -15
- package/lib/guard-limits.js +7 -7
- package/lib/guard-name.js +81 -16
- package/lib/guard-parsed.js +144 -75
- package/lib/guard-range.js +19 -19
- package/lib/guard-secret.js +11 -10
- package/lib/guard-text.js +6 -6
- package/lib/guard-time.js +10 -10
- package/lib/hpke.js +18 -17
- package/lib/http-digest.js +35 -35
- package/lib/http-retry-after.js +13 -13
- package/lib/http-transport.js +20 -19
- package/lib/inspect.js +53 -53
- package/lib/ip-utils.js +2 -2
- package/lib/jose.js +13 -13
- package/lib/key.js +16 -16
- package/lib/lint.js +51 -51
- package/lib/merkle.js +51 -36
- package/lib/mime.js +18 -18
- package/lib/ocsp-verify.js +10 -10
- package/lib/ocsp.js +32 -20
- package/lib/oid.js +29 -29
- package/lib/path-validate.js +114 -113
- package/lib/pbes2.js +16 -16
- package/lib/pkcs12-build.js +71 -56
- package/lib/pki-build.js +23 -22
- package/lib/rc2.js +1 -1
- package/lib/rfc3339.js +5 -5
- package/lib/schema-all.js +31 -31
- package/lib/schema-attrcert.js +12 -12
- package/lib/schema-c509.js +144 -142
- package/lib/schema-cmc.js +58 -58
- package/lib/schema-cmp.js +43 -43
- package/lib/schema-cms.js +169 -36
- package/lib/schema-crl.js +7 -7
- package/lib/schema-crmf.js +28 -28
- package/lib/schema-csr.js +12 -12
- package/lib/schema-csrattrs.js +16 -16
- package/lib/schema-engine.js +18 -18
- package/lib/schema-ocsp.js +15 -15
- package/lib/schema-pkcs12.js +20 -20
- package/lib/schema-pkcs8.js +2 -2
- package/lib/schema-pkix.js +131 -126
- package/lib/schema-smime.js +19 -19
- package/lib/schema-tsp.js +12 -12
- package/lib/schema-x509.js +3 -3
- package/lib/shbs.js +18 -18
- package/lib/sign-scheme.js +19 -16
- package/lib/sigstore.js +10 -11
- package/lib/sleep.js +1 -1
- package/lib/smime.js +308 -96
- package/lib/tls-cert-compress.js +18 -18
- package/lib/trust.js +27 -27
- package/lib/tsp-sign.js +22 -18
- package/lib/validator-all.js +1 -1
- package/lib/validator-attcert.js +1 -1
- package/lib/validator-cose.js +43 -44
- package/lib/validator-keydesc.js +3 -3
- package/lib/validator-sig.js +13 -13
- package/lib/validator-tls.js +11 -11
- package/lib/validator-tpm.js +21 -20
- package/lib/webauthn-mds.js +67 -67
- package/lib/webauthn.js +34 -34
- package/lib/webcrypto.js +15 -15
- package/lib/x509-sign.js +24 -15
- package/package.json +3 -2
- package/sbom.cdx.json +6 -6
package/lib/cms-encrypt.js
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
// material (RSA -> ktri OAEP; EC -> kari stdDH; X25519/X448 -> kari HKDF; ML-KEM -> ori/KEMRI;
|
|
14
14
|
// password -> pwri; symmetric KEK -> kekri) through the OID-keyed registry, never a hardcoded
|
|
15
15
|
// switch. AEAD (AES-GCM) content is the default and yields AuthEnvelopedData; CBC yields
|
|
16
|
-
// EnvelopedData. PKCS#1 v1.5 is
|
|
16
|
+
// EnvelopedData. PKCS#1 v1.5 is never emitted.
|
|
17
17
|
|
|
18
18
|
var nodeCrypto = require("crypto");
|
|
19
19
|
var asn1 = require("./asn1-der");
|
|
@@ -40,8 +40,7 @@ function _algId(name, shape) { return shape === "null" ? b.sequence([b.oid(O(nam
|
|
|
40
40
|
// A certificate descriptor -> raw DER (the recipient cert is parsed for dispatch + rid; the caller
|
|
41
41
|
// supplies bytes, not a re-encoded parse).
|
|
42
42
|
function _normCertDer(cert, what) {
|
|
43
|
-
if (Buffer.isBuffer(cert)) return cert;
|
|
44
|
-
if (cert instanceof Uint8Array) return Buffer.from(cert);
|
|
43
|
+
if (Buffer.isBuffer(cert) || cert instanceof Uint8Array) return guard.bytes.snapshot(cert, CmsError, "cms/bad-input", what || "a certificate");
|
|
45
44
|
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); } }
|
|
46
45
|
throw _err("cms/bad-input", (what || "a certificate") + " must be a DER Buffer, Uint8Array, or PEM string");
|
|
47
46
|
}
|
|
@@ -62,13 +61,13 @@ function _wrapOidForKek(keyBytes) {
|
|
|
62
61
|
if (keyBytes === 16) return "aes128-wrap";
|
|
63
62
|
if (keyBytes === 24) return "aes192-wrap";
|
|
64
63
|
if (keyBytes === 32) return "aes256-wrap";
|
|
65
|
-
// Coverage residual: unreachable via the API
|
|
64
|
+
// Coverage residual: unreachable via the API, since a KEK/CEK is always an AES key size (16/24/32);
|
|
66
65
|
// a defensive throw for a future caller that hands an off-size key.
|
|
67
66
|
throw _err("cms/bad-input", "no AES key-wrap algorithm for a " + keyBytes + "-octet key-encryption key");
|
|
68
67
|
}
|
|
69
68
|
|
|
70
|
-
// GCMParameters ::= SEQUENCE { aes-nonce OCTET STRING, aes-ICVlen INTEGER DEFAULT 12 }
|
|
71
|
-
// DEFAULT 12 is
|
|
69
|
+
// GCMParameters ::= SEQUENCE { aes-nonce OCTET STRING, aes-ICVlen INTEGER DEFAULT 12 }, where the
|
|
70
|
+
// DEFAULT 12 is omitted on emit (RFC 5084 sec. 3.2 / canonical DER).
|
|
72
71
|
function _gcmParams(nonce, icvLen) {
|
|
73
72
|
var kids = [b.octetString(nonce)];
|
|
74
73
|
if (icvLen !== 12) kids.push(b.integer(BigInt(icvLen)));
|
|
@@ -76,7 +75,7 @@ function _gcmParams(nonce, icvLen) {
|
|
|
76
75
|
}
|
|
77
76
|
|
|
78
77
|
// The keyIdentifier option selects the RecipientIdentifier form. "issuerAndSerial" is the documented
|
|
79
|
-
// default (its RFC name issuerAndSerialNumber is accepted too); reject anything else
|
|
78
|
+
// default (its RFC name issuerAndSerialNumber is accepted too); reject anything else instead of
|
|
80
79
|
// silently emitting issuerAndSerialNumber, so a typo surfaces instead of a rid the caller never asked for.
|
|
81
80
|
function _assertKeyIdentifier(form) {
|
|
82
81
|
if (form != null && form !== "issuerAndSerial" && form !== "issuerAndSerialNumber" && form !== "subjectKeyIdentifier") {
|
|
@@ -98,7 +97,7 @@ function _rid(cert, form) {
|
|
|
98
97
|
// unsupported-curve / low-order / unsupported-KEM-cert throws further below): these validate the
|
|
99
98
|
// CALLER's own recipient certificate at config time (tier-1 THROW). They fire only when a caller
|
|
100
99
|
// supplies a certificate whose SKI/keyUsage extension is malformed, or whose key is an unsupported
|
|
101
|
-
// curve
|
|
100
|
+
// curve, a low-order Montgomery point, or an unsupported KEM: inputs the toolkit never produces.
|
|
102
101
|
function _skiOf(cert) {
|
|
103
102
|
var exts = cert.extensions || [];
|
|
104
103
|
for (var i = 0; i < exts.length; i++) if (exts[i].name === "subjectKeyIdentifier" && exts[i].value != null) {
|
|
@@ -121,7 +120,7 @@ var OAEP_HASH = { sha256: "SHA-256", sha384: "SHA-384", sha512: "SHA-512" };
|
|
|
121
120
|
function _oaepParams(hashName) {
|
|
122
121
|
var hAlg = _algId(hashName, "null");
|
|
123
122
|
var mgf = b.sequence([b.oid(O("mgf1")), hAlg]);
|
|
124
|
-
// pSourceAlgorithm [2] DEFAULT pSpecifiedEmpty
|
|
123
|
+
// pSourceAlgorithm [2] DEFAULT pSpecifiedEmpty: the empty-label default MUST be omitted (X.690).
|
|
125
124
|
return b.sequence([b.explicit(0, hAlg), b.explicit(1, mgf)]);
|
|
126
125
|
}
|
|
127
126
|
async function _buildKtri(cek, cert, opts) {
|
|
@@ -145,8 +144,8 @@ MONT_KA[O("X25519")] = { name: "X25519", hkdf: "SHA-256", scheme: "dhSinglePass-
|
|
|
145
144
|
MONT_KA[O("X448")] = { name: "X448", hkdf: "SHA-512", scheme: "dhSinglePass-stdDH-hkdf-sha512-scheme" };
|
|
146
145
|
|
|
147
146
|
// ECC-CMS-SharedInfo ::= SEQUENCE { keyInfo AlgorithmIdentifier (the wrap, params ABSENT),
|
|
148
|
-
// entityUInfo [0] EXPLICIT OCTET STRING OPTIONAL, suppPubInfo [2] EXPLICIT OCTET STRING }
|
|
149
|
-
// suppPubInfo
|
|
147
|
+
// entityUInfo [0] EXPLICIT OCTET STRING OPTIONAL, suppPubInfo [2] EXPLICIT OCTET STRING }, where
|
|
148
|
+
// suppPubInfo is the KEK length in bits, 4-octet big-endian (RFC 5753 sec. 7.2). One builder,
|
|
150
149
|
// shared by encrypt + decrypt so the two sides cannot diverge.
|
|
151
150
|
function _eccSharedInfo(wrapName, ukm, kekBytes) {
|
|
152
151
|
var kids = [_algId(wrapName, "absent")];
|
|
@@ -185,12 +184,12 @@ async function _buildKari(cek, cert, opts) {
|
|
|
185
184
|
mz = Buffer.from(await subtle.deriveBits({ name: mka.name, public: rPub }, meph.privateKey, null));
|
|
186
185
|
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
186
|
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
|
|
189
|
-
// ECC-CMS-SharedInfo entityUInfo (the HKDF info)
|
|
187
|
+
// RFC 8418 sec. 2.2: when a ukm is present it is used as both the HKDF salt and the
|
|
188
|
+
// ECC-CMS-SharedInfo entityUInfo (the HKDF info). Omitting it from the info diverges the KEK
|
|
190
189
|
// from any conformant peer that reads the transmitted ukm.
|
|
191
190
|
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
191
|
} else {
|
|
193
|
-
// Coverage residual: unreachable
|
|
192
|
+
// Coverage residual: unreachable, since _buildRecipient routes only ecPublicKey / X25519 / X448
|
|
194
193
|
// keys into _buildKari; a defensive throw against a future dispatch change.
|
|
195
194
|
throw _err("cms/unsupported-algorithm", "unsupported recipient key algorithm for kari");
|
|
196
195
|
}
|
|
@@ -200,7 +199,7 @@ async function _buildKari(cek, cert, opts) {
|
|
|
200
199
|
var origPubBits = origSpki.children[1]; // BIT STRING node
|
|
201
200
|
var originatorKey = b.contextConstructed(1, Buffer.concat([origSpki.children[0].bytes, origPubBits.bytes]));
|
|
202
201
|
// KeyAgreeRecipientIdentifier CHOICE { issuerAndSerialNumber, rKeyId [0] IMPLICIT
|
|
203
|
-
// RecipientKeyIdentifier }
|
|
202
|
+
// RecipientKeyIdentifier }, where the SKI form wraps a SEQUENCE (rKeyId), unlike ktri's bare
|
|
204
203
|
// subjectKeyIdentifier [0] IMPLICIT OCTET STRING.
|
|
205
204
|
var ridNode;
|
|
206
205
|
_assertKeyIdentifier(opts.keyIdentifier);
|
|
@@ -235,13 +234,13 @@ async function _buildKekri(cek, desc) {
|
|
|
235
234
|
return { tag: 2, node: b.sequence([b.integer(4n), kekid, _algId(wrapName, "absent"), b.octetString(encryptedKey)]) };
|
|
236
235
|
}
|
|
237
236
|
|
|
238
|
-
// A PBKDF2 iterationCount MUST be a positive integer within the same cap the decryptor enforces
|
|
237
|
+
// A PBKDF2 iterationCount MUST be a positive integer within the same cap the decryptor enforces, so a
|
|
239
238
|
// ---- pwri (password) : PBKDF2 + RFC 3211 double-CBC PWRI-KEK ---------------
|
|
240
239
|
async function _buildPwri(cek, desc) {
|
|
241
|
-
// A string / Uint8Array password is encoded into a buffer
|
|
242
|
-
// copy
|
|
240
|
+
// A string / Uint8Array password is encoded into a buffer this toolkit allocated, a credential
|
|
241
|
+
// copy, so it is cleared once the derivation has consumed it. A caller-supplied Buffer is
|
|
243
242
|
// borrowed and left intact.
|
|
244
|
-
// The option validation runs
|
|
243
|
+
// The option validation runs before the password is encoded: a rejected iteration count or salt
|
|
245
244
|
// would otherwise abandon an owned credential copy on the way out.
|
|
246
245
|
var iterations = pbes2.assertIterations(desc.iterations == null ? 600000 : desc.iterations, _err, "cms");
|
|
247
246
|
var salt = desc.salt ? pbes2.assertSalt(guard.bytes.view(desc.salt, CmsError, "cms/bad-input", "salt"), _err, "cms") : nodeCrypto.randomBytes(16);
|
|
@@ -251,9 +250,9 @@ async function _buildPwri(cek, desc) {
|
|
|
251
250
|
var pwOwn = pbes2.passwordBytesOwned(desc.password, _err, "cms");
|
|
252
251
|
var password = pwOwn.bytes;
|
|
253
252
|
var kekKey = await subtle.importKey("raw", password, { name: "PBKDF2" }, false, ["deriveBits"]);
|
|
254
|
-
// The derived KEK is this function's allocation and is cleared below. `password` is
|
|
253
|
+
// The derived KEK is this function's allocation and is cleared below. `password` is not: when the
|
|
255
254
|
// caller passes a Buffer, passwordBytes hands back that very buffer, so wiping it would destroy
|
|
256
|
-
// the caller's own memory
|
|
255
|
+
// the caller's own memory, the one failure this rule must never cause.
|
|
257
256
|
var kek = Buffer.from(await subtle.deriveBits({ name: "PBKDF2", hash: _prfHash(prf), salt: salt, iterations: iterations }, kekKey, innerKeyBytes * 8));
|
|
258
257
|
if (pwOwn.owned) guard.secret.zeroize(password, CmsError, "cms/bad-input", "the password encoding");
|
|
259
258
|
try {
|
|
@@ -279,7 +278,7 @@ KEM_WRAP[O("id-ml-kem-1024")] = "aes256-wrap";
|
|
|
279
278
|
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";
|
|
280
279
|
|
|
281
280
|
// CMSORIforKEMOtherInfo ::= SEQUENCE { wrap AlgorithmIdentifier, kekLength INTEGER,
|
|
282
|
-
// ukm [0] EXPLICIT OCTET STRING OPTIONAL }
|
|
281
|
+
// ukm [0] EXPLICIT OCTET STRING OPTIONAL }, the RFC 9629 sec. 5 KDF info, one builder both sides.
|
|
283
282
|
function _kemOtherInfo(wrapName, kekBytes, ukm) {
|
|
284
283
|
var kids = [_algId(wrapName, "absent"), b.integer(BigInt(kekBytes))];
|
|
285
284
|
if (ukm) kids.push(b.explicit(0, b.octetString(ukm)));
|
|
@@ -310,7 +309,7 @@ async function _buildKemri(cek, cert, opts) {
|
|
|
310
309
|
return { tag: 4, node: b.sequence([b.oid(O("kem")), kemri]) };
|
|
311
310
|
} finally {
|
|
312
311
|
// RFC 9629 sec. 7 asks the SENDER to discard the shared secret and KEK once the recipient entry
|
|
313
|
-
// is built
|
|
312
|
+
// is built, and to use a fresh secret per recipient, so this runs per call and not once at
|
|
314
313
|
// the end of a multi-recipient message. In a `finally`, so a wrap or encoding failure does not
|
|
315
314
|
// leave them behind. The CEK is the caller's and is wiped by no one here; kemct is public.
|
|
316
315
|
// kem.sharedKey is the ArrayBuffer the engine returned and ss is this function's copy of it;
|
|
@@ -341,7 +340,7 @@ function _pwriFormat(cek) {
|
|
|
341
340
|
var padLen = body.length % blk === 0 ? 0 : blk - (body.length % blk);
|
|
342
341
|
if (body.length + padLen < 2 * blk) padLen += (2 * blk - (body.length + padLen));
|
|
343
342
|
// and each hold a plaintext copy of the CEK; only the padded result is returned, so
|
|
344
|
-
// the intermediates are cleared here
|
|
343
|
+
// the intermediates are cleared here and never abandoned.
|
|
345
344
|
var wrapped = Buffer.concat([body, nodeCrypto.randomBytes(padLen)]);
|
|
346
345
|
guard.secret.zeroizeAll([check, body], CmsError, "cms/bad-input", "a PWRI formatting intermediate");
|
|
347
346
|
return wrapped;
|
|
@@ -382,7 +381,7 @@ async function _buildRecipient(cek, desc, opts) {
|
|
|
382
381
|
function mergeOpts(opts, desc) {
|
|
383
382
|
return { oaepHash: desc.oaepHash || opts.oaepHash, keyIdentifier: desc.keyIdentifier || opts.keyIdentifier, ukm: desc.ukm != null ? desc.ukm : opts.ukm };
|
|
384
383
|
}
|
|
385
|
-
// RecipientInfo CHOICE: ktri untagged; kari [1], kekri [2], pwri [3], ori [4]
|
|
384
|
+
// RecipientInfo CHOICE: ktri untagged; kari [1], kekri [2], pwri [3], ori [4], all IMPLICIT,
|
|
386
385
|
// so the arm's SEQUENCE tag is replaced by the context tag.
|
|
387
386
|
function _taggedRecipient(r) {
|
|
388
387
|
if (r.tag == null) return r.node;
|
|
@@ -407,8 +406,8 @@ async function encrypt(content, recipients, opts) {
|
|
|
407
406
|
var contentType = opts.contentType || "data";
|
|
408
407
|
var cek = nodeCrypto.randomBytes(ca.keyBits / 8);
|
|
409
408
|
|
|
410
|
-
// The CEK protects the content for
|
|
411
|
-
// cleared
|
|
409
|
+
// The CEK protects the content for every recipient, so unlike a per-recipient shared secret it is
|
|
410
|
+
// cleared once, after the last recipient entry is built and the content is encrypted. Wiping it
|
|
412
411
|
// inside the recipient loop would destroy the key the remaining recipients must be given.
|
|
413
412
|
try {
|
|
414
413
|
// EncryptedData: a single non-array { cek } or { password } descriptor, no RecipientInfos.
|
|
@@ -451,9 +450,9 @@ function _authEnvelopedData(contentBytes, cek, ca, contentType, opts, riNodes, r
|
|
|
451
450
|
var setOf = b.setOf(opts.authAttrs);
|
|
452
451
|
aad = setOf; authAttrsDer = b.contextConstructed(1, setOf.subarray(_tlvHeaderLen(setOf)));
|
|
453
452
|
}
|
|
454
|
-
// A 16-octet (128-bit) GCM tag
|
|
453
|
+
// A 16-octet (128-bit) GCM tag, the strongest ICV and what OpenSSL emits, so the message
|
|
455
454
|
// interops across OpenSSL 3.5 / 4.x. The aes-ICVlen (16) is carried explicitly (RFC 5084 sec. 3.2
|
|
456
|
-
// omits it
|
|
455
|
+
// omits it only when it equals the DEFAULT 12); it MUST equal the mac octet length (M42).
|
|
457
456
|
var g = _gcmEncrypt(cek, nonce, contentBytes, aad, ca.keyBits, 16);
|
|
458
457
|
var eci = b.sequence([b.oid(O(contentType)), b.sequence([b.oid(O(ca.oid)), _gcmParams(nonce, 16)]), b.contextPrimitive(0, g.ct)]);
|
|
459
458
|
var kids = [b.integer(0n), b.setOf(riNodes), eci];
|
|
@@ -483,8 +482,8 @@ function _encryptedData(contentBytes, desc, ca, contentType, opts, cek) {
|
|
|
483
482
|
}
|
|
484
483
|
|
|
485
484
|
function _encryptedDataPbes2(contentBytes, desc, ca, contentType, iv, opts) {
|
|
486
|
-
// A string / Uint8Array password is encoded into a buffer
|
|
487
|
-
// copy
|
|
485
|
+
// A string / Uint8Array password is encoded into a buffer this toolkit allocated, a credential
|
|
486
|
+
// copy, so it is cleared once the derivation has consumed it. A caller-supplied Buffer is
|
|
488
487
|
// borrowed and left intact.
|
|
489
488
|
var pwOwn2 = pbes2.passwordBytesOwned(desc.password, _err, "cms");
|
|
490
489
|
var password = pwOwn2.bytes;
|
|
@@ -555,7 +554,7 @@ async function authenticate(content, recipients, opts) {
|
|
|
555
554
|
if (contentType !== "data" && !withAttrs) throw _err("cms/bad-input", "AuthenticatedData with a non-data contentType requires authenticated attributes (RFC 5652 sec. 9.1)");
|
|
556
555
|
|
|
557
556
|
// The MAC key is this path's content-encryption key: every recipient is given it, so it is cleared
|
|
558
|
-
//
|
|
557
|
+
// once at the end and not per recipient, and only after the MAC has been computed over it.
|
|
559
558
|
var macKey = nodeCrypto.randomBytes(MAC_KEY_OCTETS);
|
|
560
559
|
try {
|
|
561
560
|
var recips = [];
|
|
@@ -575,8 +574,8 @@ async function authenticate(content, recipients, opts) {
|
|
|
575
574
|
];
|
|
576
575
|
if (opts.authAttrs && opts.authAttrs.length) pairs = pairs.concat(opts.authAttrs);
|
|
577
576
|
// Every authAttr (auto-built or caller-supplied) MUST be a well-formed Attribute SEQUENCE
|
|
578
|
-
// { type OBJECT IDENTIFIER, values SET OF } and each type appears at most once (RFC 5652)
|
|
579
|
-
// malformed or duplicate caller attribute is rejected
|
|
577
|
+
// { type OBJECT IDENTIFIER, values SET OF } and each type appears at most once (RFC 5652), so a
|
|
578
|
+
// malformed or duplicate caller attribute is rejected before it is MACed and emitted, never left to
|
|
580
579
|
// fail an operator's parser downstream.
|
|
581
580
|
var seenTypes = {};
|
|
582
581
|
pairs.forEach(function (p) {
|
package/lib/cms-sign.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Copyright (c) blamejs contributors
|
|
3
3
|
"use strict";
|
|
4
4
|
//
|
|
5
|
-
// @internal -- the pki.cms.sign implementation. So the pki.cms namespace has
|
|
5
|
+
// @internal -- the pki.cms.sign implementation. So the pki.cms namespace has one @module home,
|
|
6
6
|
// the operator-facing @module pki.cms + the @primitive pki.cms.sign documentation block live in
|
|
7
7
|
// cms-verify.js, which re-exports this sign function.
|
|
8
8
|
//
|
|
@@ -33,10 +33,27 @@ function _err(code, message, cause) { return new CmsError(code, message, cause);
|
|
|
33
33
|
// cms/<kind>), so its faults keep the cms/* codes.
|
|
34
34
|
function _signE(kind, message, cause) { return new CmsError("cms/" + kind, message, cause); }
|
|
35
35
|
function O(name) { return oid.byName(name); }
|
|
36
|
+
|
|
37
|
+
// Every option each verb READS, and nothing else. Derived from the signing paths themselves rather
|
|
38
|
+
// than from what callers happen to pass: an option this list omits is one the verb would ignore in
|
|
39
|
+
// silence, and the ones that matter most are the ones a caller would most expect to work.
|
|
40
|
+
// A misspelled option name is the shape this closes: it does not select the mode the caller meant,
|
|
41
|
+
// and without the gate the message is signed the other way with nothing said about it.
|
|
42
|
+
var KNOWN_SIGN_OPTS = {
|
|
43
|
+
signedAttributes: 1, signingTime: 1, additionalSignedAttributes: 1, unsignedAttributes: 1,
|
|
44
|
+
sid: 1, eContentType: 1, detached: 1, certificates: 1, pem: 1,
|
|
45
|
+
};
|
|
46
|
+
// Countersign has its own set: it takes signerIndex and countersignatureOf to SELECT which
|
|
47
|
+
// signature it attaches to, and has no content of its own, so eContentType, detached and
|
|
48
|
+
// unsignedAttributes are not among the options it reads.
|
|
49
|
+
var KNOWN_COUNTERSIGN_OPTS = {
|
|
50
|
+
signerIndex: 1, countersignatureOf: 1, signingTime: 1, certificates: 1, pem: 1,
|
|
51
|
+
signedAttributes: 1, additionalSignedAttributes: 1, sid: 1,
|
|
52
|
+
};
|
|
36
53
|
// The shared producing-side helpers, bound to this domain's error class. Only the
|
|
37
|
-
// post-sign self-check is used here
|
|
38
|
-
// toolkit runs
|
|
39
|
-
//
|
|
54
|
+
// post-sign self-check is used here (the same one every other signer in the
|
|
55
|
+
// toolkit runs), so a key-only signer's declared public key is bound by evidence
|
|
56
|
+
// and never by the caller's word.
|
|
40
57
|
var NS = pkix.makeNS("cms", CmsError, oid);
|
|
41
58
|
var _b = pkiBuild.makeBuilder({
|
|
42
59
|
ErrorClass: CmsError, prefix: "cms", O: O, NS: NS,
|
|
@@ -85,10 +102,10 @@ function _buildSid(cert, useSki) {
|
|
|
85
102
|
// Assemble a SignedAttributes SET from resolved attribute pairs (each { type: <dotted OID>, values:
|
|
86
103
|
// [<build node or DER Buffer>] }), returning { setOf, wire }: `setOf` is the canonical DER SET OF
|
|
87
104
|
// (tag 0x31, build.set SET-OF-sorts) the signature covers (RFC 5652 sec. 5.4); `wire` is the same
|
|
88
|
-
// bytes with the on-wire [0] IMPLICIT tag (0xA0). Each attribute type appears
|
|
89
|
-
// 5.3)
|
|
105
|
+
// bytes with the on-wire [0] IMPLICIT tag (0xA0). Each attribute type appears at most once (sec.
|
|
106
|
+
// 5.3), so a duplicate throws. Shared by the top-level signer (content-type + message-digest +
|
|
90
107
|
// signing-time + caller attrs) and the countersignature builder (message-digest over the target
|
|
91
|
-
// signature octets + signing-time, content-type
|
|
108
|
+
// signature octets + signing-time, content-type omitted per sec. 11.4).
|
|
92
109
|
function _buildSignedAttrs(pairs) {
|
|
93
110
|
var seenTypes = {};
|
|
94
111
|
var attrs = pairs.map(function (p) {
|
|
@@ -112,16 +129,16 @@ function _resolveAttrPairs(list, what) {
|
|
|
112
129
|
});
|
|
113
130
|
}
|
|
114
131
|
|
|
115
|
-
// content-type / message-digest / signing-time are content-binding
|
|
116
|
-
// appear as unsigned attributes (RFC 5652 sec. 11.1/11.2/11.3
|
|
132
|
+
// content-type / message-digest / signing-time are content-binding signed attributes and MUST NOT
|
|
133
|
+
// appear as unsigned attributes (RFC 5652 sec. 11.1/11.2/11.3, the parser's ATTR_FORBIDDEN_IN).
|
|
117
134
|
var UNSIGNED_FORBIDDEN = {};
|
|
118
135
|
UNSIGNED_FORBIDDEN[O("contentType")] = "content-type";
|
|
119
136
|
UNSIGNED_FORBIDDEN[O("messageDigest")] = "message-digest";
|
|
120
137
|
UNSIGNED_FORBIDDEN[O("signingTime")] = "signing-time";
|
|
121
138
|
|
|
122
139
|
// Build the [1] IMPLICIT unsignedAttrs SET OF (on-wire tag 0xA1) from opts.unsignedAttributes
|
|
123
|
-
// (each { type, values: [DER] }
|
|
124
|
-
// absent/empty. Unsigned attributes
|
|
140
|
+
// (each { type, values: [DER] }, such as a countersignature or an RFC 3161 timestamp token), or null when
|
|
141
|
+
// absent/empty. Unsigned attributes sit outside the signature; a placement-forbidden type or a
|
|
125
142
|
// duplicate type (RFC 5652 sec. 11 / sec. 5.3) is a config-time cms/bad-input.
|
|
126
143
|
function _buildUnsignedAttrs(list) {
|
|
127
144
|
if (list == null) return null;
|
|
@@ -144,9 +161,9 @@ function _buildUnsignedAttrs(list) {
|
|
|
144
161
|
// SignedData digestAlgorithms + certificates sets).
|
|
145
162
|
function _buildSignerInfo(signer, content, eContentType, opts) {
|
|
146
163
|
var so = signer || {};
|
|
147
|
-
// A
|
|
164
|
+
// A key-only signer: `{ key, spki, keyIdentifier }` with no certificate. RFC
|
|
148
165
|
// 5272 sec. 3.2 requires exactly this when a Full PKI Request is signed with the
|
|
149
|
-
// key of a certification request it carries
|
|
166
|
+
// key of a certification request it carries: there is no certificate yet, the
|
|
150
167
|
// sid MUST be the subjectKeyIdentifier form, and its value MUST be the SKI the
|
|
151
168
|
// request itself declares. The scheme resolver only ever reads
|
|
152
169
|
// `cert.subjectPublicKeyInfo.algorithm`, so the request's own parsed SPKI stands
|
|
@@ -217,9 +234,9 @@ function _buildSignerInfo(signer, content, eContentType, opts) {
|
|
|
217
234
|
// comparing it works only for key material this process can export, and the keys
|
|
218
235
|
// this signer serves are often exactly the ones that cannot be: a non-extractable
|
|
219
236
|
// CryptoKey, an HSM handle, or a composite `{ mldsa, trad }` pair that is two keys
|
|
220
|
-
//
|
|
237
|
+
// and not one. Every one of those can still be checked the direct way: the
|
|
221
238
|
// signature it just produced either verifies under the declared public key or it
|
|
222
|
-
// does not
|
|
239
|
+
// does not, so there is no key kind this has to take on trust.
|
|
223
240
|
//
|
|
224
241
|
// This is the same post-sign self-check every other signer in the toolkit runs,
|
|
225
242
|
// through the same shared helper. It costs one verification per SignerInfo, which
|
|
@@ -227,8 +244,8 @@ function _buildSignerInfo(signer, content, eContentType, opts) {
|
|
|
227
244
|
function _assertKeyMatchesSpki(keyOnly, soKey, soSpki, scheme, sig, signedBytes, cert) {
|
|
228
245
|
var declared = keyOnly ? soSpki : (cert && cert.subjectPublicKeyInfo && cert.subjectPublicKeyInfo.bytes);
|
|
229
246
|
// A parsed certificate that does not retain its SPKI bytes cannot be checked this
|
|
230
|
-
// way. That is not a shape x509.parse produces
|
|
231
|
-
// the answer
|
|
247
|
+
// way. That is not a shape x509.parse produces, and if it ever were, refusing is
|
|
248
|
+
// the answer, never skipping, since a skip is the fail-open this prevents.
|
|
232
249
|
if (!declared) {
|
|
233
250
|
throw _signE("bad-input",
|
|
234
251
|
"a signer certificate did not surface its subjectPublicKeyInfo, so the signature it produced could " +
|
|
@@ -253,21 +270,21 @@ function _timeValue(when) {
|
|
|
253
270
|
|
|
254
271
|
// Normalize a signer certificate input to its raw DER (DER Buffer / PEM string / Uint8Array).
|
|
255
272
|
// The same bytes drive scheme resolution and the certificates [0] embedding, so a parsed
|
|
256
|
-
// certificate (which does not retain its full DER) is rejected
|
|
273
|
+
// certificate (which does not retain its full DER) is rejected; pass DER or PEM.
|
|
257
274
|
// The stand-in a key-only signer resolves its signature scheme from: the parsed
|
|
258
275
|
// SubjectPublicKeyInfo of the key that will sign. Only `.subjectPublicKeyInfo` is
|
|
259
|
-
// read downstream, so this deliberately carries nothing else
|
|
276
|
+
// read downstream, so this deliberately carries nothing else; a fuller fake
|
|
260
277
|
// would invite code to start trusting fields no certificate actually backs.
|
|
261
278
|
function _keyOnlyCertStandIn(spkiDer) {
|
|
262
279
|
var alg;
|
|
263
280
|
try {
|
|
264
281
|
// SubjectPublicKeyInfo ::= SEQUENCE { algorithm AlgorithmIdentifier, subjectPublicKey BIT STRING }
|
|
265
|
-
// The
|
|
266
|
-
//
|
|
267
|
-
// this is the
|
|
282
|
+
// The whole structure, not just the field this function goes on to read. For an
|
|
283
|
+
// opaque key handle the derivation check downstream is deliberately skipped, so
|
|
284
|
+
// this is the only thing standing between a caller's bytes and a SignerInfo that
|
|
268
285
|
// declares them: a SEQUENCE carrying an algorithm and nothing else, or a third
|
|
269
286
|
// field, would be emitted as the signer's public key and resolve to nothing for
|
|
270
|
-
// anyone trying to verify. The key
|
|
287
|
+
// anyone trying to verify. The key value is not interpreted, only that there
|
|
271
288
|
// is one, in the field the structure reserves for it.
|
|
272
289
|
var node = asn1.decode(spkiDer);
|
|
273
290
|
if (node.tagClass !== "universal" || node.tagNumber !== asn1.TAGS.SEQUENCE || !node.children ||
|
|
@@ -288,7 +305,7 @@ function _keyOnlyCertStandIn(spkiDer) {
|
|
|
288
305
|
"a key-only signer's spki algorithm is SEQUENCE { algorithm OID, parameters OPTIONAL } (RFC 5280 sec. 4.1.1.2)");
|
|
289
306
|
}
|
|
290
307
|
// The same shape schema-x509 surfaces: the algorithm OID plus its parameters
|
|
291
|
-
// as the
|
|
308
|
+
// as the raw TLV (or null when absent); the resolver re-decodes those bytes
|
|
292
309
|
// for an EC named curve and for the RSASSA-PSS hash pinning, so handing it a
|
|
293
310
|
// different representation here would break exactly those two algorithms.
|
|
294
311
|
alg = {
|
|
@@ -312,9 +329,9 @@ function _keyOnlyKeyId(so) {
|
|
|
312
329
|
throw _err("cms/bad-input",
|
|
313
330
|
"a key-only signer requires keyIdentifier -- the subjectKeyIdentifier the certification request declares (RFC 5272 sec. 3.2)");
|
|
314
331
|
}
|
|
315
|
-
// A key identifier is
|
|
316
|
-
//
|
|
317
|
-
// octets and Buffer.from("a1b2") takes the ASCII of the text
|
|
332
|
+
// A key identifier is bytes, so the type is checked and never coerced. Buffer.from
|
|
333
|
+
// accepts far more than it should mean here: Buffer.from(20) allocates twenty zero
|
|
334
|
+
// octets and Buffer.from("a1b2") takes the ASCII of the text, not the two
|
|
318
335
|
// octets a reader means by it. Either would emit a structurally valid
|
|
319
336
|
// SignerIdentifier carrying an identifier the caller never asked for, which no
|
|
320
337
|
// verifier can match back to the certification request (RFC 5272 sec. 3.2).
|
|
@@ -325,8 +342,10 @@ function _keyOnlyKeyId(so) {
|
|
|
325
342
|
|
|
326
343
|
function _normCertDer(c) {
|
|
327
344
|
if (c == null) throw _err("cms/bad-input", "each signer requires a certificate (cert)");
|
|
328
|
-
if (c instanceof Uint8Array
|
|
329
|
-
|
|
345
|
+
if (c instanceof Uint8Array || Buffer.isBuffer(c)) {
|
|
346
|
+
c = guard.bytes.snapshot(c, CmsError, "cms/bad-input", "a signer certificate");
|
|
347
|
+
return c[0] === 0x30 ? c : _pemToDer(c.toString("latin1")); // DER as-is, else PEM
|
|
348
|
+
}
|
|
330
349
|
if (typeof c === "string") return _pemToDer(c);
|
|
331
350
|
throw _err("cms/bad-input", "a signer certificate must be a DER Buffer or a PEM string");
|
|
332
351
|
}
|
|
@@ -340,12 +359,22 @@ function _pemToDer(text) {
|
|
|
340
359
|
// Documented `-> Promise`, so a fault leaves as a REJECTION (guard-async); the checks stay
|
|
341
360
|
// synchronous because they read the caller's mutable content and signer list.
|
|
342
361
|
function sign(content, signers, opts) {
|
|
343
|
-
|
|
362
|
+
// Every caller-owned argument copied at entry and released when the call settles; see the note
|
|
363
|
+
// on the same call in x509-sign. Here it is what makes the attribute-shaped-content refusal below
|
|
364
|
+
// hold: the value that decides it and the value that gets signed are now the same read.
|
|
365
|
+
return guard.bytes.fixedCall(CmsError, "cms/bad-input", [
|
|
366
|
+
[content, "content"], [signers, "the signer list"], [opts, "pki.cms.sign options"],
|
|
367
|
+
], _sign);
|
|
344
368
|
}
|
|
345
369
|
|
|
346
370
|
function _sign(content, signers, opts) {
|
|
347
371
|
opts = opts || {};
|
|
348
372
|
if (typeof opts !== "object" || Buffer.isBuffer(opts)) throw _err("cms/bad-input", "pki.cms.sign options must be an object");
|
|
373
|
+
guard.identifier.assertKnownKeys(opts, KNOWN_SIGN_OPTS, _err, "cms/bad-input", "unknown opts field ");
|
|
374
|
+
// The arguments were copied at entry (see `sign` above), which is what makes the refusal below
|
|
375
|
+
// hold: flipping signedAttributes from true to false after the call returns would otherwise skip
|
|
376
|
+
// the attribute-shaped-content check while the signer signs that content directly, which is the
|
|
377
|
+
// very signature the stripping attack needs.
|
|
349
378
|
var contentBuf = _toBuf(content, "content");
|
|
350
379
|
var list = Array.isArray(signers) ? signers : [signers];
|
|
351
380
|
if (!list.length) throw _err("cms/bad-input", "pki.cms.sign requires at least one signer");
|
|
@@ -363,13 +392,25 @@ function _sign(content, signers, opts) {
|
|
|
363
392
|
"a key-only signer must be the ONLY SignerInfo in a Full PKI Request (RFC 5272 sec. 3.2)");
|
|
364
393
|
}
|
|
365
394
|
// RFC 5652 sec. 5.3: signed attributes MUST be present (carrying a content-type attribute)
|
|
366
|
-
// whenever the encapsulated content type is not id-data
|
|
395
|
+
// whenever the encapsulated content type is not id-data, so signedAttributes:false is only
|
|
367
396
|
// valid for id-data content. Refusing it here keeps cms.sign from emitting a non-conformant
|
|
368
397
|
// SignedData (e.g. a timestamp token, id-ct-TSTInfo, with no signed attributes).
|
|
369
398
|
if (opts.signedAttributes === false && eContentType !== OID_DATA) {
|
|
370
399
|
throw _err("cms/bad-input", "signed attributes are required when eContentType is not id-data (RFC 5652 sec. 5.3)");
|
|
371
400
|
}
|
|
372
|
-
//
|
|
401
|
+
// The signer's half of the signed-attribute stripping problem
|
|
402
|
+
// (draft-vangeest-lamps-cms-euf-cma-signeddata, Attack Type 2). Signing attribute-shaped content
|
|
403
|
+
// without attributes produces a signature that can afterwards be promoted into an
|
|
404
|
+
// attributes-present message, because the signature does not commit to which mode was used: the
|
|
405
|
+
// attacker attaches the signed bytes as the SignedAttributes and swaps in whatever content their
|
|
406
|
+
// message-digest attribute names. Refusing to mint the ambiguous signature is the only point at
|
|
407
|
+
// which this direction can be stopped -- by the time it is a message, the damage is done.
|
|
408
|
+
if (opts.signedAttributes === false && cms.looksLikeSignedAttributes(contentBuf)) {
|
|
409
|
+
throw _err("cms/ambiguous-content", "this content is itself an encoded SignedAttributes block, so signing " +
|
|
410
|
+
"it WITHOUT signed attributes would produce a signature that could be re-presented as one over " +
|
|
411
|
+
"attributes (RFC 5652 sec. 5.4); sign it with signed attributes instead");
|
|
412
|
+
}
|
|
413
|
+
// A supplied signing-time MUST be a valid Date (or false to omit the attribute), never a
|
|
373
414
|
// silently-ignored non-Date or an Invalid Date that would encode a garbage Time.
|
|
374
415
|
if (opts.signingTime != null && opts.signingTime !== false) guard.time.assertValid(opts.signingTime, _err, "cms/bad-input", "signingTime");
|
|
375
416
|
|
|
@@ -403,7 +444,7 @@ function _sign(content, signers, opts) {
|
|
|
403
444
|
});
|
|
404
445
|
}
|
|
405
446
|
|
|
406
|
-
// Dedupe certificate DERs (two signers may share a cert
|
|
447
|
+
// Dedupe certificate DERs (two signers may share a cert, so embed it once).
|
|
407
448
|
function _dedupe(ders) {
|
|
408
449
|
var seen = {}, out = [];
|
|
409
450
|
ders.forEach(function (d) { var k = d.toString("hex"); if (!seen[k]) { seen[k] = 1; out.push(d); } });
|
|
@@ -411,17 +452,16 @@ function _dedupe(ders) {
|
|
|
411
452
|
}
|
|
412
453
|
|
|
413
454
|
function _toBuf(v, what) {
|
|
414
|
-
if (Buffer.isBuffer(v)) return v;
|
|
415
|
-
if (v instanceof Uint8Array) return Buffer.from(v);
|
|
455
|
+
if (Buffer.isBuffer(v) || v instanceof Uint8Array) return guard.bytes.snapshot(v, CmsError, "cms/bad-input", what);
|
|
416
456
|
throw _err("cms/bad-input", what + " must be a Buffer");
|
|
417
457
|
}
|
|
418
458
|
|
|
419
459
|
// ---- pki.cms.countersign (RFC 5652 sec. 11.4) ------------------------------
|
|
420
|
-
// A countersignature is a SignerInfo (Countersignature ::= SignerInfo) over the
|
|
421
|
-
// countersigned SignerInfo's signature OCTET STRING
|
|
460
|
+
// A countersignature is a SignerInfo (Countersignature ::= SignerInfo) over the contents of the
|
|
461
|
+
// countersigned SignerInfo's signature OCTET STRING, never the eContent, attached as the
|
|
422
462
|
// id-countersignature unsigned attribute. It reuses the whole build+sign flow (resolveSignScheme /
|
|
423
463
|
// _buildSid / _buildSignedAttrs / signOverTbs); only the preimage (the target signature octets) and
|
|
424
|
-
// the content-type
|
|
464
|
+
// the omitted content-type are the deltas, and the orchestrator splices the [1] unsignedAttrs into
|
|
425
465
|
// an existing SignedData while preserving the targeted SignerInfo's signed bytes BYTE-FOR-BYTE.
|
|
426
466
|
|
|
427
467
|
// Resolve opts.signerIndex (a number, an array of numbers, or "all"; default 0) to primary indices.
|
|
@@ -435,8 +475,8 @@ function _resolveSignerIndices(spec, n) {
|
|
|
435
475
|
}
|
|
436
476
|
|
|
437
477
|
// Build one countersignature value (RFC 5652 sec. 11.4) over `targetSigOctets`: message-digest bound
|
|
438
|
-
// to digest(targetSigOctets) under the countersignature's own digestAlgorithm, content-type
|
|
439
|
-
// signed through the
|
|
478
|
+
// to digest(targetSigOctets) under the countersignature's own digestAlgorithm, content-type omitted,
|
|
479
|
+
// signed through the same per-algorithm scheme machinery as a top-level signer.
|
|
440
480
|
function _buildCountersignature(targetSigOctets, countersigner, opts) {
|
|
441
481
|
var so = countersigner || {};
|
|
442
482
|
var certDer = _normCertDer(so.cert);
|
|
@@ -463,9 +503,9 @@ function _buildCountersignature(targetSigOctets, countersigner, opts) {
|
|
|
463
503
|
});
|
|
464
504
|
}
|
|
465
505
|
|
|
466
|
-
// Build the [1] IMPLICIT unsignedAttrs bytes for a SignerInfo, merging `newCsValues` into the
|
|
467
|
-
// id-countersignature attribute (RFC 5652 sec. 11:
|
|
468
|
-
// keeping every
|
|
506
|
+
// Build the [1] IMPLICIT unsignedAttrs bytes for a SignerInfo, merging `newCsValues` into the single
|
|
507
|
+
// id-countersignature attribute (RFC 5652 sec. 11: one instance per type, multiple values), and
|
|
508
|
+
// keeping every other unsigned attribute and every existing countersignature value verbatim.
|
|
469
509
|
function _mergeCountersig(uaNode, newCsValues) {
|
|
470
510
|
var CS = O("countersignature");
|
|
471
511
|
var others = [], csValues = [];
|
|
@@ -539,14 +579,20 @@ function _targetPreimage(siNode, opts) {
|
|
|
539
579
|
}
|
|
540
580
|
|
|
541
581
|
// pki.cms.countersign -- documented by the @primitive block in cms-verify.js (the @module pki.cms home).
|
|
542
|
-
// Documented `-> Promise`, so a fault leaves as a
|
|
582
|
+
// Documented `-> Promise`, so a fault leaves as a rejection (guard-async).
|
|
543
583
|
function countersign(cmsInput, signers, opts) {
|
|
544
|
-
|
|
584
|
+
// Every caller-owned argument copied at entry and released when the call settles; see the note
|
|
585
|
+
// on the same call in x509-sign. `signerIndex` and `countersignatureOf` select which signature is
|
|
586
|
+
// countersigned, so a late read could attach the countersignature to a different one.
|
|
587
|
+
return guard.bytes.fixedCall(CmsError, "cms/bad-input", [
|
|
588
|
+
[cmsInput, "the CMS message"], [signers, "the signer list"], [opts, "pki.cms.countersign options"],
|
|
589
|
+
], _countersign);
|
|
545
590
|
}
|
|
546
591
|
|
|
547
592
|
function _countersign(cmsInput, signers, opts) {
|
|
548
593
|
opts = opts || {};
|
|
549
594
|
if (typeof opts !== "object" || Buffer.isBuffer(opts)) throw _err("cms/bad-input", "pki.cms.countersign options must be an object");
|
|
595
|
+
guard.identifier.assertKnownKeys(opts, KNOWN_COUNTERSIGN_OPTS, _err, "cms/bad-input", "unknown opts field ");
|
|
550
596
|
var list = Array.isArray(signers) ? signers : [signers];
|
|
551
597
|
if (!list.length) throw _err("cms/bad-input", "pki.cms.countersign requires at least one countersigner");
|
|
552
598
|
if (opts.signingTime != null && opts.signingTime !== false) guard.time.assertValid(opts.signingTime, _err, "cms/bad-input", "signingTime");
|
|
@@ -576,7 +622,7 @@ function _countersign(cmsInput, signers, opts) {
|
|
|
576
622
|
return opts.countersignatureOf == null ? _appendCountersigs(siNode, byTarget[idx]) : _spliceNested(siNode, opts.countersignatureOf, byTarget[idx]);
|
|
577
623
|
}));
|
|
578
624
|
|
|
579
|
-
// Rebuild SignedData: version, digestAlgorithms (
|
|
625
|
+
// Rebuild SignedData: version, digestAlgorithms (unchanged, since a countersignature digest is not a
|
|
580
626
|
// SignedData digestAlgorithm), encapContentInfo, certificates [0]?, crls [1]?, the new signerInfos.
|
|
581
627
|
var certsNode = null, crlsNode = null;
|
|
582
628
|
for (var i = 3; i < sdKids.length - 1; i++) {
|
|
@@ -597,21 +643,21 @@ function _countersign(cmsInput, signers, opts) {
|
|
|
597
643
|
});
|
|
598
644
|
}
|
|
599
645
|
|
|
600
|
-
// Coverage residual
|
|
601
|
-
// * `_skiValue`'s `cert.extensions || []` fallback
|
|
646
|
+
// Coverage residual: three defensive branches are unreachable through the shipped path.
|
|
647
|
+
// * `_skiValue`'s `cert.extensions || []` fallback. x509.parse always surfaces `extensions`
|
|
602
648
|
// as an array (empty when absent), so the `|| []` never fires.
|
|
603
|
-
// * `_assertKeyMatchesScheme`'s `key.algorithm || {}
|
|
649
|
+
// * `_assertKeyMatchesScheme`'s `key.algorithm || {}`. A WebCrypto CryptoKey always carries
|
|
604
650
|
// an `algorithm`, so the `|| {}` fallback never fires.
|
|
605
|
-
// * `_assertKeyMatchesScheme`'s `!ka.hash` guard
|
|
651
|
+
// * `_assertKeyMatchesScheme`'s `!ka.hash` guard. An `imp.hash` is set only for an RSA
|
|
606
652
|
// scheme, which requires `ka.name` to already equal the RSA name (else the earlier name
|
|
607
653
|
// check throws); an RSA CryptoKey always carries a `hash`, so `!ka.hash` never fires.
|
|
608
654
|
// Countersign-side residuals also unreachable through the shipped path:
|
|
609
|
-
// * `_resolveSignerIndices`'s `n < 1` throw
|
|
655
|
+
// * `_resolveSignerIndices`'s `n < 1` throw. A parsed SignedData always carries at least one
|
|
610
656
|
// SignerInfo, so the default index [0] is always in range.
|
|
611
|
-
// * `_spliceNested`'s no-countersignature / index-out-of-range / not-found throws
|
|
612
|
-
// is validated by `_targetPreimage`
|
|
657
|
+
// * `_spliceNested`'s no-countersignature / index-out-of-range / not-found throws. The same node
|
|
658
|
+
// is validated by `_targetPreimage` first (it computes the nested preimage before the build), so
|
|
613
659
|
// by the time `_spliceNested` re-walks it those conditions cannot hold; the checks are
|
|
614
660
|
// belt-and-suspenders against a future caller reordering the two.
|
|
615
|
-
// * the `crls [1]` preservation branches in `countersign
|
|
661
|
+
// * the `crls [1]` preservation branches in `countersign`. pki.cms.sign never emits a crls field,
|
|
616
662
|
// so a store this producer countersigns never carries one to preserve.
|
|
617
663
|
module.exports = { sign: sign, countersign: countersign };
|