@blamejs/pki 0.6.13 → 0.6.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -1
- package/README.md +1 -1
- package/index.js +1 -1
- package/lib/acme.js +59 -815
- package/lib/asn1-der.js +7 -245
- package/lib/attrcert-sign.js +12 -260
- package/lib/attrcert-verify.js +1 -7
- package/lib/byte-reader.js +0 -8
- package/lib/byte-writer.js +0 -11
- package/lib/cbor-det.js +0 -54
- package/lib/cmc-build.js +15 -357
- package/lib/cmc-verify.js +6 -239
- package/lib/cmp-build.js +41 -317
- package/lib/cmp-session.js +73 -760
- package/lib/cmp-verify.js +32 -488
- package/lib/cms-compress.js +3 -32
- package/lib/cms-decrypt.js +29 -294
- package/lib/cms-encrypt.js +14 -234
- package/lib/cms-sign.js +29 -324
- package/lib/cms-verify.js +9 -500
- package/lib/composite-kem.js +2 -102
- package/lib/composite-sig.js +1 -77
- package/lib/constants.js +0 -249
- package/lib/crl-sign.js +9 -237
- package/lib/crl-verify.js +1 -10
- package/lib/crmf-sign.js +19 -198
- package/lib/crmf-verify.js +1 -12
- package/lib/csr-sign.js +3 -34
- package/lib/csr-verify.js +1 -12
- package/lib/ct.js +28 -253
- package/lib/edwards-point.js +10 -44
- package/lib/est.js +28 -764
- package/lib/framework-error.js +4 -182
- package/lib/guard-all.js +1 -58
- package/lib/guard-async.js +1 -36
- package/lib/guard-bytes.js +6 -650
- package/lib/guard-compress.js +5 -102
- package/lib/guard-crypto.js +1 -40
- package/lib/guard-der.js +2 -93
- package/lib/guard-encoding.js +2 -60
- package/lib/guard-header.js +1 -36
- package/lib/guard-identifier.js +7 -531
- package/lib/guard-intrinsic.js +1 -150
- package/lib/guard-json.js +10 -73
- package/lib/guard-limits.js +1 -77
- package/lib/guard-list.js +1 -49
- package/lib/guard-name.js +3 -209
- package/lib/guard-parsed.js +7 -335
- package/lib/guard-range.js +1 -71
- package/lib/guard-secret.js +1 -102
- package/lib/guard-text.js +1 -64
- package/lib/guard-time.js +1 -97
- package/lib/hpke.js +3 -105
- package/lib/http-digest.js +14 -142
- package/lib/http-retry-after.js +1 -47
- package/lib/http-transport.js +21 -131
- package/lib/inspect.js +30 -284
- package/lib/ip-utils.js +6 -47
- package/lib/jose.js +7 -142
- package/lib/key.js +2 -67
- package/lib/lint.js +9 -194
- package/lib/merkle.js +1 -27
- package/lib/mime.js +9 -82
- package/lib/ocsp-request-verify.js +1 -14
- package/lib/ocsp-verify.js +1 -82
- package/lib/ocsp.js +11 -169
- package/lib/oid.js +5 -337
- package/lib/path-validate.js +103 -1492
- package/lib/pbes2.js +2 -89
- package/lib/pkcs12-build.js +31 -379
- package/lib/pki-build.js +64 -290
- package/lib/rc2.js +2 -18
- package/lib/rfc3339.js +4 -27
- package/lib/scep.js +59 -233
- package/lib/schema-all.js +0 -122
- package/lib/schema-attrcert.js +4 -146
- package/lib/schema-c509.js +153 -802
- package/lib/schema-cmc.js +5 -220
- package/lib/schema-cmp.js +2 -223
- package/lib/schema-cms.js +13 -427
- package/lib/schema-crl.js +5 -68
- package/lib/schema-crmf.js +6 -167
- package/lib/schema-csr.js +1 -54
- package/lib/schema-csrattrs.js +2 -87
- package/lib/schema-engine.js +0 -152
- package/lib/schema-ocsp.js +0 -117
- package/lib/schema-pkcs12.js +2 -93
- package/lib/schema-pkcs8.js +3 -35
- package/lib/schema-pkix.js +24 -655
- package/lib/schema-smime.js +0 -54
- package/lib/schema-tsp.js +3 -99
- package/lib/schema-x509.js +4 -63
- package/lib/shbs.js +11 -95
- package/lib/sign-scheme.js +2 -97
- package/lib/sigstore.js +9 -169
- package/lib/sleep.js +2 -8
- package/lib/smime.js +15 -408
- package/lib/tls-cert-compress.js +0 -62
- package/lib/trust.js +19 -185
- package/lib/tsp-sign.js +16 -270
- package/lib/validator-all.js +1 -30
- package/lib/validator-attcert.js +2 -46
- package/lib/validator-cose.js +2 -153
- package/lib/validator-keydesc.js +1 -37
- package/lib/validator-sig.js +1 -46
- package/lib/validator-tls.js +1 -88
- package/lib/validator-tpm.js +19 -122
- package/lib/verify-seam.js +1 -24
- package/lib/webauthn-mds.js +3 -400
- package/lib/webauthn.js +12 -833
- package/lib/webcrypto.js +5 -485
- package/lib/x509-sign.js +4 -108
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/lib/cms-verify.js
CHANGED
|
@@ -39,10 +39,8 @@ var cmsEncrypt = require("./cms-encrypt");
|
|
|
39
39
|
var cmsDecrypt = require("./cms-decrypt");
|
|
40
40
|
var cmsCompress = require("./cms-compress");
|
|
41
41
|
var signScheme = require("./sign-scheme");
|
|
42
|
-
// The digest-strength question is asked of sign-scheme, which owns the policy table, so the signer
|
|
43
|
-
// and this verifier cannot drift (RFC 9882 sec. 3.3).
|
|
44
42
|
var _mldsaDigestSuitable = signScheme.mldsaDigestSuitable;
|
|
45
|
-
var SLHDSA_BY_OID = signScheme.SLHDSA_BY_OID;
|
|
43
|
+
var SLHDSA_BY_OID = signScheme.SLHDSA_BY_OID;
|
|
46
44
|
var validator = require("./validator-all");
|
|
47
45
|
var compositeSig = require("./composite-sig");
|
|
48
46
|
var guard = require("./guard-all");
|
|
@@ -52,11 +50,6 @@ var pkix = require("./schema-pkix");
|
|
|
52
50
|
|
|
53
51
|
var CmsError = frameworkError.CmsError;
|
|
54
52
|
function _err(code, message, cause) { return new CmsError(code, message, cause); }
|
|
55
|
-
// Promise.resolve captured at load and invoked through the captured Reflect.apply, so a streamed
|
|
56
|
-
// opts.content -- whose iterator runs during the digest await and can replace Promise.resolve -- cannot
|
|
57
|
-
// turn a resolved { ok: false } verdict into acceptance. The verify path resolves every value through
|
|
58
|
-
// this and awaits every step: Promise.all is deliberately NOT used, since its spec-internal element
|
|
59
|
-
// handling dispatches through the caller-replaceable Promise.prototype.then, which await does not.
|
|
60
53
|
var _Promise = Promise, _promiseResolveRaw = Promise.resolve;
|
|
61
54
|
function _pResolve(v) { return intrinsic.apply(_promiseResolveRaw, _Promise, [v]); }
|
|
62
55
|
|
|
@@ -64,41 +57,19 @@ var OID_MESSAGE_DIGEST = oid.byName("messageDigest");
|
|
|
64
57
|
var OID_CONTENT_TYPE = oid.byName("contentType");
|
|
65
58
|
var OID_COUNTERSIGNATURE = oid.byName("countersignature");
|
|
66
59
|
|
|
67
|
-
// A digest-algorithm name -> the engine's hash name, for computing a MESSAGE DIGEST (the
|
|
68
|
-
// message-digest attribute and the content digest). The FIPS 202 extendable-output functions
|
|
69
|
-
// belong here at the lengths RFC 8702 sec. 4 fixes for that use (SHAKE128 32 bytes, SHAKE256 64).
|
|
70
60
|
var DIGEST_HASH = {
|
|
71
61
|
sha1: "SHA-1", sha256: "SHA-256", sha384: "SHA-384", sha512: "SHA-512",
|
|
72
62
|
shake128: "SHAKE128", shake256: "SHAKE256",
|
|
73
63
|
};
|
|
74
|
-
// The SIGNATURE hash a bare-key-OID scheme (rsaEncryption, ecPublicKey) inherits from the
|
|
75
|
-
// SignerInfo digestAlgorithm. Deliberately a SEPARATE table from DIGEST_HASH, and deliberately
|
|
76
|
-
// without the extendable-output functions: RFC 8702 sec. 3.2 gives RSASSA-PKCS1-v1_5-with-SHAKE
|
|
77
|
-
// and ECDSA-with-SHAKE their own signature OIDs and never pairs a bare key OID with a SHAKE
|
|
78
|
-
// digestAlgorithm, so that combination is non-conformant and must keep its precise refusal.
|
|
79
|
-
// Folding the two roles into one table would resolve a signature hash for it instead, letting
|
|
80
|
-
// it past the fail-closed gate below and into the engine, where the caller gets a relabeled
|
|
81
|
-
// foreign fault and loses this module's own unsupported-algorithm verdict.
|
|
82
64
|
var SIG_HASH = { sha1: "SHA-1", sha256: "SHA-256", sha384: "SHA-384", sha512: "SHA-512" };
|
|
83
|
-
// Is `name` a message-digest algorithm this verifier supports?
|
|
84
65
|
function _supportedDigest(name) { return intrinsic.hasOwn(DIGEST_HASH, name) && !!DIGEST_HASH[name]; }
|
|
85
|
-
// The digest of `content` under the named algorithm, resolved to a Buffer.
|
|
86
66
|
async function _computeDigest(name, content) {
|
|
87
67
|
return intrinsic.bufferFrom(await subtle.digest(DIGEST_HASH[name], content));
|
|
88
68
|
}
|
|
89
|
-
// A signatureAlgorithm name -> its verify scheme. A combined OID (sha256WithRSAEncryption,
|
|
90
|
-
// ecdsaWithSHA256) carries its own hash; a bare key OID (rsaEncryption, ecPublicKey) takes the
|
|
91
|
-
// hash from the SignerInfo digestAlgorithm.
|
|
92
|
-
// `params` pins the AlgorithmIdentifier parameters shape RFC 5754 requires: "null" for the
|
|
93
|
-
// RSASSA-PKCS1-v1_5 family (RFC 4055), "absent" for ECDSA (RFC 5758) and EdDSA (RFC 8410).
|
|
94
|
-
// RSASSA-PSS omits it -- its parameters ARE the RSASSA-PSS-params, resolved by _resolvePss.
|
|
95
69
|
var SIG_SCHEME = {
|
|
96
70
|
rsaEncryption: { kind: "rsa", params: "null" },
|
|
97
71
|
rsassaPss: { kind: "rsapss" },
|
|
98
72
|
ecPublicKey: { kind: "ec", params: "absent" },
|
|
99
|
-
// One-shot families (EdDSA, ML-DSA): the same OID identifies the key and the signature, so the
|
|
100
|
-
// signer cert SPKI algorithm OID MUST equal the signatureAlgorithm OID. `sameKeyOid` enables
|
|
101
|
-
// that agreement check (RFC 8410 / RFC 9882; enforced in _verifyAgainstCandidates).
|
|
102
73
|
Ed25519: { kind: "eddsa", name: "Ed25519", params: "absent", sameKeyOid: true },
|
|
103
74
|
Ed448: { kind: "eddsa", name: "Ed448", params: "absent", sameKeyOid: true },
|
|
104
75
|
"id-ml-dsa-44": { kind: "mldsa", name: "ML-DSA-44", params: "absent", sameKeyOid: true },
|
|
@@ -113,17 +84,11 @@ var SIG_SCHEME = {
|
|
|
113
84
|
ecdsaWithSHA384: { kind: "ec", hash: "SHA-384", params: "absent" },
|
|
114
85
|
ecdsaWithSHA512: { kind: "ec", hash: "SHA-512", params: "absent" },
|
|
115
86
|
};
|
|
116
|
-
// SLH-DSA (RFC 9814): the twelve pure FIPS 205 sets, seeded like webcrypto's SLH_DSA_NODE. Each is
|
|
117
|
-
// a one-shot family (kind "slhdsa"), params absent, sig-OID == key-OID (sameKeyOid). The WebCrypto
|
|
118
|
-
// name is "SLH-DSA-"+SET, matching path-validate's transform (drift rule sec. 5: one name map).
|
|
119
87
|
["sha2-128s", "sha2-128f", "sha2-192s", "sha2-192f", "sha2-256s", "sha2-256f",
|
|
120
88
|
"shake-128s", "shake-128f", "shake-192s", "shake-192f", "shake-256s", "shake-256f"
|
|
121
89
|
].forEach(function (s) { SIG_SCHEME["id-slh-dsa-" + s] = { kind: "slhdsa", name: "SLH-DSA-" + s.toUpperCase(), params: "absent", sameKeyOid: true, digest: SLHDSA_BY_OID[oid.byName("id-slh-dsa-" + s)].digest }; });
|
|
122
|
-
// The signatureAlgorithm parameters match the algorithm's fixed shape (RFC 5754). A DER NULL
|
|
123
|
-
// is exactly `05 00`; "absent" is the parameters field omitted entirely.
|
|
124
90
|
function _isDerNull(p) { return Buffer.isBuffer(p) && p.length === 2 && p[0] === 0x05 && p[1] === 0x00; }
|
|
125
91
|
function _algParamsOk(shape, p) { return shape === "null" ? _isDerNull(p) : (p === null || p === undefined); }
|
|
126
|
-
// An EC named-curve OID (in the signer cert SPKI) -> the WebCrypto curve + r/s coordinate width.
|
|
127
92
|
var EC_CURVE = {};
|
|
128
93
|
EC_CURVE[oid.byName("prime256v1")] = { curve: "P-256", coordLen: 32 };
|
|
129
94
|
EC_CURVE[oid.byName("secp384r1")] = { curve: "P-384", coordLen: 48 };
|
|
@@ -134,17 +99,11 @@ function _toBuf(v, what) {
|
|
|
134
99
|
throw _err("cms/bad-input", what + " must be a Buffer");
|
|
135
100
|
}
|
|
136
101
|
|
|
137
|
-
// Every certificate a SignerInfo's signer identifier matches, among the SignedData's embedded
|
|
138
|
-
// certificates plus any the caller supplies. issuerAndSerialNumber matches on the canonical
|
|
139
|
-
// DN + serial; subjectKeyIdentifier matches the cert's SKI extension. ALL matches are returned
|
|
140
|
-
// (in candidate order) so a colliding certificate placed before the real signer cannot hide it.
|
|
141
102
|
function _findSignerCerts(sid, parsedCerts) {
|
|
142
103
|
var out = [];
|
|
143
104
|
for (var i = 0; i < parsedCerts.length; i++) {
|
|
144
105
|
var c = parsedCerts[i];
|
|
145
106
|
if (sid.subjectKeyIdentifier != null) {
|
|
146
|
-
// Captured equality: over a streamed content the untrusted iterator ran during the digest await
|
|
147
|
-
// and could replace Buffer.prototype.equals to match the wrong certificate to a signer.
|
|
148
107
|
if (c.ski && intrinsic.bufferEquals(c.ski, _toBuf(sid.subjectKeyIdentifier, "sid.subjectKeyIdentifier"))) intrinsic.push(out, c);
|
|
149
108
|
} else if (sid.issuer && sid.serialNumberHex != null) {
|
|
150
109
|
if (c.cert.serialNumberHex === sid.serialNumberHex && guard.name.dnEqual(c.cert.issuer.rdns, sid.issuer.rdns, _err, "cms/bad-name", "the signer certificate issuer")) intrinsic.push(out, c);
|
|
@@ -153,26 +112,14 @@ function _findSignerCerts(sid, parsedCerts) {
|
|
|
153
112
|
return out;
|
|
154
113
|
}
|
|
155
114
|
|
|
156
|
-
// Verify `sigBytes` over `signedBytes` against each matching candidate certificate in turn; the
|
|
157
|
-
// signer is valid iff one candidate verifies. A candidate whose key is structurally unusable (a
|
|
158
|
-
// low-order EdDSA point, an unsupported curve) or whose signature simply does not verify falls
|
|
159
|
-
// through to the next -- so a colliding certificate cannot make a valid signature read invalid.
|
|
160
|
-
// When no candidate verifies, a plain false is a code-less verdict; the last structural fault's
|
|
161
|
-
// code is surfaced if one occurred (so the diagnostic is not lost).
|
|
162
115
|
async function _verifyAgainstCandidates(scheme, sigHash, sigBytes, signedBytes, sid, candidates, pssSalt, expectedKeyOid) {
|
|
163
116
|
var lastErr = null;
|
|
164
117
|
for (var idx = 0; idx < candidates.length; idx++) {
|
|
165
118
|
var c = candidates[idx];
|
|
166
|
-
// One-shot family (EdDSA/ML-DSA): the signer cert public-key algorithm OID MUST equal the
|
|
167
|
-
// SignerInfo signatureAlgorithm OID. A candidate whose SPKI disagrees is skipped with a precise
|
|
168
|
-
// verdict; importing it under the wrong name would surface a foreign webcrypto/data throw.
|
|
169
119
|
if (expectedKeyOid && c.cert.subjectPublicKeyInfo.algorithm.oid !== expectedKeyOid) {
|
|
170
120
|
lastErr = _err("cms/unsupported-algorithm", "the signer certificate public-key algorithm does not match the SignerInfo signatureAlgorithm");
|
|
171
121
|
continue;
|
|
172
122
|
}
|
|
173
|
-
// `await`, not `.then`: over a streamed content the untrusted iterator ran during the digest await
|
|
174
|
-
// and could replace Promise.prototype.then to return a fabricated `true`; await is immune (it does
|
|
175
|
-
// not dispatch through the caller-replaceable prototype method).
|
|
176
123
|
try {
|
|
177
124
|
var ok = await _verifySignature(scheme, sigHash, sigBytes, c.cert.subjectPublicKeyInfo.bytes, signedBytes, _certCurveOid(c.cert), pssSalt);
|
|
178
125
|
if (ok === true) return { ok: true, sid: sid, cert: c.der };
|
|
@@ -184,7 +131,6 @@ async function _verifyAgainstCandidates(scheme, sigHash, sigBytes, signedBytes,
|
|
|
184
131
|
: { ok: false, sid: sid, cert: candidates[0].der };
|
|
185
132
|
}
|
|
186
133
|
|
|
187
|
-
// The cert's subjectKeyIdentifier extension value (the raw key id), or null.
|
|
188
134
|
function _certSki(cert) {
|
|
189
135
|
var ext = (cert.extensions || []).filter(function (e) { return e.oid === oid.byName("subjectKeyIdentifier"); })[0];
|
|
190
136
|
if (!ext) return null;
|
|
@@ -193,17 +139,12 @@ function _certSki(cert) {
|
|
|
193
139
|
} catch (_e) { return null; }
|
|
194
140
|
}
|
|
195
141
|
|
|
196
|
-
// RSASSA-PSS-params resolution (RFC 4055). A SHA-2 hashAlgorithm name -> its WebCrypto hash
|
|
197
|
-
// and the salt length WebCrypto verifies (the hash-length salt of the supported profile).
|
|
198
142
|
var OID_MGF1 = oid.byName("mgf1");
|
|
199
143
|
var PSS_HASH = {};
|
|
200
144
|
PSS_HASH[oid.byName("sha256")] = "SHA-256";
|
|
201
145
|
PSS_HASH[oid.byName("sha384")] = "SHA-384";
|
|
202
146
|
PSS_HASH[oid.byName("sha512")] = "SHA-512";
|
|
203
147
|
|
|
204
|
-
// A hash AlgorithmIdentifier { OID, parameters? } whose parameters, when present, MUST be a
|
|
205
|
-
// DER NULL with empty content (RFC 4055 sec. 2.1 / RFC 5754). Returns the hash OID, or null
|
|
206
|
-
// on any malformed shape.
|
|
207
148
|
function _hashAlgOid(seq) {
|
|
208
149
|
if (!seq || seq.tagClass !== "universal" || seq.tagNumber !== asn1.TAGS.SEQUENCE || !seq.children || seq.children.length < 1 || seq.children.length > 2) return null;
|
|
209
150
|
var o;
|
|
@@ -220,12 +161,6 @@ function _hashAlgOid(seq) {
|
|
|
220
161
|
return o;
|
|
221
162
|
}
|
|
222
163
|
|
|
223
|
-
// Resolve RSASSA-PSS-params (RFC 4055) to { hash, saltLength }, or null if the parameters
|
|
224
|
-
// deviate from the supported profile: an explicit SHA-2 hashAlgorithm, MGF1 keyed to the same
|
|
225
|
-
// hash, saltLength equal to the hash length, and trailerField 1. The SHA-1 DEFAULTs are
|
|
226
|
-
// rejected (params must be explicit), matching the certification-path validator, so a
|
|
227
|
-
// non-conformant PSS AlgorithmIdentifier is a fail-closed verdict. Falling back to WebCrypto's
|
|
228
|
-
// own defaults would be a signatureAlgorithm bypass.
|
|
229
164
|
function _resolvePss(paramsBytes) {
|
|
230
165
|
if (!paramsBytes) return null;
|
|
231
166
|
var n;
|
|
@@ -236,8 +171,6 @@ function _resolvePss(paramsBytes) {
|
|
|
236
171
|
var hash = null, saltLen = null, mgfNode = null, trailer = 1n, last = -1;
|
|
237
172
|
for (var i = 0; i < n.children.length; i++) {
|
|
238
173
|
var f = n.children[i];
|
|
239
|
-
// Each field is an EXPLICIT context [0..3] wrapper carrying exactly one value, at most
|
|
240
|
-
// once and in ascending order; anything else is malformed.
|
|
241
174
|
if (f.tagClass !== "context" || f.tagNumber > 3 || f.tagNumber <= last || !f.children || f.children.length !== 1) return null;
|
|
242
175
|
last = f.tagNumber;
|
|
243
176
|
try {
|
|
@@ -247,7 +180,7 @@ function _resolvePss(paramsBytes) {
|
|
|
247
180
|
else { trailer = asn1.read.integer(f.children[0]); }
|
|
248
181
|
} catch (_e3) { return null; }
|
|
249
182
|
}
|
|
250
|
-
if (hash === null || mgfNode === null) return null;
|
|
183
|
+
if (hash === null || mgfNode === null) return null;
|
|
251
184
|
if (mgfNode.tagClass !== "universal" || mgfNode.tagNumber !== asn1.TAGS.SEQUENCE || !mgfNode.children || mgfNode.children.length !== 2) return null;
|
|
252
185
|
var mgfOid;
|
|
253
186
|
try {
|
|
@@ -255,21 +188,11 @@ function _resolvePss(paramsBytes) {
|
|
|
255
188
|
} catch (_e4) { return null; }
|
|
256
189
|
if (mgfOid !== OID_MGF1 || PSS_HASH[_hashAlgOid(mgfNode.children[1])] !== hash) return null;
|
|
257
190
|
if (trailer !== 1n) return null;
|
|
258
|
-
// saltLength: an absent field is the RFC 4055 DEFAULT of 20; a declared value is honored and
|
|
259
|
-
// passed through to WebCrypto (RSASSA-PSS is not pinned to hash-length salt). It is bounded to
|
|
260
|
-
// a non-negative int -- a negative value maps to OpenSSL's RSA_PSS_SALTLEN_DIGEST/AUTO/MAX
|
|
261
|
-
// magic, and AUTO accepts any salt length, defeating the salt-length binding.
|
|
262
191
|
var saltLength = saltLen === null ? 20 : guard.range.uint31(saltLen, _err, "cms/unsupported-algorithm", "RSASSA-PSS saltLength");
|
|
263
192
|
return { hash: hash, saltLength: saltLength };
|
|
264
193
|
}
|
|
265
194
|
|
|
266
|
-
// Verify the signature over `signedBytes` with the signer certificate's public key under the
|
|
267
|
-
// resolved scheme + hash. Resolves true/false (a false verdict is a verdict); a structural
|
|
268
|
-
// fault throws a typed CmsError.
|
|
269
195
|
async function _verifySignature(scheme, hashName, sigBytes, spki, signedBytes, curveOid, pssSalt) {
|
|
270
|
-
// `await` throughout, never `.then`: a streamed content's untrusted iterator ran during the digest
|
|
271
|
-
// await and could replace Promise.prototype.then to answer a fabricated `true`; await does not
|
|
272
|
-
// dispatch through it.
|
|
273
196
|
if (scheme.kind === "rsa") {
|
|
274
197
|
var kRsa = await subtle.importKey("spki", spki, { name: "RSASSA-PKCS1-v1_5", hash: hashName }, false, ["verify"]);
|
|
275
198
|
return subtle.verify({ name: "RSASSA-PKCS1-v1_5" }, kRsa, sigBytes, signedBytes);
|
|
@@ -281,36 +204,23 @@ async function _verifySignature(scheme, hashName, sigBytes, spki, signedBytes, c
|
|
|
281
204
|
if (scheme.kind === "ec") {
|
|
282
205
|
var ec = EC_CURVE[curveOid];
|
|
283
206
|
if (!ec) throw _err("cms/unsupported-algorithm", "the signer key is on an unsupported EC curve");
|
|
284
|
-
// The ORDER-AWARE gate: r,s in [1, n-1] per FIPS 186-5 sec. 6.4.2 (rejecting an out-of-range r/s >= the
|
|
285
|
-
// curve order n, not only the r=s=0 forgery). A verifier that knows the curve MUST use it.
|
|
286
207
|
var raw = validator.sig.ecdsaDerToP1363(sigBytes, ec.curve, CmsError, "cms/bad-signature");
|
|
287
208
|
var kEc = await subtle.importKey("spki", spki, { name: "ECDSA", namedCurve: ec.curve }, false, ["verify"]);
|
|
288
209
|
return subtle.verify({ name: "ECDSA", hash: hashName }, kEc, raw, signedBytes);
|
|
289
210
|
}
|
|
290
211
|
if (scheme.kind === "mldsa" || scheme.kind === "slhdsa") {
|
|
291
|
-
// ML-DSA (RFC 9882) / SLH-DSA (RFC 9814): a one-shot post-quantum verify, pure mode, empty
|
|
292
|
-
// context -- no external signature hash and no Edwards-point validation (FIPS 204 sec. 5.3 /
|
|
293
|
-
// FIPS 205 sec. 10.3; node structurally validates the SPKI on import). The signature is raw and
|
|
294
|
-
// fixed-length (not ECDSA-DER).
|
|
295
212
|
var kPq = await subtle.importKey("spki", spki, { name: scheme.name }, false, ["verify"]);
|
|
296
213
|
return subtle.verify({ name: scheme.name }, kPq, sigBytes, signedBytes);
|
|
297
214
|
}
|
|
298
|
-
// EdDSA -- the WebCrypto name follows the signing key's SPKI OID (Ed25519 / Ed448). node/
|
|
299
|
-
// OpenSSL imports any Ed25519/Ed448 SPKI without validating the point, and a low-order (e.g.
|
|
300
|
-
// all-zeroes) key verifies a forged signature, so reject a non-full-order point first.
|
|
301
215
|
_requireValidEdPoint(spki, scheme.name);
|
|
302
216
|
var kEd = await subtle.importKey("spki", spki, { name: scheme.name }, false, ["verify"]);
|
|
303
217
|
return subtle.verify({ name: scheme.name }, kEd, sigBytes, signedBytes);
|
|
304
218
|
}
|
|
305
219
|
|
|
306
|
-
// The EdDSA signer key's point MUST be a valid, canonical, full-order Edwards point: reject an
|
|
307
|
-
// off-curve or low-order key before it verifies a signature (WebCrypto import does not check it).
|
|
308
|
-
// Routed through the shared edwards-point gate every EdDSA verify path uses; curve from the name.
|
|
309
220
|
function _requireValidEdPoint(spkiBytes, name) {
|
|
310
221
|
edwardsPoint.validateSpki(spkiBytes, name === "Ed25519" ? 6 : 7, CmsError, "cms/bad-signature");
|
|
311
222
|
}
|
|
312
223
|
|
|
313
|
-
// The EC named-curve OID carried in a signer cert's SubjectPublicKeyInfo, or null (non-EC).
|
|
314
224
|
function _certCurveOid(cert) {
|
|
315
225
|
var p = cert.subjectPublicKeyInfo.algorithm.parameters;
|
|
316
226
|
if (cert.subjectPublicKeyInfo.algorithm.oid !== oid.byName("ecPublicKey") || !Buffer.isBuffer(p)) return null;
|
|
@@ -319,11 +229,6 @@ function _certCurveOid(cert) {
|
|
|
319
229
|
} catch (_e) { return null; }
|
|
320
230
|
}
|
|
321
231
|
|
|
322
|
-
// Decode the SignedAttributes SET OF Attribute from the exact bytes the signature covers
|
|
323
|
-
// (the re-tagged signedAttrsBytes), returning [{ type, values: [valueNode] }]. Binding the
|
|
324
|
-
// content-type / message-digest checks to these decoded attributes means a parsed-object input
|
|
325
|
-
// cannot desync the checked attributes from the verified preimage (RFC 5652 sec. 5.4). The
|
|
326
|
-
// caller-mutable parsed si.signedAttrs is deliberately left out of that decision.
|
|
327
232
|
function _decodeSignedAttrs(setOfBytes) {
|
|
328
233
|
var set = asn1.decode(setOfBytes);
|
|
329
234
|
if (set.tagClass !== "universal" || set.tagNumber !== asn1.TAGS.SET || !set.children) throw _err("cms/bad-signed-attrs", "signedAttrs is not a SET OF Attribute");
|
|
@@ -335,90 +240,40 @@ function _decodeSignedAttrs(setOfBytes) {
|
|
|
335
240
|
});
|
|
336
241
|
}
|
|
337
242
|
|
|
338
|
-
// Verify one SignerInfo. Returns { ok, sid, cert, ... }, where a resolved false is a verdict and
|
|
339
|
-
// a structural failure carries a code.
|
|
340
|
-
// `csTarget`, when set (a Buffer), makes this a COUNTERSIGNATURE verify (RFC 5652 sec. 11.4): the
|
|
341
|
-
// preimage is the countersigned signature octets, not the sec. 5.4 content preimage, and no
|
|
342
|
-
// content-type attribute is required or checked (_computeCountersigBytes). content/eContentType are
|
|
343
|
-
// unused in that mode.
|
|
344
243
|
async function _verifyOne(si, content, eContentType, parsedCerts, csTarget, digestByName) {
|
|
345
|
-
// A composite ML-DSA SignerInfo (draft-ietf-lamps-cms-composite-sigs) is keyed by its composite
|
|
346
|
-
// OID, not a SIG_SCHEME name; intercept it before the classical dispatch (its verify has no
|
|
347
|
-
// single external signature hash, so it never routes through the SIG_SCHEME/sigHash gate below).
|
|
348
244
|
var composite = compositeSig.COMPOSITE_ALGS[si.signatureAlgorithm.oid];
|
|
349
245
|
if (composite) return _verifyComposite(si, composite, content, eContentType, parsedCerts, csTarget, digestByName);
|
|
350
246
|
var scheme = SIG_SCHEME[si.signatureAlgorithm.name];
|
|
351
247
|
if (!scheme) return _pResolve({ ok: false, code: "cms/unsupported-algorithm", sid: si.sid, message: "unsupported signature algorithm " + JSON.stringify(si.signatureAlgorithm.name) });
|
|
352
248
|
var digestHash = SIG_HASH[si.digestAlgorithm.name];
|
|
353
|
-
// RSASSA-PSS takes its signature hash and salt length from the RSASSA-PSS-params, not the
|
|
354
|
-
// digestAlgorithm; a non-conformant parameter set is a fail-closed unsupported verdict.
|
|
355
249
|
var pss = scheme.kind === "rsapss" ? _resolvePss(si.signatureAlgorithm.parameters) : null;
|
|
356
250
|
if (scheme.kind === "rsapss" && !pss) return _pResolve({ ok: false, code: "cms/unsupported-algorithm", sid: si.sid, message: "unsupported or non-conformant RSASSA-PSS parameters (RFC 4055)" });
|
|
357
|
-
// signatureAlgorithm is outside the signed preimage, so a forbidden/missing parameters field
|
|
358
|
-
// (a present parameter on ECDSA/EdDSA, a non-NULL or absent one on RSA) must fail closed --
|
|
359
|
-
// otherwise it is a parser differential a caller trusting the verdict inherits (RFC 5754).
|
|
360
251
|
if (scheme.params && !_algParamsOk(scheme.params, si.signatureAlgorithm.parameters)) return _pResolve({ ok: false, code: "cms/unsupported-algorithm", sid: si.sid, message: "the " + si.signatureAlgorithm.name + " signature algorithm parameters must be " + (scheme.params === "null" ? "DER NULL (RFC 4055)" : "absent (RFC 5758/8410)") });
|
|
361
|
-
// The digestAlgorithm parameters, when present, MUST be a DER NULL (RFC 5754 sec. 2 accepts
|
|
362
|
-
// absent OR NULL; a present non-NULL is malformed and, being outside the signed preimage,
|
|
363
|
-
// must fail closed like the signatureAlgorithm parameters above). EXCEPT for an ML-DSA
|
|
364
|
-
// SignerInfo carrying no signed attributes: RFC 9882 sec. 3.3 says the whole digestAlgorithm field
|
|
365
|
-
// has no meaning there and MUST be ignored, so neither its parameters nor its name may reject.
|
|
366
252
|
var dp = si.digestAlgorithm.parameters;
|
|
367
253
|
var mldsaNoAttrs = scheme.kind === "mldsa" && !si.signedAttrsBytes;
|
|
368
254
|
if (!mldsaNoAttrs && dp !== null && dp !== undefined && !_isDerNull(dp)) return _pResolve({ ok: false, code: "cms/unsupported-algorithm", sid: si.sid, message: "the " + si.digestAlgorithm.name + " digest algorithm parameters must be absent or DER NULL (RFC 5754 sec. 2)" });
|
|
369
|
-
// A SHAKE digest algorithm identifier (id-shake128 / id-shake256) carries NO parameters at all --
|
|
370
|
-
// not even a DER NULL (RFC 8702 sec. 3.1) -- so for a one-shot PQC SignerInfo (ML-DSA per RFC 9882,
|
|
371
|
-
// SLH-DSA per RFC 9814 sec. 4, both citing RFC 8702) with signed attributes a present SHAKE
|
|
372
|
-
// parameter fails closed. The SHA-2 digests (id-sha256/384/512) follow RFC 5754, which requires a
|
|
373
|
-
// verifier to accept absent OR NULL (the generic rule above); the RFCs' omit-on-encode rule binds
|
|
374
|
-
// the signer, not the verifier, so a SHA-2 NULL is not tightened here.
|
|
375
255
|
if ((scheme.kind === "mldsa" || scheme.kind === "slhdsa") && si.signedAttrsBytes && (si.digestAlgorithm.name === "shake128" || si.digestAlgorithm.name === "shake256") && dp !== null && dp !== undefined) return _pResolve({ ok: false, code: "cms/unsupported-algorithm", sid: si.sid, message: "a SHAKE digestAlgorithm carries no parameters (RFC 8702 sec. 3.1)" });
|
|
376
256
|
var sigHash = pss ? pss.hash : (scheme.hash || digestHash);
|
|
377
|
-
// The signature hash is required for every non-EdDSA scheme (EdDSA hashes internally);
|
|
378
|
-
// the content digest is required whenever signed attributes are present, for every scheme
|
|
379
|
-
// (the message-digest attribute is computed under digestAlgorithm). Either gap is a
|
|
380
|
-
// fail-closed unsupported-algorithm verdict, never a foreign-domain throw from the digest.
|
|
381
257
|
if ((scheme.kind !== "eddsa" && scheme.kind !== "mldsa" && scheme.kind !== "slhdsa" && !sigHash) || (si.signedAttrsBytes && !_supportedDigest(si.digestAlgorithm.name))) return _pResolve({ ok: false, code: "cms/unsupported-algorithm", sid: si.sid, message: "unsupported digest algorithm " + JSON.stringify(si.digestAlgorithm.name) });
|
|
382
|
-
// ML-DSA (RFC 9882 sec. 3.3): with signed attributes present, the message-digest algorithm MUST
|
|
383
|
-
// meet the parameter set's security strength -- a below-strength digest is the weaker link and is
|
|
384
|
-
// rejected fail-closed (the RFC's "verifiers MAY reject", taken by the strict-verifier posture).
|
|
385
|
-
// With signed attributes absent the digestAlgorithm has no meaning (sec. 3.3) and is not checked.
|
|
386
258
|
if (scheme.kind === "mldsa" && si.signedAttrsBytes && !_mldsaDigestSuitable(scheme.name, si.digestAlgorithm.name)) return _pResolve({ ok: false, code: "cms/unsupported-algorithm", sid: si.sid, message: "the " + si.digestAlgorithm.name + " message digest is below the security strength of " + scheme.name + " (RFC 9882 sec. 3.3)" });
|
|
387
|
-
// RFC 9814 sec. 4: an SLH-DSA SignerInfo's message-digest algorithm is the one paired with the
|
|
388
|
-
// parameter set (the sec. 4 list, always at least twice the tree-hash size); the signer emits it
|
|
389
|
-
// and this strict verifier requires it, so a digest that does not match the set's paired hash fails
|
|
390
|
-
// closed -- otherwise a signer could compute the message-digest attribute under a weaker hash the
|
|
391
|
-
// set does not pair. Gated on signed attributes (absent -> the digest is not consulted, sec. 4).
|
|
392
259
|
if (scheme.kind === "slhdsa" && si.signedAttrsBytes && si.digestAlgorithm.name !== scheme.digest) return _pResolve({ ok: false, code: "cms/unsupported-algorithm", sid: si.sid, message: "SLH-DSA " + scheme.name + " requires the " + scheme.digest + " message digest (RFC 9814 sec. 4)" });
|
|
393
260
|
var signers = _findSignerCerts(si.sid, parsedCerts);
|
|
394
261
|
if (!signers.length) return _pResolve({ ok: false, code: "cms/signer-cert-not-found", sid: si.sid, message: "no certificate matches this SignerInfo's signer identifier" });
|
|
395
262
|
var sigBytes = _toBuf(si.signature, "the SignerInfo signature");
|
|
396
263
|
|
|
397
|
-
// `await`, not `.then`: see _verifyAgainstCandidates -- a streamed content's iterator could replace
|
|
398
|
-
// Promise.prototype.then to skip the signed-bytes computation and fabricate a passing verdict.
|
|
399
264
|
var signedBytes = await (csTarget != null ? _computeCountersigBytes(si, csTarget) : _computeSignedBytes(si, content, eContentType, digestByName));
|
|
400
265
|
if (signedBytes && signedBytes.mismatch) return { ok: false, code: signedBytes.mismatch.code, sid: si.sid, cert: signers[0].der, message: signedBytes.mismatch.message };
|
|
401
266
|
return _verifyAgainstCandidates(scheme, sigHash, sigBytes, signedBytes, si.sid, signers, pss ? pss.saltLength : 0, scheme.sameKeyOid ? oid.byName(si.signatureAlgorithm.name) : null);
|
|
402
267
|
}
|
|
403
268
|
|
|
404
|
-
// The exact bytes a countersignature covers (RFC 5652 sec. 11.4): the countersigned SignerInfo's
|
|
405
|
-
// signature value octets (`csTarget`). With signed attributes present, the message-digest attribute
|
|
406
|
-
// MUST equal digest(csTarget) under the countersignature's own digestAlgorithm and the signature
|
|
407
|
-
// covers the re-tagged SignedAttributes ([0] -> universal SET OF) -- the identical transform the
|
|
408
|
-
// top-level signer uses, minus the content-type attribute (which sec. 11.4 FORBIDS). Resolves the
|
|
409
|
-
// signed bytes, or { mismatch } when the message-digest disagrees.
|
|
410
269
|
async function _computeCountersigBytes(si, csTarget) {
|
|
411
|
-
|
|
412
|
-
// below dispatches through no caller-replaceable Promise.prototype.then (see _verifyAgainstCandidates).
|
|
413
|
-
if (!si.signedAttrsBytes) return csTarget; // no signed attributes: sign over the target signature octets directly
|
|
270
|
+
if (!si.signedAttrsBytes) return csTarget;
|
|
414
271
|
var reTagged = intrinsic.bufferFrom(si.signedAttrsBytes);
|
|
415
|
-
reTagged[0] = 0x31;
|
|
272
|
+
reTagged[0] = 0x31;
|
|
416
273
|
var attrs;
|
|
417
274
|
try {
|
|
418
275
|
attrs = _decodeSignedAttrs(reTagged);
|
|
419
276
|
} catch (e) { if (e instanceof CmsError) throw e; throw _err("cms/bad-signed-attrs", "the countersignature signedAttrs is not a valid SET OF Attribute", e); }
|
|
420
|
-
// sec. 11.4: a content-type attribute MUST NOT appear (the parser already rejects it at parse, but
|
|
421
|
-
// verify never relies on the parse alone: a parsed-object input could carry one).
|
|
422
277
|
if (intrinsic.filter(attrs, function (a) { return a.type === OID_CONTENT_TYPE; }).length) throw _err("cms/misplaced-attr", "a countersignature must not carry a content-type attribute (RFC 5652 sec. 11.4)");
|
|
423
278
|
var mdAttr = intrinsic.filter(attrs, function (a) { return a.type === OID_MESSAGE_DIGEST; })[0];
|
|
424
279
|
if (!mdAttr || mdAttr.values.length !== 1) throw _err("cms/bad-signed-attrs", "a countersignature's signedAttrs must carry exactly one message-digest attribute (RFC 5652 sec. 11.4)");
|
|
@@ -431,19 +286,8 @@ async function _computeCountersigBytes(si, csTarget) {
|
|
|
431
286
|
return reTagged;
|
|
432
287
|
}
|
|
433
288
|
|
|
434
|
-
// The exact bytes the signature covers (RFC 5652 sec. 5.4), shared by the classical and composite
|
|
435
|
-
// verify paths so both bind the message-digest / content-type checks to the VERIFIED preimage (not
|
|
436
|
-
// the caller-mutable parsed si.signedAttrs). Resolves the signed bytes, or { mismatch } when a
|
|
437
|
-
// content-type / message-digest attribute disagrees.
|
|
438
289
|
async function _computeSignedBytes(si, content, eContentType, digestByName) {
|
|
439
|
-
// async, not `_pResolve().then`: a synchronous throw still becomes a rejection, and every digest step
|
|
440
|
-
// dispatches through no caller-replaceable Promise.prototype.then (see _verifyAgainstCandidates).
|
|
441
290
|
if (!si.signedAttrsBytes) {
|
|
442
|
-
// No signed attributes: the signature is over the content itself (RFC 5652 sec. 5.4). A streamed
|
|
443
|
-
// content is not retained, so there are no content bytes to verify that signature against; this
|
|
444
|
-
// is a per-signer verdict, not a whole-message abort, so the other signers of a mixed message
|
|
445
|
-
// still verify (the same shape the buffered path gives every per-signer condition). `digestByName`
|
|
446
|
-
// is the streaming marker: an object (possibly empty) when streamed, null when buffered.
|
|
447
291
|
if (digestByName) {
|
|
448
292
|
return { mismatch: {
|
|
449
293
|
code: "cms/streamed-content-unverifiable",
|
|
@@ -451,12 +295,6 @@ async function _computeSignedBytes(si, content, eContentType, digestByName) {
|
|
|
451
295
|
"verified against a streamed content, whose bytes are not retained; pass opts.content as a Buffer",
|
|
452
296
|
} };
|
|
453
297
|
}
|
|
454
|
-
// Signing content directly is also exactly what a stripped-attributes forgery looks like, because
|
|
455
|
-
// a CMS signature does not commit to whether attributes were present -- so a signature made over a
|
|
456
|
-
// SignedAttributes block re-presented as one made over content verifies, and with no attributes
|
|
457
|
-
// there is no message-digest or content-type attribute left to disagree. Refuse the shape: content
|
|
458
|
-
// that parses as a SignedAttributes block cannot be told apart from that forgery, and it is not
|
|
459
|
-
// the verifier's place to guess which one it is holding.
|
|
460
298
|
if (cms.looksLikeSignedAttributes(content)) {
|
|
461
299
|
return { mismatch: {
|
|
462
300
|
code: "cms/ambiguous-content",
|
|
@@ -467,19 +305,12 @@ async function _computeSignedBytes(si, content, eContentType, digestByName) {
|
|
|
467
305
|
}
|
|
468
306
|
return content;
|
|
469
307
|
}
|
|
470
|
-
// With signed attributes: decode them from the EXACT bytes the signature covers, namely the
|
|
471
|
-
// SignedAttributes SET OF with the on-wire [0] IMPLICIT tag replaced by a universal SET OF
|
|
472
|
-
// (RFC 5652 sec. 5.4). The content-type / message-digest checks then bind the same bytes
|
|
473
|
-
// that are verified. Reading the caller-mutable parsed si.signedAttrs instead would let a
|
|
474
|
-
// parsed-object input desync the checked attributes from the verified preimage.
|
|
475
308
|
var reTagged = intrinsic.bufferFrom(si.signedAttrsBytes);
|
|
476
|
-
reTagged[0] = 0x31;
|
|
309
|
+
reTagged[0] = 0x31;
|
|
477
310
|
var attrs;
|
|
478
311
|
try {
|
|
479
312
|
attrs = _decodeSignedAttrs(reTagged);
|
|
480
313
|
} catch (e) { if (e instanceof CmsError) throw e; throw _err("cms/bad-signed-attrs", "signedAttrs is not a valid SET OF Attribute", e); }
|
|
481
|
-
// The content-type attribute MUST be present, single-valued, and equal the eContentType
|
|
482
|
-
// (RFC 5652 sec. 5.3).
|
|
483
314
|
var ctAttr = intrinsic.filter(attrs, function (a) { return a.type === OID_CONTENT_TYPE; });
|
|
484
315
|
if (ctAttr.length !== 1 || ctAttr[0].values.length !== 1) throw _err("cms/bad-signed-attrs", "signedAttrs must carry exactly one content-type attribute (RFC 5652 sec. 5.3)");
|
|
485
316
|
var ctOid;
|
|
@@ -487,72 +318,39 @@ async function _computeSignedBytes(si, content, eContentType, digestByName) {
|
|
|
487
318
|
ctOid = asn1.read.oid(ctAttr[0].values[0]);
|
|
488
319
|
} catch (e) { throw _err("cms/bad-signed-attrs", "the content-type attribute value is not an OBJECT IDENTIFIER", e); }
|
|
489
320
|
if (ctOid !== eContentType) return { mismatch: { code: "cms/content-type-mismatch", message: "the content-type signed attribute does not match the SignedData eContentType (RFC 5652 sec. 5.3)" } };
|
|
490
|
-
// The message-digest attribute MUST be present, single-valued, and equal the digest of the
|
|
491
|
-
// content (RFC 5652 sec. 5.4).
|
|
492
321
|
var mdAttr = intrinsic.filter(attrs, function (a) { return a.type === OID_MESSAGE_DIGEST; })[0];
|
|
493
322
|
if (!mdAttr || mdAttr.values.length !== 1) throw _err("cms/bad-signed-attrs", "signedAttrs must carry exactly one message-digest attribute (RFC 5652 sec. 5.4)");
|
|
494
323
|
var declared;
|
|
495
324
|
try {
|
|
496
325
|
declared = asn1.read.octetString(mdAttr.values[0]);
|
|
497
326
|
} catch (e) { throw _err("cms/bad-signed-attrs", "the message-digest attribute value is not an OCTET STRING", e); }
|
|
498
|
-
// Over a buffered content the digest is computed here; over a streamed content it was computed once,
|
|
499
|
-
// incrementally, before any signer was verified, and looked up by algorithm.
|
|
500
327
|
var d = digestByName ? digestByName[si.digestAlgorithm.name] : await _computeDigest(si.digestAlgorithm.name, content);
|
|
501
|
-
// Captured equality, not d.equals: over a streamed content the untrusted iterator's next() ran during
|
|
502
|
-
// the digest await and could have replaced Buffer.prototype.equals with one that always answers true,
|
|
503
|
-
// passing this message-digest check for content that was never signed (CWE-347).
|
|
504
328
|
if (!intrinsic.bufferEquals(d, declared)) return { mismatch: { code: "cms/message-digest-mismatch", message: "the message-digest attribute does not match the content digest" } };
|
|
505
329
|
return reTagged;
|
|
506
330
|
}
|
|
507
331
|
|
|
508
|
-
// Verify a composite ML-DSA SignerInfo (draft-ietf-lamps-cms-composite-sigs). The composite
|
|
509
|
-
// verify engine lives in composite-sig.js (shared with certification-path validation); this branch
|
|
510
|
-
// enforces the CMS-specific rules the X.509 path does not: params-absent on the signatureAlgorithm
|
|
511
|
-
// (sec. 3.4, defense-in-depth over the parse-time _PARAMS_ABSENT gate), the unsupported-arm refusal,
|
|
512
|
-
// and the sec. 3.4/sec. 5 coherence gate binding the SignerInfo digestAlgorithm to the arm's Table-1
|
|
513
|
-
// pre-hash. It reuses the sec. 5.4 preimage + message-digest binding UNCHANGED, then dispatches
|
|
514
|
-
// compositeVerify per candidate certificate (both components must verify, with no AND->OR
|
|
515
|
-
// downgrade).
|
|
516
332
|
async function _verifyComposite(si, comp, content, eContentType, parsedCerts, csTarget, digestByName) {
|
|
517
333
|
if (si.signatureAlgorithm.parameters !== null && si.signatureAlgorithm.parameters !== undefined) {
|
|
518
334
|
return _pResolve({ ok: false, code: "cms/unsupported-algorithm", sid: si.sid, message: "the composite signatureAlgorithm parameters must be absent (draft-ietf-lamps-cms-composite-sigs sec. 3.4)" });
|
|
519
335
|
}
|
|
520
|
-
// The 2 arms Node's WebCrypto surface cannot verify (the brainpool curves) fail closed --
|
|
521
|
-
// never a silent single-component accept (the AND->OR downgrade this feature prevents).
|
|
522
336
|
if (comp.trad.unsupported) {
|
|
523
337
|
return _pResolve({ ok: false, code: "cms/unsupported-algorithm", sid: si.sid, message: "composite " + comp.name + ": " + comp.trad.unsupported });
|
|
524
338
|
}
|
|
525
|
-
// draft sec. 3.4: for a composite SignerInfo the digestAlgorithm parameters MUST be OMITTED
|
|
526
|
-
// (id-sha256/id-sha512 and id-shake256). That is stricter than the generic RFC 5754 absent-OR-NULL
|
|
527
|
-
// rule (which the classical/ML-DSA paths follow), because the composite draft explicitly requires the
|
|
528
|
-
// field omitted. A present parameter -- even a DER NULL -- is non-conformant and fails closed
|
|
529
|
-
// (it is outside the signed preimage, so a parser differential must not verify).
|
|
530
339
|
var dp = si.digestAlgorithm.parameters;
|
|
531
340
|
if (dp !== null && dp !== undefined) {
|
|
532
341
|
return _pResolve({ ok: false, code: "cms/unsupported-algorithm", sid: si.sid, message: "the composite " + si.digestAlgorithm.name + " digestAlgorithm parameters must be omitted (draft-ietf-lamps-cms-composite-sigs sec. 3.4)" });
|
|
533
342
|
}
|
|
534
|
-
// sec. 3.4 / sec. 5 (the one CMS-specific MUST the X.509 path does not cover): the SignerInfo
|
|
535
|
-
// digestAlgorithm MUST equal the arm's pre-hash (Table 1) -- compositeVerify takes the pre-hash
|
|
536
|
-
// from the OID-keyed descriptor, so a disagreeing digestAlgorithm would recompute the
|
|
537
|
-
// message-digest attribute under the WRONG algorithm. The sec. 5 SHOULD-reject is taken
|
|
538
|
-
// fail-closed; the MAY-verify-anyway leniency is deliberately not taken.
|
|
539
343
|
if (si.digestAlgorithm.name !== comp.phCms) {
|
|
540
344
|
return _pResolve({ ok: false, code: "cms/unsupported-algorithm", sid: si.sid, message: "the SignerInfo digestAlgorithm " + JSON.stringify(si.digestAlgorithm.name) + " is not the composite " + comp.name + " pre-hash " + JSON.stringify(comp.phCms) + " (draft-ietf-lamps-cms-composite-sigs sec. 3.4)" });
|
|
541
345
|
}
|
|
542
346
|
var signers = _findSignerCerts(si.sid, parsedCerts);
|
|
543
347
|
if (!signers.length) return _pResolve({ ok: false, code: "cms/signer-cert-not-found", sid: si.sid, message: "no certificate matches this SignerInfo's signer identifier" });
|
|
544
348
|
var sigBytes = _toBuf(si.signature, "the SignerInfo signature");
|
|
545
|
-
// `await`, not `.then`: see _verifyAgainstCandidates.
|
|
546
349
|
var signedBytes = await (csTarget != null ? _computeCountersigBytes(si, csTarget) : _computeSignedBytes(si, content, eContentType, digestByName));
|
|
547
350
|
if (signedBytes && signedBytes.mismatch) return { ok: false, code: signedBytes.mismatch.code, sid: si.sid, cert: signers[0].der, message: signedBytes.mismatch.message };
|
|
548
351
|
return _verifyCompositeAgainstCandidates(comp, sigBytes, signedBytes, si.sid, signers, si.signatureAlgorithm.oid);
|
|
549
352
|
}
|
|
550
353
|
|
|
551
|
-
// Dispatch compositeVerify against each matching candidate; the signer is valid iff one candidate
|
|
552
|
-
// verifies both components. A candidate whose SPKI composite OID != the SignerInfo signatureAlgorithm
|
|
553
|
-
// OID is skipped with a precise verdict (RFC 9814 sec. 4 key<->signature agreement); a candidate that
|
|
554
|
-
// returns a structural fault code (bad split length, unsupported) falls through, its code surfaced if
|
|
555
|
-
// none verifies. A clean both-components-checked failure (the AND-downgrade) is a code-less false.
|
|
556
354
|
async function _verifyCompositeAgainstCandidates(comp, sigBytes, signedBytes, sid, candidates, expectedKeyOid) {
|
|
557
355
|
var lastErr = null;
|
|
558
356
|
for (var idx = 0; idx < candidates.length; idx++) {
|
|
@@ -561,7 +359,6 @@ async function _verifyCompositeAgainstCandidates(comp, sigBytes, signedBytes, si
|
|
|
561
359
|
lastErr = _err("cms/unsupported-algorithm", "the signer certificate public-key algorithm does not match the SignerInfo signatureAlgorithm");
|
|
562
360
|
continue;
|
|
563
361
|
}
|
|
564
|
-
// `await`, not `.then`: see _verifyAgainstCandidates.
|
|
565
362
|
var r = await compositeSig.compositeVerify(c.cert.subjectPublicKeyInfo.bytes, sigBytes, signedBytes, comp, CmsError, "cms/unsupported-algorithm", "cms/bad-signature");
|
|
566
363
|
if (r.ok === true) return { ok: true, sid: sid, cert: c.der };
|
|
567
364
|
if (r.code) lastErr = (r.error instanceof CmsError) ? r.error : _err(r.code, r.error && r.error.message ? r.error.message : "the composite signature could not be evaluated");
|
|
@@ -570,51 +367,25 @@ async function _verifyCompositeAgainstCandidates(comp, sigBytes, signedBytes, si
|
|
|
570
367
|
: { ok: false, sid: sid, cert: candidates[0].der };
|
|
571
368
|
}
|
|
572
369
|
|
|
573
|
-
// The decoded unsigned attributes of a SignerInfo, surfaced UNAUTHENTICATED (they are outside
|
|
574
|
-
// the signature) with each OID resolved to a name, so a caller can read an attached RFC 3161
|
|
575
|
-
// timestamp token or inspect a countersignature attribute. They are never counted toward a
|
|
576
|
-
// signer's ok / res.valid.
|
|
577
370
|
function _surfaceUnsignedAttrs(si) {
|
|
578
371
|
return intrinsic.map(si.unsignedAttrs || [], function (a) {
|
|
579
372
|
return { type: a.type, typeName: oid.name(a.type) || null, values: a.values };
|
|
580
373
|
});
|
|
581
374
|
}
|
|
582
375
|
|
|
583
|
-
// The AUTHENTICATED attributes, surfaced per signer so a caller reads a signed attribute -- a content
|
|
584
|
-
// type, a signing time, a protocol's own transaction state (SCEP's messageType / nonces) -- BOUND to
|
|
585
|
-
// the verified signature rather than re-parsed from the untrusted message, where an attacker could
|
|
586
|
-
// present attributes the signature never covered. The signature is over the SignedAttributes these
|
|
587
|
-
// decode from, so read them only where the signer's `ok` is true.
|
|
588
376
|
function _surfaceSignedAttrs(si) {
|
|
589
377
|
return intrinsic.map(si.signedAttrs || [], function (a) {
|
|
590
378
|
return { type: a.type, typeName: oid.name(a.type) || null, values: a.values };
|
|
591
379
|
});
|
|
592
380
|
}
|
|
593
381
|
|
|
594
|
-
// Verify every countersignature attached to `si` (RFC 5652 sec. 11.4): each id-countersignature
|
|
595
|
-
// value is a SignerInfo over `si`'s signature octets. Returns per-countersignature verdicts; a
|
|
596
|
-
// countersignature's own countersignatures verify over its signature octets (recursive). A
|
|
597
|
-
// countersignature that fails to verify is surfaced ok:false. It is never silently dropped, and
|
|
598
|
-
// never allowed to change the primary verdict. A countersignature value that is not a well-formed
|
|
599
|
-
// SignerInfo does not reach here at all: the decoder validates every id-countersignature value by
|
|
600
|
-
// its content, so such a message is refused whole. The recursion terminates because it only walks
|
|
601
|
-
// the FINITE parsed structure: each nested countersignature value is a sub-encoding of its parent,
|
|
602
|
-
// and the strict decoder already bounds total nesting by C.LIMITS.DER_MAX_DEPTH at parse
|
|
603
|
-
// (CWE-834/770), so a hostile deep chain fails closed before verify.
|
|
604
382
|
async function _verifyCountersignatures(si, parsedCerts) {
|
|
605
|
-
// si.signature is always a Buffer from the strict parser (and from walkCountersignature on the
|
|
606
|
-
// recursive path), so _toBuf is a pass-through here. The SignerInfo is already well-formed.
|
|
607
383
|
var targetSig = _toBuf(si.signature, "the countersigned signature");
|
|
608
384
|
var values = [];
|
|
609
|
-
// Captured enumeration: over a streamed content the untrusted iterator's next() ran during the
|
|
610
|
-
// digest await and could replace Array.prototype.forEach / push to drop a failing countersignature.
|
|
611
385
|
intrinsic.forEach(si.unsignedAttrs || [], function (a) {
|
|
612
386
|
if (a.type !== OID_COUNTERSIGNATURE) return;
|
|
613
387
|
intrinsic.forEach(a.values, function (v) { intrinsic.push(values, v && v.bytes ? v.bytes : v); });
|
|
614
388
|
});
|
|
615
|
-
// Sequential, started only as awaited (see _verify): not Promise.all (its element handling dispatches
|
|
616
|
-
// through the replaceable Promise.prototype.then), and not started-all-then-awaited (an early
|
|
617
|
-
// rejection would leave a later one unobserved).
|
|
618
389
|
var out = [];
|
|
619
390
|
for (var _cp = 0; _cp < values.length; _cp++) intrinsic.push(out, await _verifyOneCountersig(values[_cp], targetSig, parsedCerts));
|
|
620
391
|
return out;
|
|
@@ -622,17 +393,8 @@ async function _verifyCountersignatures(si, parsedCerts) {
|
|
|
622
393
|
|
|
623
394
|
async function _verifyOneCountersig(vDer, targetSig, parsedCerts) {
|
|
624
395
|
var csSi;
|
|
625
|
-
// Coverage residual, and deliberately kept. Every value reaching here was already walked by the
|
|
626
|
-
// decoder, which validates an id-countersignature by content and not by the attribute type. A
|
|
627
|
-
// malformed one therefore refused the whole message before verify was entered, so this catch
|
|
628
|
-
// cannot be reached through the public path. That is settled behavior: a message the decoder
|
|
629
|
-
// has found to be malformed has no sound remainder to report a verdict over, so it is refused
|
|
630
|
-
// whole instead of surfaced as one failed countersignature.
|
|
631
|
-
// cms-verify.test.js pins that through the shipped verbs; this stays because the walk is also
|
|
632
|
-
// reachable recursively and a backstop that returns a NEGATIVE verdict costs nothing.
|
|
633
396
|
try { csSi = cms.walkCountersignature(asn1.decode(intrinsic.isBuffer(vDer) ? vDer : intrinsic.bufferFrom(vDer))); }
|
|
634
397
|
catch (e) { return { ok: false, code: (e instanceof CmsError ? e.code : "cms/bad-countersignature"), message: e && e.message }; }
|
|
635
|
-
// `await`, not `.then`: see _verifyAgainstCandidates.
|
|
636
398
|
var verdict = await _verifyOne(csSi, targetSig, null, parsedCerts, targetSig);
|
|
637
399
|
var nested = await _verifyCountersignatures(csSi, parsedCerts);
|
|
638
400
|
var node = { ok: verdict.ok, sid: verdict.sid, cert: verdict.cert, digestAlgorithm: csSi.digestAlgorithm.name };
|
|
@@ -715,29 +477,13 @@ async function _verifyOneCountersig(vDer, targetSig, parsedCerts) {
|
|
|
715
477
|
* res.valid; // boolean
|
|
716
478
|
* res.signers[0].ok; // per-signer verdict
|
|
717
479
|
*/
|
|
718
|
-
// A private copy of the input when -- and only when -- it is memory the caller can
|
|
719
|
-
// still write to. Anything else (a PEM string, and whatever else the parser accepts)
|
|
720
|
-
// is returned untouched so this cannot narrow the input contract of the parse it
|
|
721
|
-
// feeds; the parser stays the one place that decides what an acceptable input is.
|
|
722
480
|
function _snapshotIfBytes(input, label) {
|
|
723
|
-
// Every byte form the parser accepts -- an ArrayBuffer or a DataView reaches the
|
|
724
|
-
// decoder as readily as a Buffer, and leaving those aliased would reopen the
|
|
725
|
-
// window for exactly the inputs that came in by the wider door.
|
|
726
481
|
if (guard.bytes.isByteSource(input)) {
|
|
727
482
|
return guard.bytes.snapshotSource(input, CmsError, "cms/bad-input", label);
|
|
728
483
|
}
|
|
729
484
|
return input;
|
|
730
485
|
}
|
|
731
486
|
|
|
732
|
-
// For a streamed (async-iterable) detached content: hash the stream ONCE, incrementally, under every
|
|
733
|
-
// distinct SUPPORTED SignerInfo digest algorithm, returning { <digestName>: Buffer } (streaming), or
|
|
734
|
-
// null (not streaming; digest per-signer over the buffered content). No per-signer condition aborts
|
|
735
|
-
// the whole message here, matching the buffered path's per-signer verdicts: a content-only signer
|
|
736
|
-
// (no signed attributes, whose signature is over content a stream does not retain) and an unsupported
|
|
737
|
-
// digest each contribute no name and are verdicted per-signer by _verifyOne / _computeSignedBytes.
|
|
738
|
-
// An empty map is still the streaming marker, so those per-signer verdicts are produced. `streamSource`
|
|
739
|
-
// is the lazy stream classified at the door (null when not streaming, buffered per-signer); its
|
|
740
|
-
// iterator is acquired only if a digest is actually needed, so an all-content-only message never opens it.
|
|
741
487
|
async function _resolveContentDigests(parsed, streamSource) {
|
|
742
488
|
if (streamSource == null) return null;
|
|
743
489
|
var names = [], seen = {};
|
|
@@ -745,18 +491,11 @@ async function _resolveContentDigests(parsed, streamSource) {
|
|
|
745
491
|
var dn = si.digestAlgorithm.name;
|
|
746
492
|
if (si.signedAttrsBytes && _supportedDigest(dn) && !seen[dn]) { seen[dn] = 1; intrinsic.push(names, dn); }
|
|
747
493
|
});
|
|
748
|
-
// No signer needs a streamed digest (all content-only or unsupported): nothing to hash, but the
|
|
749
|
-
// empty map still marks streaming mode so each signer gets its per-signer verdict.
|
|
750
494
|
if (!names.length) return {};
|
|
751
|
-
// `await`, not `.then`: this is where the UNTRUSTED stream is consumed, so the very next promise step
|
|
752
|
-
// must not dispatch through a Promise.prototype.then the stream may have replaced.
|
|
753
495
|
var ds;
|
|
754
496
|
try {
|
|
755
497
|
ds = await subtle.digestStream(intrinsic.map(names, function (n) { return DIGEST_HASH[n]; }), streamSource);
|
|
756
498
|
} catch (e) {
|
|
757
|
-
// The engine reports malformed streamed content in its own domain; report it in this verb's via the
|
|
758
|
-
// shared guard, rather than leaking the engine code. The digest algorithm names are this module's own,
|
|
759
|
-
// so a webcrypto/syntax here is never an algorithm fault.
|
|
760
499
|
guard.bytes.translateStreamError(e, _err, "cms/bad-input");
|
|
761
500
|
}
|
|
762
501
|
var byName = {};
|
|
@@ -764,56 +503,20 @@ async function _resolveContentDigests(parsed, streamSource) {
|
|
|
764
503
|
return byName;
|
|
765
504
|
}
|
|
766
505
|
|
|
767
|
-
// Every option pki.cms.verify reads. Adding one here is the only way to make it accepted, so a
|
|
768
|
-
// capability cannot arrive with its option silently ignored at this boundary.
|
|
769
506
|
var _VERIFY_OPTS = { certs: 1, content: 1, trustAnchors: 1, time: 1, requiredEku: 1, checkPurpose: 1 };
|
|
770
507
|
|
|
771
|
-
// Documented `-> Promise`, so a fault leaves as a REJECTION (guard-async). The checks below stay
|
|
772
|
-
// synchronous -- they read a caller's mutable options and bytes, and resolving those before any turn
|
|
773
|
-
// passes is what stops a value being swapped between the check and the use.
|
|
774
508
|
function verify(input, opts) {
|
|
775
509
|
return guard.async.deferred(function () { return _verify(input, opts); });
|
|
776
510
|
}
|
|
777
511
|
|
|
778
512
|
async function _verify(input, opts) {
|
|
779
513
|
opts = guard.identifier.optionsObject(opts, _err, "cms/bad-input", "pki.cms.verify options");
|
|
780
|
-
// An unrecognized option is refused, not swallowed. This is what kept the missing trust seam
|
|
781
|
-
// silent: a caller writing `trustAnchors` before it existed -- or `trustAnchor` now -- got a
|
|
782
|
-
// verdict that looked anchored and was not. The guard rejects through a (code, message) FACTORY
|
|
783
|
-
// and tests membership with hasOwnProperty, so the permitted set is a lookup object.
|
|
784
514
|
guard.identifier.assertKnownKeys(opts, _VERIFY_OPTS, _err, "cms/bad-input", "pki.cms.verify has an unknown option ");
|
|
785
|
-
// Handed MUTABLE BYTES, parse a private copy. This function decodes synchronously
|
|
786
|
-
// and checks signatures in a later promise turn, so without the copy every range
|
|
787
|
-
// the parse surfaced -- the signed content above all -- stays a view into the
|
|
788
|
-
// caller's memory across that await, and a buffer rewritten in the gap would leave
|
|
789
|
-
// the result describing one message while the signature was checked over another.
|
|
790
|
-
// Re-derived from the bytes the parser read, never trusted as the object it arrives as. A
|
|
791
|
-
// SignedData's meaning is a signature over a byte range, but a parsed one presents that range
|
|
792
|
-
// (`signedAttrsBytes`, or the encapsulated `eContent` when there are no signed attributes), the
|
|
793
|
-
// signature, the algorithms and the certificates as SEPARATE properties. Keep a genuine signer's
|
|
794
|
-
// signature and signed attributes and put different content beside them and every part of this
|
|
795
|
-
// check passes for a message that signer never signed -- the forgery this verb's own block claims
|
|
796
|
-
// to defend (CWE-347). A duck-type test on `signerInfos` cannot see that, because every field is
|
|
797
|
-
// individually well-formed; only re-deriving them all from one byte string can.
|
|
798
515
|
var parsed = guard.parsed.acceptDerived(input, "cms", function (bytes) {
|
|
799
516
|
return cms.parse(_snapshotIfBytes(bytes, "pki.cms.verify"));
|
|
800
517
|
}, _err, "cms/bad-input", "the SignedData");
|
|
801
518
|
if (!Array.isArray(parsed.signerInfos)) throw _err("cms/bad-input", "input is not a CMS SignedData");
|
|
802
|
-
// The trust configuration is snapshotted HERE, before opts.content is read at all. A streamed
|
|
803
|
-
// content's Symbol.asyncIterator accessor runs the moment asyncStreamOf reads it, one turn before a
|
|
804
|
-
// snapshot taken further down would run; from that accessor a caller can replace a global the snapshot
|
|
805
|
-
// itself relies on -- the Date it defaults the validation instant from, or the Array.isArray /
|
|
806
|
-
// Array.prototype.map / slice it copies the anchors and requiredEku with -- and steer the trust
|
|
807
|
-
// decision. Taken before the content object is touched, every value the snapshot reads comes from a
|
|
808
|
-
// pristine global. It also stays caller-owned across the later await: the array cannot be re-pointed,
|
|
809
|
-
// an anchor's DER rewritten, or the instant moved, once copied -- the same defense the input and the
|
|
810
|
-
// certificates already have.
|
|
811
519
|
var trustCfg = _snapshotTrust(opts);
|
|
812
|
-
// Pre-parse every candidate certificate (embedded + caller-supplied) once, BEFORE opts.content is
|
|
813
|
-
// touched. The caller-supplied set (opts.certs) is read here; a streamed content's accessor -- run the
|
|
814
|
-
// moment asyncStreamOf reads it below -- could otherwise mutate that array, or replace the
|
|
815
|
-
// Array.prototype.forEach this loop uses, and change which certificates the signer match and the trust
|
|
816
|
-
// build see. Taken first, the candidate pool is the one the caller passed.
|
|
817
520
|
var parsedCerts = [];
|
|
818
521
|
(parsed.certificates || []).forEach(function (c) { _addCert(parsedCerts, c && c.bytes ? c.bytes : c); });
|
|
819
522
|
(opts.certs || []).forEach(function (c) { _addCert(parsedCerts, c); });
|
|
@@ -821,74 +524,31 @@ async function _verify(input, opts) {
|
|
|
821
524
|
var streamSource = null;
|
|
822
525
|
if (content == null) {
|
|
823
526
|
if (opts.content == null) throw _err("cms/detached-content-required", "this SignedData is detached; opts.content (the external content) is required");
|
|
824
|
-
// A streamed (async-iterable) detached content is classified by asyncStreamOf, which reads
|
|
825
|
-
// Symbol.asyncIterator a single time and returns a LAZY stream whose iterator is acquired only when
|
|
826
|
-
// it is driven below, after the per-signer digest algorithms are known, so a single-use stream
|
|
827
|
-
// serves every signer in one pass and a message needing no streamed digest never acquires it. It is
|
|
828
|
-
// not snapshotted: a stream has no bytes to copy, and it is consumed exactly once. A byte source (or
|
|
829
|
-
// any non-iterable) yields null and is buffered.
|
|
830
527
|
streamSource = guard.bytes.asyncStreamOf(opts.content);
|
|
831
528
|
if (streamSource == null) {
|
|
832
|
-
// Copied, not aliased: the digest over this content is computed in a later promise turn,
|
|
833
|
-
// and a detached signature's content is the whole of what the signature speaks about. A
|
|
834
|
-
// caller who rewrote the buffer in that gap would have the signature checked against the
|
|
835
|
-
// original bytes while holding the replacement, and the verdict would report a valid
|
|
836
|
-
// signature over content that was never signed.
|
|
837
529
|
content = _toBuf(_snapshotIfBytes(opts.content, "opts.content"), "opts.content");
|
|
838
530
|
}
|
|
839
531
|
} else if (opts.content != null) {
|
|
840
|
-
// A caller supplied external content AND the SignedData is attached. A redundant COPY that equals
|
|
841
|
-
// the encapsulated content is fine; content that DIFFERS is a substitution trap -- silently
|
|
842
|
-
// preferring the embedded copy would let the caller believe it verified opts.content when the
|
|
843
|
-
// signature covers different embedded bytes. Fail closed on a mismatch.
|
|
844
532
|
if (Buffer.compare(_toBuf(opts.content, "opts.content"), Buffer.from(content)) !== 0) {
|
|
845
533
|
throw _err("cms/content-conflict", "opts.content disagrees with this SignedData's own encapsulated content (a detached signature must omit eContent)");
|
|
846
534
|
}
|
|
847
535
|
}
|
|
848
536
|
var eContentType = parsed.encapContentInfo.eContentType;
|
|
849
|
-
// `await` throughout the aggregation, never `.then`: the streamed content is consumed inside
|
|
850
|
-
// _resolveContentDigests, so from here on every promise step must dispatch through no
|
|
851
|
-
// caller-replaceable Promise.prototype.then. Captured enumeration (intrinsic.map) guards
|
|
852
|
-
// Array.prototype.map; awaiting each promise (never Promise.all) guards the chaining (CWE-347).
|
|
853
537
|
var digestByName = await _resolveContentDigests(parsed, streamSource);
|
|
854
|
-
// Each signer is verified SEQUENTIALLY -- started only as it is awaited. NOT Promise.all: its
|
|
855
|
-
// spec-internal element handling calls the caller-replaceable Promise.prototype.then, so a streamed
|
|
856
|
-
// content that replaced it could fabricate a resolved `{ ok: true }` for every signer; await
|
|
857
|
-
// dispatches through no such method. And NOT started-all-then-awaited-each: an earlier signer's
|
|
858
|
-
// rejection would leave a later one's rejection unobserved, which Node can escalate to a crash.
|
|
859
|
-
// Per-signer work is small, so the sequential cost is negligible.
|
|
860
538
|
var signers = [];
|
|
861
539
|
for (var _sp = 0; _sp < parsed.signerInfos.length; _sp++) {
|
|
862
540
|
var siOne = parsed.signerInfos[_sp];
|
|
863
|
-
// res.valid reflects only the PRIMARY signerInfos; a countersignature / unsigned attribute is
|
|
864
|
-
// outside the signature and NEVER flips the top-level verdict. Both are surfaced per signer.
|
|
865
541
|
var verdict = await _verifyOne(siOne, content, eContentType, parsedCerts, null, digestByName);
|
|
866
542
|
verdict.countersignatures = await _verifyCountersignatures(siOne, parsedCerts);
|
|
867
543
|
verdict.unsignedAttrs = _surfaceUnsignedAttrs(siOne);
|
|
868
|
-
// Whether THIS signer signed attributes or signed the content directly. The two are different
|
|
869
|
-
// claims -- attributes bind a content type and a signing time alongside the digest, and content-only
|
|
870
|
-
// binds nothing but the bytes -- and RFC 5652 lets a message carry one signer of each. A caller
|
|
871
|
-
// whose profile requires attributes (RFC 8551 S/MIME does) can only enforce it if the verdict says
|
|
872
|
-
// which they got.
|
|
873
544
|
verdict.signedAttributesPresent = !!siOne.signedAttrsBytes;
|
|
874
|
-
// The authenticated attributes themselves, bound to this signer's verdict (read only when ok).
|
|
875
545
|
verdict.signedAttributes = _surfaceSignedAttrs(siOne);
|
|
876
|
-
// The SignerInfo digestAlgorithm on the primary signer node (the countersignature node already
|
|
877
|
-
// carries it), so a caller reading a signer's verdict learns the digest without re-parsing.
|
|
878
546
|
verdict.digestAlgorithm = siOne.digestAlgorithm.name;
|
|
879
547
|
intrinsic.push(signers, verdict);
|
|
880
548
|
}
|
|
881
|
-
// The content type travels with the verdict. An operator applying a policy of their own -- "I only
|
|
882
|
-
// accept id-data", or a profile that names its own type -- otherwise had to parse the message a
|
|
883
|
-
// second time to learn it, and a check that needs a second parse is a check most callers will not write.
|
|
884
549
|
var res = {
|
|
885
|
-
// Signature soundness is decided by comparison for the same reason the trust verdict below is:
|
|
886
|
-
// `every` replaced after load reports the whole set sound without reading one signer.
|
|
887
550
|
valid: signers.length > 0 && guard.list.allMatch(signers, function (s) { return s.ok === true; }),
|
|
888
551
|
eContentType: parsed.encapContentInfo.eContentType,
|
|
889
|
-
// The attached encapsulated content (null for a detached signature). A signer's message-digest
|
|
890
|
-
// attribute binds these exact bytes, so a `valid` verdict lets a caller consume the authenticated
|
|
891
|
-
// eContent -- SCEP's pkcsPKIEnvelope -- without a second parse of the untrusted message.
|
|
892
552
|
eContent: parsed.encapContentInfo.eContent != null ? parsed.encapContentInfo.eContent : null,
|
|
893
553
|
signers: signers,
|
|
894
554
|
};
|
|
@@ -896,47 +556,16 @@ async function _verify(input, opts) {
|
|
|
896
556
|
return res;
|
|
897
557
|
}
|
|
898
558
|
|
|
899
|
-
// The full path build + validate, injected by path-validate the way it injects into crl-verify and
|
|
900
|
-
// cmp-verify. It is a seam rather than a require because path-validate is the higher layer: taking
|
|
901
|
-
// the dependency the other way round would be a cycle, and re-implementing a weaker chain walk here
|
|
902
|
-
// is exactly how a second, divergent notion of trust gets into a toolkit.
|
|
903
559
|
var _engine = null;
|
|
904
560
|
function setEngine(engine) { _engine = engine; }
|
|
905
561
|
|
|
906
|
-
// `valid` and `trusted` are DIFFERENT claims and neither implies the other. `valid` says every
|
|
907
|
-
// signature is sound under a certificate the message or the caller supplied -- a message carries its
|
|
908
|
-
// own certificates, so that establishes internal consistency and nothing about who signed. `trusted`
|
|
909
|
-
// says the signer chained to a root the CALLER named. Without anchors there is no one to chain to,
|
|
910
|
-
// so it is false: not null, because "nobody anchored this" is a definite answer, and the same one a
|
|
911
|
-
// caller gets from pki.cmp.verify.
|
|
912
|
-
// A private copy of the trust configuration, taken at the entry point. The array is copied so it
|
|
913
|
-
// cannot be re-pointed or grown; each anchor's BYTES are copied because rewriting those in place is
|
|
914
|
-
// the same substitution one level down; the instant is copied because a Date is mutable. An anchor
|
|
915
|
-
// TUPLE is copied too, field by field: `{ name, publicKey, algorithm }` is an ordinary object whose
|
|
916
|
-
// key bytes and name can be rewritten just as readily as a DER buffer's, and copying only the DER
|
|
917
|
-
// form would leave the documented tuple form aliased across the same gap -- the window closed for
|
|
918
|
-
// one spelling of an anchor and left open for the other. A PEM string needs no copy: it cannot be
|
|
919
|
-
// rewritten. `requiredEku` is copied for the same reason as the array.
|
|
920
562
|
var _ANCHOR_CLONE_DEPTH = 8;
|
|
921
563
|
function _cloneAnchorValue(v, depth) {
|
|
922
564
|
if (guard.bytes.isByteSource(v)) {
|
|
923
565
|
return _snapshotIfBytes(v, "opts.trustAnchors[]");
|
|
924
566
|
}
|
|
925
567
|
if (v === null || typeof v !== "object") return v;
|
|
926
|
-
// A Date BEFORE the generic object walk. It has no enumerable own properties, so copying it
|
|
927
|
-
// field by field yields `{}` -- an anchor's `distrustAfter: { emailProtection: <Date> }` would
|
|
928
|
-
// survive the snapshot as an empty object and be rejected as an invalid date, disabling the very
|
|
929
|
-
// policy it encodes. Copied by value, like the validation instant.
|
|
930
|
-
// The instant comes through the guard's intrinsic read, so a caller's `getTime` override can
|
|
931
|
-
// neither throw out of the snapshot nor put a different instant in the copy than the original
|
|
932
|
-
// holds -- which would leave the anchor policy that was checked and the one that is applied two
|
|
933
|
-
// different values.
|
|
934
568
|
if (guard.time.isDate(v)) return new intrinsic.Date(guard.time.instantOf(v));
|
|
935
|
-
// A structure too deep to copy is REFUSED, not shared. Returning it by reference at the cap
|
|
936
|
-
// would leave part of the anchor caller-mutable across the chain walk while the rest was
|
|
937
|
-
// snapshotted -- a guarantee that holds for the shallow fields and quietly lapses for the deep
|
|
938
|
-
// ones, which is worse than not offering it. An anchor tuple is a handful of levels deep; a
|
|
939
|
-
// deeper one is a caller's mistake, and it fails closed at the entry point.
|
|
940
569
|
if (depth >= _ANCHOR_CLONE_DEPTH) {
|
|
941
570
|
throw _err("cms/bad-input", "an opts.trustAnchors entry nests deeper than " + _ANCHOR_CLONE_DEPTH +
|
|
942
571
|
" levels, so it cannot be copied before the chain is walked; pass the anchor as certificate DER or a { name, publicKey, algorithm } tuple");
|
|
@@ -959,148 +588,64 @@ function _snapshotTrust(opts) {
|
|
|
959
588
|
}
|
|
960
589
|
return {
|
|
961
590
|
trustAnchors: anchors,
|
|
962
|
-
// The validation instant, read from the captured Date intrinsic, never the live global Date. The
|
|
963
|
-
// snapshot is taken before opts.content is touched (see _verify), so no content accessor has run to
|
|
964
|
-
// replace a global yet; reading the instant through the captured constructor holds even if that
|
|
965
|
-
// ordering regresses. A caller Date is cloned; undefined becomes now.
|
|
966
591
|
time: guard.time.isDate(opts.time) ? new intrinsic.Date(guard.time.instantOf(opts.time)) : (opts.time === undefined ? new intrinsic.Date() : opts.time),
|
|
967
592
|
requiredEku: Array.isArray(opts.requiredEku) ? opts.requiredEku.slice() : opts.requiredEku,
|
|
968
593
|
checkPurpose: opts.checkPurpose,
|
|
969
594
|
};
|
|
970
595
|
}
|
|
971
596
|
|
|
972
|
-
// A signer certificate whose keyUsage FORBIDS signing has not been permitted to sign, however well
|
|
973
|
-
// it chains. RFC 5280 sec. 4.2.1.3: when the extension is present it is binding, and a leaf
|
|
974
|
-
// asserting keyEncipherment alone must not verify a signature. Path validation checks the CA's
|
|
975
|
-
// keyCertSign, not the target's own usage, so the format that knows a signature was made asks the
|
|
976
|
-
// question -- the same format-local gate on top of path.validate that pki.cmp.verify applies.
|
|
977
|
-
// contentCommitment (nonRepudiation) counts: RFC 5652 signatures are made under either bit.
|
|
978
|
-
// The value is read through the ONE strict decoder, which enforces the X.690 sec. 11.2.2 minimal
|
|
979
|
-
// NamedBitList form, so a malformed keyUsage fails the gate rather than a hand-rolled bit test
|
|
980
|
-
// authorizing it.
|
|
981
|
-
// The shared RFC 5280 sec. 4.2.1 extension-value decoders, the same set pki.cmp.verify reads its
|
|
982
|
-
// signer keyUsage through -- one structurally-strict decoder rather than a second, weaker bit test.
|
|
983
597
|
var _CERT_EXT_DECODERS = pkix.certExtensionDecoders(pkix.makeNS("cms", CmsError, oid)).byOid;
|
|
984
598
|
|
|
985
599
|
function _keyUsagePermitsSigning(parsedCerts, der) {
|
|
986
|
-
// Captured filter + equality: this runs after the streamed content was consumed, and the fail-open
|
|
987
|
-
// `if (!entry) return true` below would otherwise be reachable by a hostile iterator that replaced
|
|
988
|
-
// Buffer.prototype.equals to return false, making the signer certificate un-findable and its
|
|
989
|
-
// keyUsage un-read -- a keyEncipherment-only certificate would then be reported trusted (CWE-347).
|
|
990
600
|
var entry = intrinsic.filter(parsedCerts, function (c) { return intrinsic.bufferEquals(c.der, der); })[0];
|
|
991
|
-
if (!entry) return true;
|
|
601
|
+
if (!entry) return true;
|
|
992
602
|
var kuOid = oid.byName("keyUsage");
|
|
993
603
|
var exts = entry.cert.extensions || [];
|
|
994
604
|
for (var i = 0; i < exts.length; i++) {
|
|
995
605
|
if (exts[i].oid !== kuOid) continue;
|
|
996
606
|
var ku;
|
|
997
607
|
try { ku = _CERT_EXT_DECODERS[kuOid](exts[i].value); }
|
|
998
|
-
catch (_e) { return false; }
|
|
608
|
+
catch (_e) { return false; }
|
|
999
609
|
return ku.digitalSignature === true || ku.contentCommitment === true || ku.nonRepudiation === true;
|
|
1000
610
|
}
|
|
1001
|
-
return true;
|
|
611
|
+
return true;
|
|
1002
612
|
}
|
|
1003
613
|
|
|
1004
|
-
// Every signer trusted? The answer IS the message-level trust verdict, so it is decided by
|
|
1005
|
-
// comparison instead of by an `every`. See guard-list.
|
|
1006
614
|
function _allTrusted(signers) {
|
|
1007
615
|
return guard.list.allMatch(signers, function (s) { return s.trusted === true; });
|
|
1008
616
|
}
|
|
1009
617
|
|
|
1010
|
-
// `cfg` is verify's SNAPSHOT, never the caller's options object -- the parameter is named for that
|
|
1011
|
-
// so a future caller cannot hand it the live one without noticing.
|
|
1012
618
|
async function _applyTrust(res, parsedCerts, cfg) {
|
|
1013
619
|
res.trusted = false;
|
|
1014
|
-
// Every signer carries the field too, whether or not anchors were supplied. A verdict whose
|
|
1015
|
-
// per-signer shape depends on which options were passed makes a caller iterating `signers` write
|
|
1016
|
-
// a different loop for each case -- and the absent field reads as "unknown" where the answer is
|
|
1017
|
-
// a definite "nothing anchored this".
|
|
1018
|
-
// async + captured enumeration + await (see _verify): the trust verdict is decided after the streamed
|
|
1019
|
-
// content was consumed, so a hostile iterator must not steer it through a replaced Array.prototype or
|
|
1020
|
-
// Promise.prototype.then.
|
|
1021
620
|
intrinsic.forEach(res.signers, function (s) { s.trusted = false; });
|
|
1022
621
|
if (cfg.trustAnchors == null) return;
|
|
1023
622
|
if (!_engine) {
|
|
1024
623
|
throw _err("cms/bad-input", "opts.trustAnchors requires the path validator; load pki.path before verifying (require the toolkit through its index)");
|
|
1025
624
|
}
|
|
1026
|
-
// Every signer, not merely one: a multi-signer message's signers are independent claims, and
|
|
1027
|
-
// reporting the whole as trusted because one of them anchored would let an unanchored signer ride
|
|
1028
|
-
// out on another's chain -- the same rule the per-signer `ok` already follows for signatures.
|
|
1029
625
|
var pool = intrinsic.map(parsedCerts, function (c) { return c.der; });
|
|
1030
|
-
// The default instant was captured at snapshot time (see _snapshotTrust), before the stream ran, so
|
|
1031
|
-
// no live `new Date()` is read here where a replaced Date could steer the validation instant.
|
|
1032
626
|
var at = cfg.time;
|
|
1033
627
|
var anchors = cfg.trustAnchors;
|
|
1034
|
-
// The configuration is checked HERE, once, before any signer is looked at. Leaving it to the
|
|
1035
|
-
// chain walk would make a caller's mistake depend on the message: a SignedData whose every signer
|
|
1036
|
-
// failed to verify never reaches a build call, so unusable anchors would be accepted in silence
|
|
1037
|
-
// and reported as `trusted: false` -- the config fault dressed as a verdict, which is exactly the
|
|
1038
|
-
// conflation this seam exists to remove. An empty anchor list is refused for the same reason a
|
|
1039
|
-
// policy that constrains nothing is: it cannot make anything trusted, so asking for it is a
|
|
1040
|
-
// mistake rather than a request.
|
|
1041
628
|
if (!anchors.length) {
|
|
1042
629
|
throw _err("cms/bad-input", "opts.trustAnchors is empty -- name at least one root, or omit it to state that the signer is not being anchored");
|
|
1043
630
|
}
|
|
1044
|
-
// The INSTANT and the key purposes, before the anchors -- the anchor check reads the NORMALIZED
|
|
1045
|
-
// purpose the resolver returns, so it has to run first.
|
|
1046
631
|
if (cfg.time !== undefined) guard.time.assertValid(cfg.time, _err, "cms/bad-input", "opts.time");
|
|
1047
|
-
// The resolver's RETURN is what the walk goes on to use: it normalizes a dotted purpose OID to
|
|
1048
|
-
// its registered name, which is the key an anchor's per-purpose metadata is stored under.
|
|
1049
|
-
// Preflighting with the caller's raw spelling would look up `distrustAfter["1.3.6.1.5.5.7.3.4"]`
|
|
1050
|
-
// where the walk reads `distrustAfter.emailProtection` -- the same value checked under two
|
|
1051
|
-
// different keys, so the early check would pass on metadata the walk then rejects.
|
|
1052
632
|
var purposes = _engine.resolvePurposeOpts({ requiredEku: cfg.requiredEku, checkPurpose: cfg.checkPurpose });
|
|
1053
|
-
// The WHOLE anchor, not only its identity: `toAnchor` settles the name / key / algorithm tuple,
|
|
1054
|
-
// and the constraint metadata beside it -- the per-purpose distrustAfter dates -- is validated
|
|
1055
|
-
// through the same definition the walk uses. Checking one and not the other is how this rule has
|
|
1056
|
-
// repeatedly come back: each new part of the configuration has to join the preflight, not wait
|
|
1057
|
-
// to be caught where it is consumed.
|
|
1058
633
|
intrinsic.forEach(anchors, function (a) {
|
|
1059
634
|
_engine.toAnchor(a);
|
|
1060
635
|
if (purposes.checkPurpose != null) _engine.assertAnchorConstraints(a, purposes.checkPurpose);
|
|
1061
636
|
});
|
|
1062
|
-
// The INSTANT is checked here for the same reason the anchors are, and in the same place: a
|
|
1063
|
-
// Everything above is the preflight: the anchor list, the instant, the key purposes, and each
|
|
1064
|
-
// anchor's own constraint metadata -- all judged before a single signer is looked at, and all
|
|
1065
|
-
// through the definitions the walk itself uses. A new option joins it here; validating one only
|
|
1066
|
-
// where it is consumed is what made a caller's mistake depend on the message.
|
|
1067
637
|
for (var _si = 0; _si < res.signers.length; _si++) {
|
|
1068
638
|
var s = res.signers[_si];
|
|
1069
639
|
{
|
|
1070
640
|
if (s.ok !== true || !s.cert) { s.trusted = false; continue; }
|
|
1071
641
|
var buildOpts = { trustAnchors: anchors, intermediates: pool, validate: true, time: at };
|
|
1072
|
-
// A key purpose, when the caller names one. "Trusted" is not a property of a chain alone: a
|
|
1073
|
-
// certificate restricted to serverAuth chains perfectly well and is still the wrong key to
|
|
1074
|
-
// have signed an email. The verb that KNOWS the purpose supplies it -- pki.smime.verify asks
|
|
1075
|
-
// for emailProtection -- rather than this layer guessing one for every CMS use.
|
|
1076
642
|
if (cfg.requiredEku != null) buildOpts.requiredEku = cfg.requiredEku;
|
|
1077
|
-
// The ANCHOR's own trust metadata, which is a separate question from the leaf's EKU. A root
|
|
1078
|
-
// distributed with NSS trust bits can be marked untrusted for email while remaining a
|
|
1079
|
-
// perfectly good TLS root, and `pki.path` consults those bits -- and distrustAfter -- only
|
|
1080
|
-
// when a purpose is named. Requiring the leaf's EKU without naming the purpose checks one
|
|
1081
|
-
// end of the chain and not the other, so a root explicitly distrusted for the very purpose
|
|
1082
|
-
// being asked about would still answer "trusted".
|
|
1083
643
|
if (cfg.checkPurpose != null) buildOpts.checkPurpose = cfg.checkPurpose;
|
|
1084
|
-
// THE certificate this SignerInfo selected, and only that one -- the same certificate the
|
|
1085
|
-
// verdict reports as `cert`. When a subjectKeyIdentifier is used, several certificates can
|
|
1086
|
-
// hold the signing key, and it is tempting to let any of them answer: the embedded one may
|
|
1087
|
-
// be self-signed while the caller supplied a CA-issued twin. Doing so would decide trust
|
|
1088
|
-
// from a certificate the message did not present, carrying a different validity window, key
|
|
1089
|
-
// usage and policy set -- so an expired or wrong-purpose signer certificate could be
|
|
1090
|
-
// reported trusted through its sibling. `trusted` describes the certificate named in the
|
|
1091
|
-
// same verdict; a caller who wants a particular one used supplies that one.
|
|
1092
|
-
// Permitted to have signed, as well as chained. Checked BEFORE the chain walk because a
|
|
1093
|
-
// certificate that forbids signing cannot become trusted by chaining, so there is nothing to
|
|
1094
|
-
// learn from walking it.
|
|
1095
644
|
if (!_keyUsagePermitsSigning(parsedCerts, s.cert)) { s.trusted = false; continue; }
|
|
1096
|
-
// `await`, not `.then`: see _verify.
|
|
1097
645
|
try {
|
|
1098
646
|
var r = await _engine.build(s.cert, buildOpts);
|
|
1099
647
|
s.trusted = !!(r && r.valid);
|
|
1100
648
|
} catch (e) {
|
|
1101
|
-
// A CONFIG fault -- unusable anchors, an invalid time -- is the caller's mistake and must
|
|
1102
|
-
// not be absorbed into "untrusted", which would read as a verdict about the message. A
|
|
1103
|
-
// chain that simply does not reach an anchor is not that: it is the answer.
|
|
1104
649
|
if (e && e.code) {
|
|
1105
650
|
var code = intrinsic.String(e.code);
|
|
1106
651
|
if (intrinsic.stringIndexOf(code, "bad-input") !== -1 ||
|
|
@@ -1111,18 +656,8 @@ async function _applyTrust(res, parsedCerts, cfg) {
|
|
|
1111
656
|
}
|
|
1112
657
|
}
|
|
1113
658
|
}
|
|
1114
|
-
// The trust verdict is decided by comparison, not by a prototype method: `every` replaced after
|
|
1115
|
-
// load answers true without consulting a single signer, and a message no anchor covers reports
|
|
1116
|
-
// trusted:true.
|
|
1117
659
|
res.trusted = res.valid && res.signers.length > 0 && _allTrusted(res.signers);
|
|
1118
660
|
}
|
|
1119
|
-
// Parse a candidate cert DER and index its SKI; a cert that will not parse is skipped (it
|
|
1120
|
-
// simply cannot be a signer match, and a malformed embedded cert must not fail the verify).
|
|
1121
|
-
// The certificate bytes are copied BEFORE they are parsed, not after: the parse surfaces the
|
|
1122
|
-
// signed portion and the public key as VIEWS into this buffer, and both are read in a later
|
|
1123
|
-
// promise turn to check the signer's signature and walk the chain. Copying afterwards would
|
|
1124
|
-
// leave those views pointing at bytes the caller can still rewrite -- a signer certificate
|
|
1125
|
-
// swapped for another in that gap, with the verdict still naming the first.
|
|
1126
661
|
function _addCert(out, der) {
|
|
1127
662
|
var buf;
|
|
1128
663
|
try {
|
|
@@ -1135,18 +670,6 @@ function _addCert(out, der) {
|
|
|
1135
670
|
out.push({ cert: cert, der: buf, ski: _certSki(cert) });
|
|
1136
671
|
}
|
|
1137
672
|
|
|
1138
|
-
// Coverage residual -- these defensive branches are unreachable through the shipped path
|
|
1139
|
-
// because an upstream contract already narrows the shape:
|
|
1140
|
-
// * `cert.extensions || []` -- x509.parse always surfaces `extensions` as an array (empty
|
|
1141
|
-
// when absent), so the `|| []` fallback never fires.
|
|
1142
|
-
// * `_decodeSignedAttrs`'s `tagNumber !== SET` / `!children` guard -- the caller forces the
|
|
1143
|
-
// leading byte to a universal SET OF before decoding, so `asn1.decode` yields a SET whose
|
|
1144
|
-
// `children` is always an array; the guard is belt-and-suspenders against a future caller.
|
|
1145
|
-
// * `if (!Array.isArray(parsed.signerInfos))` -- the object-input branch is only taken when
|
|
1146
|
-
// `signerInfos` is already an array, and cms.parse yields a SignedData whose `signerInfos`
|
|
1147
|
-
// is always an array (or it throws), so this belt-and-suspenders throw never fires.
|
|
1148
|
-
// * `c && c.bytes ? c.bytes : c` -- schema-cms surfaces every embedded certificate as an
|
|
1149
|
-
// object carrying a `bytes` Buffer, so the raw-value fallback never fires.
|
|
1150
673
|
/**
|
|
1151
674
|
* @primitive pki.cms.sign
|
|
1152
675
|
* @signature pki.cms.sign(content, signers, opts?) -> Promise<Buffer|string>
|
|
@@ -1443,9 +966,6 @@ var compress = cmsCompress.compress;
|
|
|
1443
966
|
*/
|
|
1444
967
|
var decompress = cmsCompress.decompress;
|
|
1445
968
|
|
|
1446
|
-
// A certs-only reader/recognizer input: a DER Buffer (snapshotted at the door so a later mutation
|
|
1447
|
-
// cannot swap the bytes the verdict describes) or a PEM CMS/PKCS7 block. An undecodable input is a
|
|
1448
|
-
// typed cms/bad-input, never a raw throw.
|
|
1449
969
|
var KNOWN_PARSE_CERTS_ONLY_OPTS = { maxCerts: 1 };
|
|
1450
970
|
function _certsOnlyInputDer(input, verb) {
|
|
1451
971
|
if (guard.bytes.isByteSource(input)) {
|
|
@@ -1518,10 +1038,6 @@ var certsOnly = cmsSign.certsOnly;
|
|
|
1518
1038
|
function parseCertsOnly(input, opts) {
|
|
1519
1039
|
opts = guard.identifier.optionsObject(opts, _err, "cms/bad-input", "pki.cms.parseCertsOnly options");
|
|
1520
1040
|
guard.identifier.assertKnownKeys(opts, KNOWN_PARSE_CERTS_ONLY_OPTS, _err, "cms/bad-input", "pki.cms.parseCertsOnly has an unknown option ");
|
|
1521
|
-
// maxCerts bounds the parse of an untrusted bundle, so the bound itself is validated at the door: a
|
|
1522
|
-
// negative, fractional, NaN, or non-numeric cap that slice would silently treat as no cap (or, for
|
|
1523
|
-
// -1, as "all but the last") is refused rather than forwarded (guard.limits.cap). Absent or null
|
|
1524
|
-
// leaves the count uncapped.
|
|
1525
1041
|
var maxCerts = guard.limits.cap(opts.maxCerts == null ? undefined : opts.maxCerts, "maxCerts", undefined, { E: _err, code: "cms/bad-input" });
|
|
1526
1042
|
return cms.parseCertsOnly(_certsOnlyInputDer(input, "pki.cms.parseCertsOnly"), _err, "cms", maxCerts, false);
|
|
1527
1043
|
}
|
|
@@ -1546,18 +1062,11 @@ function parseCertsOnly(input, opts) {
|
|
|
1546
1062
|
* pki.cms.isCertsOnly(pki.cms.certsOnly([certDer])); // true
|
|
1547
1063
|
*/
|
|
1548
1064
|
function isCertsOnly(input) {
|
|
1549
|
-
// A certs-only message is a SignedData with an id-data content, absent eContent, and no signer.
|
|
1550
|
-
// digestAlgorithms is deliberately NOT constrained: RFC 5652 sec. 5.1 permits a non-empty set even
|
|
1551
|
-
// with no signer, so a valid but non-canonical bundle (an EST or AIA response) is still recognized.
|
|
1552
1065
|
var der = _certsOnlyInputDer(input, "pki.cms.isCertsOnly");
|
|
1553
1066
|
var r;
|
|
1554
1067
|
try {
|
|
1555
1068
|
r = cms.parse(der);
|
|
1556
1069
|
} catch (e) {
|
|
1557
|
-
// A well-formed CMS ContentInfo whose content type this toolkit does not model (a bare id-data,
|
|
1558
|
-
// a DigestedData) is definitively not a certs-only SignedData, so the predicate reports false
|
|
1559
|
-
// rather than making a caller catch an exception for a valid non-SignedData CMS. Truly
|
|
1560
|
-
// undecodable bytes (not a ContentInfo at all) still throw.
|
|
1561
1070
|
if (e && e.code === "cms/unsupported-content-type") return false;
|
|
1562
1071
|
throw e;
|
|
1563
1072
|
}
|
|
@@ -1567,5 +1076,5 @@ function isCertsOnly(input) {
|
|
|
1567
1076
|
r.signerInfos.length === 0;
|
|
1568
1077
|
}
|
|
1569
1078
|
|
|
1570
|
-
//
|
|
1079
|
+
// @internal
|
|
1571
1080
|
module.exports = { verify: verify, sign: sign, countersign: countersign, encrypt: encrypt, authenticate: authenticate, decrypt: decrypt, compress: compress, decompress: decompress, certsOnly: certsOnly, parseCertsOnly: parseCertsOnly, isCertsOnly: isCertsOnly, setEngine: setEngine };
|