@blamejs/pki 0.5.7 → 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 +405 -386
- package/MIGRATING.md +43 -0
- package/README.md +12 -12
- package/lib/acme.js +31 -31
- package/lib/asn1-der.js +10 -10
- package/lib/attrcert-sign.js +19 -19
- 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 +97 -92
- package/lib/cmc-verify.js +106 -30
- package/lib/cmp-build.js +25 -25
- package/lib/cmp-session.js +70 -70
- package/lib/cmp-verify.js +71 -57
- package/lib/cms-compress.js +7 -7
- package/lib/cms-decrypt.js +90 -72
- package/lib/cms-encrypt.js +32 -32
- package/lib/cms-sign.js +74 -55
- package/lib/cms-verify.js +97 -75
- package/lib/composite-sig.js +13 -13
- package/lib/constants.js +4 -4
- package/lib/crl-sign.js +22 -22
- package/lib/crl-verify.js +7 -6
- package/lib/crmf-sign.js +14 -14
- package/lib/csr-sign.js +8 -8
- package/lib/ct.js +37 -37
- package/lib/edwards-point.js +7 -7
- package/lib/est.js +98 -55
- 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 +79 -79
- 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 +80 -80
- 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 +13 -13
- package/lib/oid.js +29 -29
- package/lib/path-validate.js +114 -113
- package/lib/pbes2.js +16 -16
- package/lib/pkcs12-build.js +53 -53
- package/lib/pki-build.js +21 -19
- 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 +45 -45
- 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 +13 -13
- 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 +17 -17
- 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 +20 -19
- package/lib/webauthn-mds.js +66 -66
- package/lib/webauthn.js +33 -33
- package/lib/webcrypto.js +15 -15
- package/lib/x509-sign.js +13 -13
- 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");
|
|
@@ -61,13 +61,13 @@ function _wrapOidForKek(keyBytes) {
|
|
|
61
61
|
if (keyBytes === 16) return "aes128-wrap";
|
|
62
62
|
if (keyBytes === 24) return "aes192-wrap";
|
|
63
63
|
if (keyBytes === 32) return "aes256-wrap";
|
|
64
|
-
// 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);
|
|
65
65
|
// a defensive throw for a future caller that hands an off-size key.
|
|
66
66
|
throw _err("cms/bad-input", "no AES key-wrap algorithm for a " + keyBytes + "-octet key-encryption key");
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
// GCMParameters ::= SEQUENCE { aes-nonce OCTET STRING, aes-ICVlen INTEGER DEFAULT 12 }
|
|
70
|
-
// 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).
|
|
71
71
|
function _gcmParams(nonce, icvLen) {
|
|
72
72
|
var kids = [b.octetString(nonce)];
|
|
73
73
|
if (icvLen !== 12) kids.push(b.integer(BigInt(icvLen)));
|
|
@@ -75,7 +75,7 @@ function _gcmParams(nonce, icvLen) {
|
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
// The keyIdentifier option selects the RecipientIdentifier form. "issuerAndSerial" is the documented
|
|
78
|
-
// 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
|
|
79
79
|
// silently emitting issuerAndSerialNumber, so a typo surfaces instead of a rid the caller never asked for.
|
|
80
80
|
function _assertKeyIdentifier(form) {
|
|
81
81
|
if (form != null && form !== "issuerAndSerial" && form !== "issuerAndSerialNumber" && form !== "subjectKeyIdentifier") {
|
|
@@ -97,7 +97,7 @@ function _rid(cert, form) {
|
|
|
97
97
|
// unsupported-curve / low-order / unsupported-KEM-cert throws further below): these validate the
|
|
98
98
|
// CALLER's own recipient certificate at config time (tier-1 THROW). They fire only when a caller
|
|
99
99
|
// supplies a certificate whose SKI/keyUsage extension is malformed, or whose key is an unsupported
|
|
100
|
-
// curve
|
|
100
|
+
// curve, a low-order Montgomery point, or an unsupported KEM: inputs the toolkit never produces.
|
|
101
101
|
function _skiOf(cert) {
|
|
102
102
|
var exts = cert.extensions || [];
|
|
103
103
|
for (var i = 0; i < exts.length; i++) if (exts[i].name === "subjectKeyIdentifier" && exts[i].value != null) {
|
|
@@ -120,7 +120,7 @@ var OAEP_HASH = { sha256: "SHA-256", sha384: "SHA-384", sha512: "SHA-512" };
|
|
|
120
120
|
function _oaepParams(hashName) {
|
|
121
121
|
var hAlg = _algId(hashName, "null");
|
|
122
122
|
var mgf = b.sequence([b.oid(O("mgf1")), hAlg]);
|
|
123
|
-
// pSourceAlgorithm [2] DEFAULT pSpecifiedEmpty
|
|
123
|
+
// pSourceAlgorithm [2] DEFAULT pSpecifiedEmpty: the empty-label default MUST be omitted (X.690).
|
|
124
124
|
return b.sequence([b.explicit(0, hAlg), b.explicit(1, mgf)]);
|
|
125
125
|
}
|
|
126
126
|
async function _buildKtri(cek, cert, opts) {
|
|
@@ -144,8 +144,8 @@ MONT_KA[O("X25519")] = { name: "X25519", hkdf: "SHA-256", scheme: "dhSinglePass-
|
|
|
144
144
|
MONT_KA[O("X448")] = { name: "X448", hkdf: "SHA-512", scheme: "dhSinglePass-stdDH-hkdf-sha512-scheme" };
|
|
145
145
|
|
|
146
146
|
// ECC-CMS-SharedInfo ::= SEQUENCE { keyInfo AlgorithmIdentifier (the wrap, params ABSENT),
|
|
147
|
-
// entityUInfo [0] EXPLICIT OCTET STRING OPTIONAL, suppPubInfo [2] EXPLICIT OCTET STRING }
|
|
148
|
-
// 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,
|
|
149
149
|
// shared by encrypt + decrypt so the two sides cannot diverge.
|
|
150
150
|
function _eccSharedInfo(wrapName, ukm, kekBytes) {
|
|
151
151
|
var kids = [_algId(wrapName, "absent")];
|
|
@@ -184,12 +184,12 @@ async function _buildKari(cek, cert, opts) {
|
|
|
184
184
|
mz = Buffer.from(await subtle.deriveBits({ name: mka.name, public: rPub }, meph.privateKey, null));
|
|
185
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)");
|
|
186
186
|
var mzKey = await subtle.importKey("raw", mz, { name: "HKDF" }, false, ["deriveBits"]);
|
|
187
|
-
// RFC 8418 sec. 2.2: when a ukm is present it is used
|
|
188
|
-
// 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
|
|
189
189
|
// from any conformant peer that reads the transmitted ukm.
|
|
190
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));
|
|
191
191
|
} else {
|
|
192
|
-
// Coverage residual: unreachable
|
|
192
|
+
// Coverage residual: unreachable, since _buildRecipient routes only ecPublicKey / X25519 / X448
|
|
193
193
|
// keys into _buildKari; a defensive throw against a future dispatch change.
|
|
194
194
|
throw _err("cms/unsupported-algorithm", "unsupported recipient key algorithm for kari");
|
|
195
195
|
}
|
|
@@ -199,7 +199,7 @@ async function _buildKari(cek, cert, opts) {
|
|
|
199
199
|
var origPubBits = origSpki.children[1]; // BIT STRING node
|
|
200
200
|
var originatorKey = b.contextConstructed(1, Buffer.concat([origSpki.children[0].bytes, origPubBits.bytes]));
|
|
201
201
|
// KeyAgreeRecipientIdentifier CHOICE { issuerAndSerialNumber, rKeyId [0] IMPLICIT
|
|
202
|
-
// RecipientKeyIdentifier }
|
|
202
|
+
// RecipientKeyIdentifier }, where the SKI form wraps a SEQUENCE (rKeyId), unlike ktri's bare
|
|
203
203
|
// subjectKeyIdentifier [0] IMPLICIT OCTET STRING.
|
|
204
204
|
var ridNode;
|
|
205
205
|
_assertKeyIdentifier(opts.keyIdentifier);
|
|
@@ -234,13 +234,13 @@ async function _buildKekri(cek, desc) {
|
|
|
234
234
|
return { tag: 2, node: b.sequence([b.integer(4n), kekid, _algId(wrapName, "absent"), b.octetString(encryptedKey)]) };
|
|
235
235
|
}
|
|
236
236
|
|
|
237
|
-
// 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
|
|
238
238
|
// ---- pwri (password) : PBKDF2 + RFC 3211 double-CBC PWRI-KEK ---------------
|
|
239
239
|
async function _buildPwri(cek, desc) {
|
|
240
|
-
// A string / Uint8Array password is encoded into a buffer
|
|
241
|
-
// 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
|
|
242
242
|
// borrowed and left intact.
|
|
243
|
-
// The option validation runs
|
|
243
|
+
// The option validation runs before the password is encoded: a rejected iteration count or salt
|
|
244
244
|
// would otherwise abandon an owned credential copy on the way out.
|
|
245
245
|
var iterations = pbes2.assertIterations(desc.iterations == null ? 600000 : desc.iterations, _err, "cms");
|
|
246
246
|
var salt = desc.salt ? pbes2.assertSalt(guard.bytes.view(desc.salt, CmsError, "cms/bad-input", "salt"), _err, "cms") : nodeCrypto.randomBytes(16);
|
|
@@ -250,9 +250,9 @@ async function _buildPwri(cek, desc) {
|
|
|
250
250
|
var pwOwn = pbes2.passwordBytesOwned(desc.password, _err, "cms");
|
|
251
251
|
var password = pwOwn.bytes;
|
|
252
252
|
var kekKey = await subtle.importKey("raw", password, { name: "PBKDF2" }, false, ["deriveBits"]);
|
|
253
|
-
// 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
|
|
254
254
|
// caller passes a Buffer, passwordBytes hands back that very buffer, so wiping it would destroy
|
|
255
|
-
// the caller's own memory
|
|
255
|
+
// the caller's own memory, the one failure this rule must never cause.
|
|
256
256
|
var kek = Buffer.from(await subtle.deriveBits({ name: "PBKDF2", hash: _prfHash(prf), salt: salt, iterations: iterations }, kekKey, innerKeyBytes * 8));
|
|
257
257
|
if (pwOwn.owned) guard.secret.zeroize(password, CmsError, "cms/bad-input", "the password encoding");
|
|
258
258
|
try {
|
|
@@ -278,7 +278,7 @@ KEM_WRAP[O("id-ml-kem-1024")] = "aes256-wrap";
|
|
|
278
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";
|
|
279
279
|
|
|
280
280
|
// CMSORIforKEMOtherInfo ::= SEQUENCE { wrap AlgorithmIdentifier, kekLength INTEGER,
|
|
281
|
-
// ukm [0] EXPLICIT OCTET STRING OPTIONAL }
|
|
281
|
+
// ukm [0] EXPLICIT OCTET STRING OPTIONAL }, the RFC 9629 sec. 5 KDF info, one builder both sides.
|
|
282
282
|
function _kemOtherInfo(wrapName, kekBytes, ukm) {
|
|
283
283
|
var kids = [_algId(wrapName, "absent"), b.integer(BigInt(kekBytes))];
|
|
284
284
|
if (ukm) kids.push(b.explicit(0, b.octetString(ukm)));
|
|
@@ -309,7 +309,7 @@ async function _buildKemri(cek, cert, opts) {
|
|
|
309
309
|
return { tag: 4, node: b.sequence([b.oid(O("kem")), kemri]) };
|
|
310
310
|
} finally {
|
|
311
311
|
// RFC 9629 sec. 7 asks the SENDER to discard the shared secret and KEK once the recipient entry
|
|
312
|
-
// is built
|
|
312
|
+
// is built, and to use a fresh secret per recipient, so this runs per call and not once at
|
|
313
313
|
// the end of a multi-recipient message. In a `finally`, so a wrap or encoding failure does not
|
|
314
314
|
// leave them behind. The CEK is the caller's and is wiped by no one here; kemct is public.
|
|
315
315
|
// kem.sharedKey is the ArrayBuffer the engine returned and ss is this function's copy of it;
|
|
@@ -340,7 +340,7 @@ function _pwriFormat(cek) {
|
|
|
340
340
|
var padLen = body.length % blk === 0 ? 0 : blk - (body.length % blk);
|
|
341
341
|
if (body.length + padLen < 2 * blk) padLen += (2 * blk - (body.length + padLen));
|
|
342
342
|
// and each hold a plaintext copy of the CEK; only the padded result is returned, so
|
|
343
|
-
// the intermediates are cleared here
|
|
343
|
+
// the intermediates are cleared here and never abandoned.
|
|
344
344
|
var wrapped = Buffer.concat([body, nodeCrypto.randomBytes(padLen)]);
|
|
345
345
|
guard.secret.zeroizeAll([check, body], CmsError, "cms/bad-input", "a PWRI formatting intermediate");
|
|
346
346
|
return wrapped;
|
|
@@ -381,7 +381,7 @@ async function _buildRecipient(cek, desc, opts) {
|
|
|
381
381
|
function mergeOpts(opts, desc) {
|
|
382
382
|
return { oaepHash: desc.oaepHash || opts.oaepHash, keyIdentifier: desc.keyIdentifier || opts.keyIdentifier, ukm: desc.ukm != null ? desc.ukm : opts.ukm };
|
|
383
383
|
}
|
|
384
|
-
// 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,
|
|
385
385
|
// so the arm's SEQUENCE tag is replaced by the context tag.
|
|
386
386
|
function _taggedRecipient(r) {
|
|
387
387
|
if (r.tag == null) return r.node;
|
|
@@ -406,8 +406,8 @@ async function encrypt(content, recipients, opts) {
|
|
|
406
406
|
var contentType = opts.contentType || "data";
|
|
407
407
|
var cek = nodeCrypto.randomBytes(ca.keyBits / 8);
|
|
408
408
|
|
|
409
|
-
// The CEK protects the content for
|
|
410
|
-
// 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
|
|
411
411
|
// inside the recipient loop would destroy the key the remaining recipients must be given.
|
|
412
412
|
try {
|
|
413
413
|
// EncryptedData: a single non-array { cek } or { password } descriptor, no RecipientInfos.
|
|
@@ -450,9 +450,9 @@ function _authEnvelopedData(contentBytes, cek, ca, contentType, opts, riNodes, r
|
|
|
450
450
|
var setOf = b.setOf(opts.authAttrs);
|
|
451
451
|
aad = setOf; authAttrsDer = b.contextConstructed(1, setOf.subarray(_tlvHeaderLen(setOf)));
|
|
452
452
|
}
|
|
453
|
-
// 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
|
|
454
454
|
// interops across OpenSSL 3.5 / 4.x. The aes-ICVlen (16) is carried explicitly (RFC 5084 sec. 3.2
|
|
455
|
-
// omits it
|
|
455
|
+
// omits it only when it equals the DEFAULT 12); it MUST equal the mac octet length (M42).
|
|
456
456
|
var g = _gcmEncrypt(cek, nonce, contentBytes, aad, ca.keyBits, 16);
|
|
457
457
|
var eci = b.sequence([b.oid(O(contentType)), b.sequence([b.oid(O(ca.oid)), _gcmParams(nonce, 16)]), b.contextPrimitive(0, g.ct)]);
|
|
458
458
|
var kids = [b.integer(0n), b.setOf(riNodes), eci];
|
|
@@ -482,8 +482,8 @@ function _encryptedData(contentBytes, desc, ca, contentType, opts, cek) {
|
|
|
482
482
|
}
|
|
483
483
|
|
|
484
484
|
function _encryptedDataPbes2(contentBytes, desc, ca, contentType, iv, opts) {
|
|
485
|
-
// A string / Uint8Array password is encoded into a buffer
|
|
486
|
-
// 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
|
|
487
487
|
// borrowed and left intact.
|
|
488
488
|
var pwOwn2 = pbes2.passwordBytesOwned(desc.password, _err, "cms");
|
|
489
489
|
var password = pwOwn2.bytes;
|
|
@@ -554,7 +554,7 @@ async function authenticate(content, recipients, opts) {
|
|
|
554
554
|
if (contentType !== "data" && !withAttrs) throw _err("cms/bad-input", "AuthenticatedData with a non-data contentType requires authenticated attributes (RFC 5652 sec. 9.1)");
|
|
555
555
|
|
|
556
556
|
// The MAC key is this path's content-encryption key: every recipient is given it, so it is cleared
|
|
557
|
-
//
|
|
557
|
+
// once at the end and not per recipient, and only after the MAC has been computed over it.
|
|
558
558
|
var macKey = nodeCrypto.randomBytes(MAC_KEY_OCTETS);
|
|
559
559
|
try {
|
|
560
560
|
var recips = [];
|
|
@@ -574,8 +574,8 @@ async function authenticate(content, recipients, opts) {
|
|
|
574
574
|
];
|
|
575
575
|
if (opts.authAttrs && opts.authAttrs.length) pairs = pairs.concat(opts.authAttrs);
|
|
576
576
|
// Every authAttr (auto-built or caller-supplied) MUST be a well-formed Attribute SEQUENCE
|
|
577
|
-
// { type OBJECT IDENTIFIER, values SET OF } and each type appears at most once (RFC 5652)
|
|
578
|
-
// 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
|
|
579
579
|
// fail an operator's parser downstream.
|
|
580
580
|
var seenTypes = {};
|
|
581
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).
|
|
@@ -342,7 +359,7 @@ function _pemToDer(text) {
|
|
|
342
359
|
// Documented `-> Promise`, so a fault leaves as a REJECTION (guard-async); the checks stay
|
|
343
360
|
// synchronous because they read the caller's mutable content and signer list.
|
|
344
361
|
function sign(content, signers, opts) {
|
|
345
|
-
// Every caller-owned argument copied at entry and released when the call settles
|
|
362
|
+
// Every caller-owned argument copied at entry and released when the call settles; see the note
|
|
346
363
|
// on the same call in x509-sign. Here it is what makes the attribute-shaped-content refusal below
|
|
347
364
|
// hold: the value that decides it and the value that gets signed are now the same read.
|
|
348
365
|
return guard.bytes.fixedCall(CmsError, "cms/bad-input", [
|
|
@@ -353,10 +370,11 @@ function sign(content, signers, opts) {
|
|
|
353
370
|
function _sign(content, signers, opts) {
|
|
354
371
|
opts = opts || {};
|
|
355
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 ");
|
|
356
374
|
// The arguments were copied at entry (see `sign` above), which is what makes the refusal below
|
|
357
375
|
// hold: flipping signedAttributes from true to false after the call returns would otherwise skip
|
|
358
|
-
// the attribute-shaped-content check while the signer signs that content directly
|
|
359
|
-
// signature the stripping attack needs.
|
|
376
|
+
// the attribute-shaped-content check while the signer signs that content directly, which is the
|
|
377
|
+
// very signature the stripping attack needs.
|
|
360
378
|
var contentBuf = _toBuf(content, "content");
|
|
361
379
|
var list = Array.isArray(signers) ? signers : [signers];
|
|
362
380
|
if (!list.length) throw _err("cms/bad-input", "pki.cms.sign requires at least one signer");
|
|
@@ -374,7 +392,7 @@ function _sign(content, signers, opts) {
|
|
|
374
392
|
"a key-only signer must be the ONLY SignerInfo in a Full PKI Request (RFC 5272 sec. 3.2)");
|
|
375
393
|
}
|
|
376
394
|
// RFC 5652 sec. 5.3: signed attributes MUST be present (carrying a content-type attribute)
|
|
377
|
-
// whenever the encapsulated content type is not id-data
|
|
395
|
+
// whenever the encapsulated content type is not id-data, so signedAttributes:false is only
|
|
378
396
|
// valid for id-data content. Refusing it here keeps cms.sign from emitting a non-conformant
|
|
379
397
|
// SignedData (e.g. a timestamp token, id-ct-TSTInfo, with no signed attributes).
|
|
380
398
|
if (opts.signedAttributes === false && eContentType !== OID_DATA) {
|
|
@@ -382,9 +400,9 @@ function _sign(content, signers, opts) {
|
|
|
382
400
|
}
|
|
383
401
|
// The signer's half of the signed-attribute stripping problem
|
|
384
402
|
// (draft-vangeest-lamps-cms-euf-cma-signeddata, Attack Type 2). Signing attribute-shaped content
|
|
385
|
-
//
|
|
403
|
+
// without attributes produces a signature that can afterwards be promoted into an
|
|
386
404
|
// attributes-present message, because the signature does not commit to which mode was used: the
|
|
387
|
-
// attacker attaches the signed bytes
|
|
405
|
+
// attacker attaches the signed bytes as the SignedAttributes and swaps in whatever content their
|
|
388
406
|
// message-digest attribute names. Refusing to mint the ambiguous signature is the only point at
|
|
389
407
|
// which this direction can be stopped -- by the time it is a message, the damage is done.
|
|
390
408
|
if (opts.signedAttributes === false && cms.looksLikeSignedAttributes(contentBuf)) {
|
|
@@ -392,7 +410,7 @@ function _sign(content, signers, opts) {
|
|
|
392
410
|
"it WITHOUT signed attributes would produce a signature that could be re-presented as one over " +
|
|
393
411
|
"attributes (RFC 5652 sec. 5.4); sign it with signed attributes instead");
|
|
394
412
|
}
|
|
395
|
-
// A supplied signing-time MUST be a valid Date (or false to omit the attribute)
|
|
413
|
+
// A supplied signing-time MUST be a valid Date (or false to omit the attribute), never a
|
|
396
414
|
// silently-ignored non-Date or an Invalid Date that would encode a garbage Time.
|
|
397
415
|
if (opts.signingTime != null && opts.signingTime !== false) guard.time.assertValid(opts.signingTime, _err, "cms/bad-input", "signingTime");
|
|
398
416
|
|
|
@@ -426,7 +444,7 @@ function _sign(content, signers, opts) {
|
|
|
426
444
|
});
|
|
427
445
|
}
|
|
428
446
|
|
|
429
|
-
// Dedupe certificate DERs (two signers may share a cert
|
|
447
|
+
// Dedupe certificate DERs (two signers may share a cert, so embed it once).
|
|
430
448
|
function _dedupe(ders) {
|
|
431
449
|
var seen = {}, out = [];
|
|
432
450
|
ders.forEach(function (d) { var k = d.toString("hex"); if (!seen[k]) { seen[k] = 1; out.push(d); } });
|
|
@@ -439,11 +457,11 @@ function _toBuf(v, what) {
|
|
|
439
457
|
}
|
|
440
458
|
|
|
441
459
|
// ---- pki.cms.countersign (RFC 5652 sec. 11.4) ------------------------------
|
|
442
|
-
// A countersignature is a SignerInfo (Countersignature ::= SignerInfo) over the
|
|
443
|
-
// 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
|
|
444
462
|
// id-countersignature unsigned attribute. It reuses the whole build+sign flow (resolveSignScheme /
|
|
445
463
|
// _buildSid / _buildSignedAttrs / signOverTbs); only the preimage (the target signature octets) and
|
|
446
|
-
// the content-type
|
|
464
|
+
// the omitted content-type are the deltas, and the orchestrator splices the [1] unsignedAttrs into
|
|
447
465
|
// an existing SignedData while preserving the targeted SignerInfo's signed bytes BYTE-FOR-BYTE.
|
|
448
466
|
|
|
449
467
|
// Resolve opts.signerIndex (a number, an array of numbers, or "all"; default 0) to primary indices.
|
|
@@ -457,8 +475,8 @@ function _resolveSignerIndices(spec, n) {
|
|
|
457
475
|
}
|
|
458
476
|
|
|
459
477
|
// Build one countersignature value (RFC 5652 sec. 11.4) over `targetSigOctets`: message-digest bound
|
|
460
|
-
// to digest(targetSigOctets) under the countersignature's own digestAlgorithm, content-type
|
|
461
|
-
// 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.
|
|
462
480
|
function _buildCountersignature(targetSigOctets, countersigner, opts) {
|
|
463
481
|
var so = countersigner || {};
|
|
464
482
|
var certDer = _normCertDer(so.cert);
|
|
@@ -485,9 +503,9 @@ function _buildCountersignature(targetSigOctets, countersigner, opts) {
|
|
|
485
503
|
});
|
|
486
504
|
}
|
|
487
505
|
|
|
488
|
-
// Build the [1] IMPLICIT unsignedAttrs bytes for a SignerInfo, merging `newCsValues` into the
|
|
489
|
-
// id-countersignature attribute (RFC 5652 sec. 11:
|
|
490
|
-
// 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.
|
|
491
509
|
function _mergeCountersig(uaNode, newCsValues) {
|
|
492
510
|
var CS = O("countersignature");
|
|
493
511
|
var others = [], csValues = [];
|
|
@@ -561,10 +579,10 @@ function _targetPreimage(siNode, opts) {
|
|
|
561
579
|
}
|
|
562
580
|
|
|
563
581
|
// pki.cms.countersign -- documented by the @primitive block in cms-verify.js (the @module pki.cms home).
|
|
564
|
-
// Documented `-> Promise`, so a fault leaves as a
|
|
582
|
+
// Documented `-> Promise`, so a fault leaves as a rejection (guard-async).
|
|
565
583
|
function countersign(cmsInput, signers, opts) {
|
|
566
|
-
// Every caller-owned argument copied at entry and released when the call settles
|
|
567
|
-
// on the same call in x509-sign. `signerIndex` and `countersignatureOf`
|
|
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
|
|
568
586
|
// countersigned, so a late read could attach the countersignature to a different one.
|
|
569
587
|
return guard.bytes.fixedCall(CmsError, "cms/bad-input", [
|
|
570
588
|
[cmsInput, "the CMS message"], [signers, "the signer list"], [opts, "pki.cms.countersign options"],
|
|
@@ -574,6 +592,7 @@ function countersign(cmsInput, signers, opts) {
|
|
|
574
592
|
function _countersign(cmsInput, signers, opts) {
|
|
575
593
|
opts = opts || {};
|
|
576
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 ");
|
|
577
596
|
var list = Array.isArray(signers) ? signers : [signers];
|
|
578
597
|
if (!list.length) throw _err("cms/bad-input", "pki.cms.countersign requires at least one countersigner");
|
|
579
598
|
if (opts.signingTime != null && opts.signingTime !== false) guard.time.assertValid(opts.signingTime, _err, "cms/bad-input", "signingTime");
|
|
@@ -603,7 +622,7 @@ function _countersign(cmsInput, signers, opts) {
|
|
|
603
622
|
return opts.countersignatureOf == null ? _appendCountersigs(siNode, byTarget[idx]) : _spliceNested(siNode, opts.countersignatureOf, byTarget[idx]);
|
|
604
623
|
}));
|
|
605
624
|
|
|
606
|
-
// Rebuild SignedData: version, digestAlgorithms (
|
|
625
|
+
// Rebuild SignedData: version, digestAlgorithms (unchanged, since a countersignature digest is not a
|
|
607
626
|
// SignedData digestAlgorithm), encapContentInfo, certificates [0]?, crls [1]?, the new signerInfos.
|
|
608
627
|
var certsNode = null, crlsNode = null;
|
|
609
628
|
for (var i = 3; i < sdKids.length - 1; i++) {
|
|
@@ -624,21 +643,21 @@ function _countersign(cmsInput, signers, opts) {
|
|
|
624
643
|
});
|
|
625
644
|
}
|
|
626
645
|
|
|
627
|
-
// Coverage residual
|
|
628
|
-
// * `_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`
|
|
629
648
|
// as an array (empty when absent), so the `|| []` never fires.
|
|
630
|
-
// * `_assertKeyMatchesScheme`'s `key.algorithm || {}
|
|
649
|
+
// * `_assertKeyMatchesScheme`'s `key.algorithm || {}`. A WebCrypto CryptoKey always carries
|
|
631
650
|
// an `algorithm`, so the `|| {}` fallback never fires.
|
|
632
|
-
// * `_assertKeyMatchesScheme`'s `!ka.hash` guard
|
|
651
|
+
// * `_assertKeyMatchesScheme`'s `!ka.hash` guard. An `imp.hash` is set only for an RSA
|
|
633
652
|
// scheme, which requires `ka.name` to already equal the RSA name (else the earlier name
|
|
634
653
|
// check throws); an RSA CryptoKey always carries a `hash`, so `!ka.hash` never fires.
|
|
635
654
|
// Countersign-side residuals also unreachable through the shipped path:
|
|
636
|
-
// * `_resolveSignerIndices`'s `n < 1` throw
|
|
655
|
+
// * `_resolveSignerIndices`'s `n < 1` throw. A parsed SignedData always carries at least one
|
|
637
656
|
// SignerInfo, so the default index [0] is always in range.
|
|
638
|
-
// * `_spliceNested`'s no-countersignature / index-out-of-range / not-found throws
|
|
639
|
-
// 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
|
|
640
659
|
// by the time `_spliceNested` re-walks it those conditions cannot hold; the checks are
|
|
641
660
|
// belt-and-suspenders against a future caller reordering the two.
|
|
642
|
-
// * the `crls [1]` preservation branches in `countersign
|
|
661
|
+
// * the `crls [1]` preservation branches in `countersign`. pki.cms.sign never emits a crls field,
|
|
643
662
|
// so a store this producer countersigns never carries one to preserve.
|
|
644
663
|
module.exports = { sign: sign, countersign: countersign };
|