@blamejs/pki 0.5.6 → 0.5.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +419 -378
- package/MIGRATING.md +65 -0
- package/README.md +12 -12
- package/lib/acme.js +31 -31
- package/lib/asn1-der.js +10 -10
- package/lib/attrcert-sign.js +24 -20
- package/lib/byte-reader.js +6 -6
- package/lib/byte-writer.js +5 -5
- package/lib/cbor-det.js +27 -24
- package/lib/cmc-build.js +104 -104
- package/lib/cmc-verify.js +108 -32
- package/lib/cmp-build.js +31 -26
- package/lib/cmp-session.js +74 -72
- package/lib/cmp-verify.js +72 -58
- package/lib/cms-compress.js +8 -9
- package/lib/cms-decrypt.js +92 -76
- package/lib/cms-encrypt.js +33 -34
- package/lib/cms-sign.js +100 -54
- package/lib/cms-verify.js +141 -82
- package/lib/composite-sig.js +13 -13
- package/lib/constants.js +4 -4
- package/lib/crl-sign.js +31 -25
- package/lib/crl-verify.js +7 -6
- package/lib/crmf-sign.js +19 -15
- package/lib/csr-sign.js +13 -9
- package/lib/ct.js +37 -37
- package/lib/edwards-point.js +7 -7
- package/lib/est.js +103 -59
- package/lib/framework-error.js +5 -5
- package/lib/guard-all.js +3 -3
- package/lib/guard-async.js +4 -4
- package/lib/guard-bytes.js +378 -15
- package/lib/guard-compress.js +17 -17
- package/lib/guard-crypto.js +1 -1
- package/lib/guard-encoding.js +15 -15
- package/lib/guard-header.js +3 -3
- package/lib/guard-identifier.js +16 -16
- package/lib/guard-json.js +15 -15
- package/lib/guard-limits.js +7 -7
- package/lib/guard-name.js +81 -16
- package/lib/guard-parsed.js +144 -75
- package/lib/guard-range.js +19 -19
- package/lib/guard-secret.js +11 -10
- package/lib/guard-text.js +6 -6
- package/lib/guard-time.js +10 -10
- package/lib/hpke.js +18 -17
- package/lib/http-digest.js +35 -35
- package/lib/http-retry-after.js +13 -13
- package/lib/http-transport.js +20 -19
- package/lib/inspect.js +53 -53
- package/lib/ip-utils.js +2 -2
- package/lib/jose.js +13 -13
- package/lib/key.js +16 -16
- package/lib/lint.js +51 -51
- package/lib/merkle.js +51 -36
- package/lib/mime.js +18 -18
- package/lib/ocsp-verify.js +10 -10
- package/lib/ocsp.js +32 -20
- package/lib/oid.js +29 -29
- package/lib/path-validate.js +114 -113
- package/lib/pbes2.js +16 -16
- package/lib/pkcs12-build.js +71 -56
- package/lib/pki-build.js +23 -22
- package/lib/rc2.js +1 -1
- package/lib/rfc3339.js +5 -5
- package/lib/schema-all.js +31 -31
- package/lib/schema-attrcert.js +12 -12
- package/lib/schema-c509.js +144 -142
- package/lib/schema-cmc.js +58 -58
- package/lib/schema-cmp.js +43 -43
- package/lib/schema-cms.js +169 -36
- package/lib/schema-crl.js +7 -7
- package/lib/schema-crmf.js +28 -28
- package/lib/schema-csr.js +12 -12
- package/lib/schema-csrattrs.js +16 -16
- package/lib/schema-engine.js +18 -18
- package/lib/schema-ocsp.js +15 -15
- package/lib/schema-pkcs12.js +20 -20
- package/lib/schema-pkcs8.js +2 -2
- package/lib/schema-pkix.js +131 -126
- package/lib/schema-smime.js +19 -19
- package/lib/schema-tsp.js +12 -12
- package/lib/schema-x509.js +3 -3
- package/lib/shbs.js +18 -18
- package/lib/sign-scheme.js +19 -16
- package/lib/sigstore.js +10 -11
- package/lib/sleep.js +1 -1
- package/lib/smime.js +308 -96
- package/lib/tls-cert-compress.js +18 -18
- package/lib/trust.js +27 -27
- package/lib/tsp-sign.js +22 -18
- package/lib/validator-all.js +1 -1
- package/lib/validator-attcert.js +1 -1
- package/lib/validator-cose.js +43 -44
- package/lib/validator-keydesc.js +3 -3
- package/lib/validator-sig.js +13 -13
- package/lib/validator-tls.js +11 -11
- package/lib/validator-tpm.js +21 -20
- package/lib/webauthn-mds.js +67 -67
- package/lib/webauthn.js +34 -34
- package/lib/webcrypto.js +15 -15
- package/lib/x509-sign.js +24 -15
- package/package.json +3 -2
- package/sbom.cdx.json +6 -6
package/lib/cms-verify.js
CHANGED
|
@@ -16,9 +16,9 @@
|
|
|
16
16
|
* detached content, single and multiple signers, and RSA / RSASSA-PSS / ECDSA / EdDSA, the
|
|
17
17
|
* post-quantum ML-DSA (ML-DSA-44/65/87, RFC 9882) and SLH-DSA (the twelve FIPS 205 sets, RFC 9814),
|
|
18
18
|
* and composite ML-DSA (pairing ML-DSA with a traditional RSA / ECDSA / EdDSA, accepted only when
|
|
19
|
-
*
|
|
19
|
+
* both components verify, per draft-ietf-lamps-cms-composite-sigs) are covered. It reports a
|
|
20
20
|
* per-signer verdict;
|
|
21
|
-
* it does
|
|
21
|
+
* it does not chain the signer certificate to a trust anchor, which is the caller's step through
|
|
22
22
|
* `pki.path.validate`.
|
|
23
23
|
* @spec RFC 5652
|
|
24
24
|
* @spec RFC 9882
|
|
@@ -63,11 +63,11 @@ var DIGEST_HASH = {
|
|
|
63
63
|
// The SIGNATURE hash a bare-key-OID scheme (rsaEncryption, ecPublicKey) inherits from the
|
|
64
64
|
// SignerInfo digestAlgorithm. Deliberately a SEPARATE table from DIGEST_HASH, and deliberately
|
|
65
65
|
// without the extendable-output functions: RFC 8702 sec. 3.2 gives RSASSA-PKCS1-v1_5-with-SHAKE
|
|
66
|
-
// and ECDSA-with-SHAKE their
|
|
66
|
+
// and ECDSA-with-SHAKE their own signature OIDs and never pairs a bare key OID with a SHAKE
|
|
67
67
|
// digestAlgorithm, so that combination is non-conformant and must keep its precise refusal.
|
|
68
68
|
// Folding the two roles into one table would resolve a signature hash for it instead, letting
|
|
69
|
-
// it past the fail-closed gate below and into the engine, where
|
|
70
|
-
// foreign fault
|
|
69
|
+
// it past the fail-closed gate below and into the engine, where the caller gets a relabeled
|
|
70
|
+
// foreign fault and loses this module's own unsupported-algorithm verdict.
|
|
71
71
|
var SIG_HASH = { sha1: "SHA-1", sha256: "SHA-256", sha384: "SHA-384", sha512: "SHA-512" };
|
|
72
72
|
// Is `name` a message-digest algorithm this verifier supports?
|
|
73
73
|
function _supportedDigest(name) { return !!DIGEST_HASH[name]; }
|
|
@@ -86,7 +86,7 @@ var SIG_SCHEME = {
|
|
|
86
86
|
rsassaPss: { kind: "rsapss" },
|
|
87
87
|
ecPublicKey: { kind: "ec", params: "absent" },
|
|
88
88
|
// One-shot families (EdDSA, ML-DSA): the same OID identifies the key and the signature, so the
|
|
89
|
-
// signer cert SPKI algorithm OID MUST equal the signatureAlgorithm OID
|
|
89
|
+
// signer cert SPKI algorithm OID MUST equal the signatureAlgorithm OID. `sameKeyOid` enables
|
|
90
90
|
// that agreement check (RFC 8410 / RFC 9882; enforced in _verifyAgainstCandidates).
|
|
91
91
|
Ed25519: { kind: "eddsa", name: "Ed25519", params: "absent", sameKeyOid: true },
|
|
92
92
|
Ed448: { kind: "eddsa", name: "Ed448", params: "absent", sameKeyOid: true },
|
|
@@ -104,7 +104,7 @@ var SIG_SCHEME = {
|
|
|
104
104
|
};
|
|
105
105
|
// SLH-DSA (RFC 9814): the twelve pure FIPS 205 sets, seeded like webcrypto's SLH_DSA_NODE. Each is
|
|
106
106
|
// a one-shot family (kind "slhdsa"), params absent, sig-OID == key-OID (sameKeyOid). The WebCrypto
|
|
107
|
-
// name is "SLH-DSA-"+SET, matching path-validate's transform (drift rule sec. 5
|
|
107
|
+
// name is "SLH-DSA-"+SET, matching path-validate's transform (drift rule sec. 5: one name map).
|
|
108
108
|
["sha2-128s", "sha2-128f", "sha2-192s", "sha2-192f", "sha2-256s", "sha2-256f",
|
|
109
109
|
"shake-128s", "shake-128f", "shake-192s", "shake-192f", "shake-256s", "shake-256f"
|
|
110
110
|
].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 }; });
|
|
@@ -119,8 +119,7 @@ EC_CURVE[oid.byName("secp384r1")] = { curve: "P-384", coordLen: 48 };
|
|
|
119
119
|
EC_CURVE[oid.byName("secp521r1")] = { curve: "P-521", coordLen: 66 };
|
|
120
120
|
|
|
121
121
|
function _toBuf(v, what) {
|
|
122
|
-
if (Buffer.isBuffer(v)) return v;
|
|
123
|
-
if (v instanceof Uint8Array) return Buffer.from(v);
|
|
122
|
+
if (Buffer.isBuffer(v) || v instanceof Uint8Array) return guard.bytes.snapshot(v, CmsError, "cms/bad-input", what);
|
|
124
123
|
throw _err("cms/bad-input", what + " must be a Buffer");
|
|
125
124
|
}
|
|
126
125
|
|
|
@@ -141,7 +140,7 @@ function _findSignerCerts(sid, parsedCerts) {
|
|
|
141
140
|
return out;
|
|
142
141
|
}
|
|
143
142
|
|
|
144
|
-
// Verify `sigBytes` over `signedBytes` against
|
|
143
|
+
// Verify `sigBytes` over `signedBytes` against each matching candidate certificate in turn; the
|
|
145
144
|
// signer is valid iff one candidate verifies. A candidate whose key is structurally unusable (a
|
|
146
145
|
// low-order EdDSA point, an unsupported curve) or whose signature simply does not verify falls
|
|
147
146
|
// through to the next -- so a colliding certificate cannot make a valid signature read invalid.
|
|
@@ -157,7 +156,7 @@ function _verifyAgainstCandidates(scheme, sigHash, sigBytes, signedBytes, sid, c
|
|
|
157
156
|
var c = candidates[idx];
|
|
158
157
|
// One-shot family (EdDSA/ML-DSA): the signer cert public-key algorithm OID MUST equal the
|
|
159
158
|
// SignerInfo signatureAlgorithm OID. A candidate whose SPKI disagrees is skipped with a precise
|
|
160
|
-
// verdict
|
|
159
|
+
// verdict; importing it under the wrong name would surface a foreign webcrypto/data throw.
|
|
161
160
|
if (expectedKeyOid && c.cert.subjectPublicKeyInfo.algorithm.oid !== expectedKeyOid) {
|
|
162
161
|
lastErr = _err("cms/unsupported-algorithm", "the signer certificate public-key algorithm does not match the SignerInfo signatureAlgorithm");
|
|
163
162
|
return attempt(idx + 1);
|
|
@@ -207,11 +206,11 @@ function _hashAlgOid(seq) {
|
|
|
207
206
|
}
|
|
208
207
|
|
|
209
208
|
// Resolve RSASSA-PSS-params (RFC 4055) to { hash, saltLength }, or null if the parameters
|
|
210
|
-
// deviate from the supported profile: an explicit SHA-2 hashAlgorithm, MGF1 keyed to the
|
|
209
|
+
// deviate from the supported profile: an explicit SHA-2 hashAlgorithm, MGF1 keyed to the same
|
|
211
210
|
// hash, saltLength equal to the hash length, and trailerField 1. The SHA-1 DEFAULTs are
|
|
212
211
|
// rejected (params must be explicit), matching the certification-path validator, so a
|
|
213
|
-
// non-conformant PSS AlgorithmIdentifier is a fail-closed verdict
|
|
214
|
-
//
|
|
212
|
+
// non-conformant PSS AlgorithmIdentifier is a fail-closed verdict. Falling back to WebCrypto's
|
|
213
|
+
// own defaults would be a signatureAlgorithm bypass.
|
|
215
214
|
function _resolvePss(paramsBytes) {
|
|
216
215
|
if (!paramsBytes) return null;
|
|
217
216
|
var n;
|
|
@@ -265,7 +264,7 @@ function _verifySignature(scheme, hashName, sigBytes, spki, signedBytes, curveOi
|
|
|
265
264
|
var ec = EC_CURVE[curveOid];
|
|
266
265
|
if (!ec) throw _err("cms/unsupported-algorithm", "the signer key is on an unsupported EC curve");
|
|
267
266
|
// 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
|
|
268
|
-
// curve order n, not only the r=s=0 forgery)
|
|
267
|
+
// curve order n, not only the r=s=0 forgery). A verifier that knows the curve MUST use it.
|
|
269
268
|
var raw = validator.sig.ecdsaDerToP1363(sigBytes, ec.curve, CmsError, "cms/bad-signature");
|
|
270
269
|
return subtle.importKey("spki", spki, { name: "ECDSA", namedCurve: ec.curve }, false, ["verify"])
|
|
271
270
|
.then(function (k) { return subtle.verify({ name: "ECDSA", hash: hashName }, k, raw, signedBytes); });
|
|
@@ -280,13 +279,13 @@ function _verifySignature(scheme, hashName, sigBytes, spki, signedBytes, curveOi
|
|
|
280
279
|
}
|
|
281
280
|
// EdDSA -- the WebCrypto name follows the signing key's SPKI OID (Ed25519 / Ed448). node/
|
|
282
281
|
// OpenSSL imports any Ed25519/Ed448 SPKI without validating the point, and a low-order (e.g.
|
|
283
|
-
// all-zeroes) key verifies a forged signature
|
|
282
|
+
// all-zeroes) key verifies a forged signature, so reject a non-full-order point first.
|
|
284
283
|
_requireValidEdPoint(spki, scheme.name);
|
|
285
284
|
return subtle.importKey("spki", spki, { name: scheme.name }, false, ["verify"])
|
|
286
285
|
.then(function (k) { return subtle.verify({ name: scheme.name }, k, sigBytes, signedBytes); });
|
|
287
286
|
}
|
|
288
287
|
|
|
289
|
-
// The EdDSA signer key's point MUST be a valid, canonical, full-order Edwards point
|
|
288
|
+
// The EdDSA signer key's point MUST be a valid, canonical, full-order Edwards point: reject an
|
|
290
289
|
// off-curve or low-order key before it verifies a signature (WebCrypto import does not check it).
|
|
291
290
|
// Routed through the shared edwards-point gate every EdDSA verify path uses; curve from the name.
|
|
292
291
|
function _requireValidEdPoint(spkiBytes, name) {
|
|
@@ -304,9 +303,9 @@ function _certCurveOid(cert) {
|
|
|
304
303
|
|
|
305
304
|
// Decode the SignedAttributes SET OF Attribute from the exact bytes the signature covers
|
|
306
305
|
// (the re-tagged signedAttrsBytes), returning [{ type, values: [valueNode] }]. Binding the
|
|
307
|
-
// content-type / message-digest checks to
|
|
308
|
-
//
|
|
309
|
-
//
|
|
306
|
+
// content-type / message-digest checks to these decoded attributes means a parsed-object input
|
|
307
|
+
// cannot desync the checked attributes from the verified preimage (RFC 5652 sec. 5.4). The
|
|
308
|
+
// caller-mutable parsed si.signedAttrs is deliberately left out of that decision.
|
|
310
309
|
function _decodeSignedAttrs(setOfBytes) {
|
|
311
310
|
var set = asn1.decode(setOfBytes);
|
|
312
311
|
if (set.tagClass !== "universal" || set.tagNumber !== asn1.TAGS.SET || !set.children) throw _err("cms/bad-signed-attrs", "signedAttrs is not a SET OF Attribute");
|
|
@@ -318,8 +317,8 @@ function _decodeSignedAttrs(setOfBytes) {
|
|
|
318
317
|
});
|
|
319
318
|
}
|
|
320
319
|
|
|
321
|
-
// Verify one SignerInfo. Returns { ok, sid, cert, ... }
|
|
322
|
-
// structural failure carries a code.
|
|
320
|
+
// Verify one SignerInfo. Returns { ok, sid, cert, ... }, where a resolved false is a verdict and
|
|
321
|
+
// a structural failure carries a code.
|
|
323
322
|
// `csTarget`, when set (a Buffer), makes this a COUNTERSIGNATURE verify (RFC 5652 sec. 11.4): the
|
|
324
323
|
// preimage is the countersigned signature octets, not the sec. 5.4 content preimage, and no
|
|
325
324
|
// content-type attribute is required or checked (_computeCountersigBytes). content/eContentType are
|
|
@@ -344,7 +343,7 @@ function _verifyOne(si, content, eContentType, parsedCerts, csTarget) {
|
|
|
344
343
|
// The digestAlgorithm parameters, when present, MUST be a DER NULL (RFC 5754 sec. 2 accepts
|
|
345
344
|
// absent OR NULL; a present non-NULL is malformed and, being outside the signed preimage,
|
|
346
345
|
// must fail closed like the signatureAlgorithm parameters above). EXCEPT for an ML-DSA
|
|
347
|
-
// SignerInfo
|
|
346
|
+
// SignerInfo carrying no signed attributes: RFC 9882 sec. 3.3 says the whole digestAlgorithm field
|
|
348
347
|
// has no meaning there and MUST be ignored, so neither its parameters nor its name may reject.
|
|
349
348
|
var dp = si.digestAlgorithm.parameters;
|
|
350
349
|
var mldsaNoAttrs = scheme.kind === "mldsa" && !si.signedAttrsBytes;
|
|
@@ -399,7 +398,7 @@ function _computeCountersigBytes(si, csTarget) {
|
|
|
399
398
|
attrs = _decodeSignedAttrs(reTagged);
|
|
400
399
|
} 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); }
|
|
401
400
|
// sec. 11.4: a content-type attribute MUST NOT appear (the parser already rejects it at parse, but
|
|
402
|
-
// verify never relies on the parse alone
|
|
401
|
+
// verify never relies on the parse alone: a parsed-object input could carry one).
|
|
403
402
|
if (attrs.filter(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)");
|
|
404
403
|
var mdAttr = attrs.filter(function (a) { return a.type === OID_MESSAGE_DIGEST; })[0];
|
|
405
404
|
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)");
|
|
@@ -420,10 +419,27 @@ function _computeCountersigBytes(si, csTarget) {
|
|
|
420
419
|
// content-type / message-digest attribute disagrees.
|
|
421
420
|
function _computeSignedBytes(si, content, eContentType) {
|
|
422
421
|
return Promise.resolve().then(function () {
|
|
423
|
-
if (!si.signedAttrsBytes)
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
422
|
+
if (!si.signedAttrsBytes) {
|
|
423
|
+
// No signed attributes: the signature is over the content itself (RFC 5652 sec. 5.4). That is
|
|
424
|
+
// also exactly what a stripped-attributes forgery looks like, because a CMS signature does not
|
|
425
|
+
// commit to whether attributes were present -- so a signature made over a SignedAttributes
|
|
426
|
+
// block re-presented as one made over content verifies, and with no attributes there is no
|
|
427
|
+
// message-digest or content-type attribute left to disagree. Refuse the shape: content that
|
|
428
|
+
// parses as a SignedAttributes block cannot be told apart from that forgery, and it is not
|
|
429
|
+
// the verifier's place to guess which one it is holding.
|
|
430
|
+
if (cms.looksLikeSignedAttributes(content)) {
|
|
431
|
+
return { mismatch: {
|
|
432
|
+
code: "cms/ambiguous-content",
|
|
433
|
+
message: "the content of a SignerInfo with no signed attributes is itself an encoded " +
|
|
434
|
+
"SignedAttributes block, which is indistinguishable from a signature over attributes " +
|
|
435
|
+
"re-presented as one over content (RFC 5652 sec. 5.4); sign such content WITH signed attributes",
|
|
436
|
+
} };
|
|
437
|
+
}
|
|
438
|
+
return content;
|
|
439
|
+
}
|
|
440
|
+
// With signed attributes: decode them from the EXACT bytes the signature covers, namely the
|
|
441
|
+
// SignedAttributes SET OF with the on-wire [0] IMPLICIT tag replaced by a universal SET OF
|
|
442
|
+
// (RFC 5652 sec. 5.4). The content-type / message-digest checks then bind the same bytes
|
|
427
443
|
// that are verified. Reading the caller-mutable parsed si.signedAttrs instead would let a
|
|
428
444
|
// parsed-object input desync the checked attributes from the verified preimage.
|
|
429
445
|
var reTagged = Buffer.from(si.signedAttrsBytes);
|
|
@@ -462,7 +478,7 @@ function _computeSignedBytes(si, content, eContentType) {
|
|
|
462
478
|
// (sec. 3.4, defense-in-depth over the parse-time _PARAMS_ABSENT gate), the unsupported-arm refusal,
|
|
463
479
|
// and the sec. 3.4/sec. 5 coherence gate binding the SignerInfo digestAlgorithm to the arm's Table-1
|
|
464
480
|
// pre-hash. It reuses the sec. 5.4 preimage + message-digest binding UNCHANGED, then dispatches
|
|
465
|
-
// compositeVerify per candidate certificate (
|
|
481
|
+
// compositeVerify per candidate certificate (both components must verify, with no AND->OR
|
|
466
482
|
// downgrade).
|
|
467
483
|
function _verifyComposite(si, comp, content, eContentType, parsedCerts, csTarget) {
|
|
468
484
|
if (si.signatureAlgorithm.parameters !== null && si.signatureAlgorithm.parameters !== undefined) {
|
|
@@ -474,8 +490,8 @@ function _verifyComposite(si, comp, content, eContentType, parsedCerts, csTarget
|
|
|
474
490
|
return Promise.resolve({ ok: false, code: "cms/unsupported-algorithm", sid: si.sid, message: "composite " + comp.name + ": " + comp.trad.unsupported });
|
|
475
491
|
}
|
|
476
492
|
// draft sec. 3.4: for a composite SignerInfo the digestAlgorithm parameters MUST be OMITTED
|
|
477
|
-
// (id-sha256/id-sha512 and id-shake256)
|
|
478
|
-
// (which the classical/ML-DSA paths follow), because the composite draft explicitly requires the
|
|
493
|
+
// (id-sha256/id-sha512 and id-shake256). That is stricter than the generic RFC 5754 absent-OR-NULL
|
|
494
|
+
// rule (which the classical/ML-DSA paths follow), because the composite draft explicitly requires the
|
|
479
495
|
// field omitted. A present parameter -- even a DER NULL -- is non-conformant and fails closed
|
|
480
496
|
// (it is outside the signed preimage, so a parser differential must not verify).
|
|
481
497
|
var dp = si.digestAlgorithm.parameters;
|
|
@@ -499,7 +515,7 @@ function _verifyComposite(si, comp, content, eContentType, parsedCerts, csTarget
|
|
|
499
515
|
});
|
|
500
516
|
}
|
|
501
517
|
|
|
502
|
-
// Dispatch compositeVerify against
|
|
518
|
+
// Dispatch compositeVerify against each matching candidate; the signer is valid iff one candidate
|
|
503
519
|
// verifies both components. A candidate whose SPKI composite OID != the SignerInfo signatureAlgorithm
|
|
504
520
|
// OID is skipped with a precise verdict (RFC 9814 sec. 4 key<->signature agreement); a candidate that
|
|
505
521
|
// returns a structural fault code (bad split length, unsupported) falls through, its code surfaced if
|
|
@@ -526,9 +542,10 @@ function _verifyCompositeAgainstCandidates(comp, sigBytes, signedBytes, sid, can
|
|
|
526
542
|
return attempt(0);
|
|
527
543
|
}
|
|
528
544
|
|
|
529
|
-
// The decoded unsigned attributes of a SignerInfo, surfaced UNAUTHENTICATED (they are outside
|
|
530
|
-
// signature) with each OID resolved to a name
|
|
531
|
-
// token or inspect a countersignature attribute.
|
|
545
|
+
// The decoded unsigned attributes of a SignerInfo, surfaced UNAUTHENTICATED (they are outside
|
|
546
|
+
// the signature) with each OID resolved to a name, so a caller can read an attached RFC 3161
|
|
547
|
+
// timestamp token or inspect a countersignature attribute. They are never counted toward a
|
|
548
|
+
// signer's ok / res.valid.
|
|
532
549
|
function _surfaceUnsignedAttrs(si) {
|
|
533
550
|
return (si.unsignedAttrs || []).map(function (a) {
|
|
534
551
|
return { type: a.type, typeName: oid.name(a.type) || null, values: a.values };
|
|
@@ -537,17 +554,17 @@ function _surfaceUnsignedAttrs(si) {
|
|
|
537
554
|
|
|
538
555
|
// Verify every countersignature attached to `si` (RFC 5652 sec. 11.4): each id-countersignature
|
|
539
556
|
// value is a SignerInfo over `si`'s signature octets. Returns per-countersignature verdicts; a
|
|
540
|
-
// countersignature's
|
|
541
|
-
// countersignature that
|
|
542
|
-
// allowed to change the primary verdict. A countersignature value that is not a well-formed
|
|
557
|
+
// countersignature's own countersignatures verify over its signature octets (recursive). A
|
|
558
|
+
// countersignature that fails to verify is surfaced ok:false. It is never silently dropped, and
|
|
559
|
+
// never allowed to change the primary verdict. A countersignature value that is not a well-formed
|
|
543
560
|
// SignerInfo does not reach here at all: the decoder validates every id-countersignature value by
|
|
544
|
-
// content
|
|
545
|
-
//
|
|
546
|
-
//
|
|
547
|
-
//
|
|
561
|
+
// its content, so such a message is refused whole. The recursion terminates because it only walks
|
|
562
|
+
// the FINITE parsed structure: each nested countersignature value is a sub-encoding of its parent,
|
|
563
|
+
// and the strict decoder already bounds total nesting by C.LIMITS.DER_MAX_DEPTH at parse
|
|
564
|
+
// (CWE-834/770), so a hostile deep chain fails closed before verify.
|
|
548
565
|
function _verifyCountersignatures(si, parsedCerts) {
|
|
549
566
|
// si.signature is always a Buffer from the strict parser (and from walkCountersignature on the
|
|
550
|
-
// recursive path), so _toBuf is a pass-through here
|
|
567
|
+
// recursive path), so _toBuf is a pass-through here. The SignerInfo is already well-formed.
|
|
551
568
|
var targetSig = _toBuf(si.signature, "the countersigned signature");
|
|
552
569
|
var values = [];
|
|
553
570
|
(si.unsignedAttrs || []).forEach(function (a) {
|
|
@@ -559,6 +576,14 @@ function _verifyCountersignatures(si, parsedCerts) {
|
|
|
559
576
|
|
|
560
577
|
function _verifyOneCountersig(vDer, targetSig, parsedCerts) {
|
|
561
578
|
var csSi;
|
|
579
|
+
// Coverage residual, and deliberately kept. Every value reaching here was already walked by the
|
|
580
|
+
// decoder, which validates an id-countersignature by content and not by the attribute type. A
|
|
581
|
+
// malformed one therefore refused the whole message before verify was entered, so this catch
|
|
582
|
+
// cannot be reached through the public path. That is settled behaviour: a message the decoder
|
|
583
|
+
// has found to be malformed has no sound remainder to report a verdict over, so it is refused
|
|
584
|
+
// whole instead of surfaced as one failed countersignature.
|
|
585
|
+
// cms-verify.test.js pins that through the shipped verbs; this stays because the walk is also
|
|
586
|
+
// reachable recursively and a backstop that returns a NEGATIVE verdict costs nothing.
|
|
562
587
|
try { csSi = cms.walkCountersignature(asn1.decode(Buffer.isBuffer(vDer) ? vDer : Buffer.from(vDer))); }
|
|
563
588
|
catch (e) { return Promise.resolve({ ok: false, code: (e instanceof CmsError ? e.code : "cms/bad-countersignature"), message: e && e.message }); }
|
|
564
589
|
return _verifyOne(csSi, targetSig, null, parsedCerts, targetSig).then(function (verdict) {
|
|
@@ -574,7 +599,7 @@ function _verifyOneCountersig(vDer, targetSig, parsedCerts) {
|
|
|
574
599
|
|
|
575
600
|
/**
|
|
576
601
|
* @primitive pki.cms.verify
|
|
577
|
-
* @signature pki.cms.verify(input, opts?) -> Promise<{ valid, trusted, signers }>
|
|
602
|
+
* @signature pki.cms.verify(input, opts?) -> Promise<{ valid, trusted, eContentType, signers }>
|
|
578
603
|
* @since 0.2.14
|
|
579
604
|
* @status stable
|
|
580
605
|
* @spec RFC 5652
|
|
@@ -584,31 +609,38 @@ function _verifyOneCountersig(vDer, targetSig, parsedCerts) {
|
|
|
584
609
|
* @related pki.schema.cms.parse, pki.path.validate
|
|
585
610
|
*
|
|
586
611
|
* Verify a CMS SignedData signature (RFC 5652 sec. 5). `input` is a PEM string, a DER
|
|
587
|
-
* `Buffer`, or a parsed `pki.schema.cms` object. Returns `{ valid, trusted, signers }`
|
|
588
|
-
* `signers[i]` is `{ ok, sid, cert, trusted }` (`cert` the
|
|
589
|
-
* a `code` on a structural failure; `valid` is true when
|
|
590
|
-
* every signer verified.
|
|
612
|
+
* `Buffer`, or a parsed `pki.schema.cms` object. Returns `{ valid, trusted, eContentType, signers }`
|
|
613
|
+
* where each `signers[i]` is `{ ok, sid, cert, trusted, signedAttributesPresent }` (`cert` the
|
|
614
|
+
* matched signer certificate DER) or carries a `code` on a structural failure; `valid` is true when
|
|
615
|
+
* there is at least one signer and every signer verified.
|
|
616
|
+
*
|
|
617
|
+
* `eContentType` and `signedAttributesPresent` are there for a caller whose profile is stricter
|
|
618
|
+
* than RFC 5652's. Signing WITH attributes and signing the content directly are different claims --
|
|
619
|
+
* attributes bind a content type and a signing time alongside the digest, content-only binds
|
|
620
|
+
* nothing but the bytes -- and one message may carry a signer of each. A profile that requires
|
|
621
|
+
* attributes (RFC 8551 S/MIME does) or a particular content type can enforce it from the verdict,
|
|
622
|
+
* with no need to parse the message a second time.
|
|
591
623
|
*
|
|
592
624
|
* `valid` and `trusted` are DIFFERENT claims and neither implies the other. A SignedData carries
|
|
593
|
-
* its own certificates, so `valid` establishes that the message is internally consistent
|
|
625
|
+
* its own certificates, so `valid` establishes that the message is internally consistent: the
|
|
594
626
|
* signature is sound under a certificate the message or `opts.certs` supplied. Anyone can mint a
|
|
595
627
|
* certificate, sign with it, and embed it, so that says nothing about WHO signed. `trusted` says
|
|
596
628
|
* every signer chained to a root named in `opts.trustAnchors`, validated through the same RFC 5280
|
|
597
629
|
* path engine `pki.path.validate` uses. Without anchors there is nothing to chain to and `trusted`
|
|
598
630
|
* is `false` -- a definite answer, not a missing one. Anchors that cannot be read are a
|
|
599
|
-
* configuration fault and throw
|
|
600
|
-
*
|
|
631
|
+
* configuration fault and throw. Absorbing them into `trusted: false` would report a verdict
|
|
632
|
+
* about the message for a check that never ran.
|
|
601
633
|
*
|
|
602
|
-
* Trust is decided from the certificate the SignerInfo selected
|
|
603
|
-
* `signers[i].cert
|
|
634
|
+
* Trust is decided from the certificate the SignerInfo selected, the one reported as
|
|
635
|
+
* `signers[i].cert`, never from another certificate that happens to share its key. A
|
|
604
636
|
* `subjectKeyIdentifier` names a key, and several certificates can hold it with different
|
|
605
637
|
* validity windows, key usage and policies; deciding from a sibling would let an expired or
|
|
606
638
|
* wrong-purpose signer certificate be reported trusted because a different certificate chained.
|
|
607
639
|
* Supply the certificate you want used. RSA (PKCS#1 v1.5 and RSASSA-PSS), ECDSA, EdDSA, and the post-quantum
|
|
608
|
-
* ML-DSA (ML-DSA-44/65/87, RFC 9882) and SLH-DSA (the twelve FIPS 205 sets, RFC 9814)
|
|
609
|
-
*
|
|
610
|
-
* (draft-ietf-lamps-cms-composite-sigs), which pairs ML-DSA with a traditional
|
|
611
|
-
* and verifies only when
|
|
640
|
+
* ML-DSA (ML-DSA-44/65/87, RFC 9882) and SLH-DSA (the twelve FIPS 205 sets, RFC 9814) signatures
|
|
641
|
+
* are recognized, the post-quantum families in pure mode with an empty context, as is composite
|
|
642
|
+
* ML-DSA (draft-ietf-lamps-cms-composite-sigs), which pairs ML-DSA with a traditional
|
|
643
|
+
* RSA / ECDSA / EdDSA and verifies only when both components pass (never an AND-to-OR downgrade).
|
|
612
644
|
*
|
|
613
645
|
* @opts content The detached content (a `Buffer`) when the SignedData carries no
|
|
614
646
|
* encapsulated eContent. Required for a detached signature.
|
|
@@ -620,7 +652,7 @@ function _verifyOneCountersig(vDer, targetSig, parsedCerts) {
|
|
|
620
652
|
* @opts time The instant to validate the signer's chain at (default now). Only read when
|
|
621
653
|
* `trustAnchors` is supplied.
|
|
622
654
|
* @opts requiredEku Key purposes the SIGNER certificate must carry, as OID names or dotted OIDs.
|
|
623
|
-
* @opts checkPurpose The purpose the ANCHOR's own trust metadata must permit
|
|
655
|
+
* @opts checkPurpose The purpose the ANCHOR's own trust metadata must permit. This is a separate
|
|
624
656
|
* question from `requiredEku`, since a root distributed with NSS trust bits can be
|
|
625
657
|
* marked untrusted for one purpose and good for another. Those bits and
|
|
626
658
|
* `distrustAfter` are consulted only when this names a purpose.
|
|
@@ -721,11 +753,25 @@ function _verify(input, opts) {
|
|
|
721
753
|
return _verifyCountersignatures(si, parsedCerts).then(function (countersignatures) {
|
|
722
754
|
verdict.countersignatures = countersignatures;
|
|
723
755
|
verdict.unsignedAttrs = _surfaceUnsignedAttrs(si);
|
|
756
|
+
// Whether THIS signer signed attributes or signed the content directly. The two are
|
|
757
|
+
// different claims -- attributes bind a content type and a signing time alongside the
|
|
758
|
+
// digest, and content-only binds nothing but the bytes -- and RFC 5652 lets a message carry
|
|
759
|
+
// one signer of each. A caller whose profile requires attributes (RFC 8551 S/MIME does) can
|
|
760
|
+
// only enforce it if the verdict says which they got.
|
|
761
|
+
verdict.signedAttributesPresent = !!si.signedAttrsBytes;
|
|
724
762
|
return verdict;
|
|
725
763
|
});
|
|
726
764
|
});
|
|
727
765
|
})).then(function (signers) {
|
|
728
|
-
|
|
766
|
+
// The content type travels with the verdict. An operator applying a policy of their own -- "I
|
|
767
|
+
// only accept id-data", or a profile that names its own type -- otherwise had to parse the
|
|
768
|
+
// message a second time to learn it, and a check that needs a second parse is a check most
|
|
769
|
+
// callers will not write.
|
|
770
|
+
var res = {
|
|
771
|
+
valid: signers.length > 0 && signers.every(function (s) { return s.ok === true; }),
|
|
772
|
+
eContentType: parsed.encapContentInfo.eContentType,
|
|
773
|
+
signers: signers,
|
|
774
|
+
};
|
|
729
775
|
return _applyTrust(res, parsedCerts, trustCfg).then(function () { return res; });
|
|
730
776
|
});
|
|
731
777
|
}
|
|
@@ -957,7 +1003,7 @@ function _addCert(out, der) {
|
|
|
957
1003
|
* @spec RFC 9814
|
|
958
1004
|
* @related pki.cms.verify, pki.schema.cms.parse
|
|
959
1005
|
*
|
|
960
|
-
* Produce a CMS SignedData (RFC 5652 sec. 5) over `content` (a `Buffer`)
|
|
1006
|
+
* Produce a CMS SignedData (RFC 5652 sec. 5) over `content` (a `Buffer`): the structure
|
|
961
1007
|
* S/MIME signed mail, RFC 3161 timestamp tokens, and code signing rest on, and exactly what
|
|
962
1008
|
* `pki.cms.verify` consumes and OpenSSL `cms -verify` validates. Each `signers[i]` is
|
|
963
1009
|
* `{ cert, key, digestAlgorithm?, pss? }`: `cert` the signer certificate (PEM or DER), `key`
|
|
@@ -967,9 +1013,10 @@ function _addCert(out, der) {
|
|
|
967
1013
|
* message-digest attribute is bound to the content digest and the signature is over the
|
|
968
1014
|
* canonical DER SET OF SignedAttributes; otherwise over the content directly. RSA (PKCS#1 v1.5
|
|
969
1015
|
* and, with `pss`, RSASSA-PSS), ECDSA (P-256/384/521), Ed25519, Ed448, and the post-quantum ML-DSA
|
|
970
|
-
* (ML-DSA-44/65/87, RFC 9882) and SLH-DSA (the twelve FIPS 205 sets, RFC 9814
|
|
971
|
-
* pinned per parameter set) are covered, as is composite ML-DSA
|
|
972
|
-
*
|
|
1016
|
+
* (ML-DSA-44/65/87, RFC 9882) and SLH-DSA (the twelve FIPS 205 sets, RFC 9814, with the message
|
|
1017
|
+
* digest pinned per parameter set) are covered, as is composite ML-DSA
|
|
1018
|
+
* (draft-ietf-lamps-cms-composite-sigs), where the arm's pre-hash fixes the digestAlgorithm and
|
|
1019
|
+
* the two component keys are signed together.
|
|
973
1020
|
*
|
|
974
1021
|
* @opts detached Omit the encapsulated content (a detached signature; the verifier
|
|
975
1022
|
* supplies the content). Default false.
|
|
@@ -982,7 +1029,7 @@ function _addCert(out, der) {
|
|
|
982
1029
|
* @opts pem Return a PEM string (`-----BEGIN CMS-----`) instead of a DER Buffer.
|
|
983
1030
|
* @opts unsignedAttributes Unsigned attributes for every SignerInfo, each `{ type, values }`
|
|
984
1031
|
* (`type` an OID name or dotted string, `values` an array of DER value
|
|
985
|
-
* `Buffer`s). Placed in the SignerInfo `[1]` unsignedAttrs
|
|
1032
|
+
* `Buffer`s). Placed in the SignerInfo `[1]` unsignedAttrs, outside the
|
|
986
1033
|
* signature, so they carry no cryptographic assurance and a verifier never
|
|
987
1034
|
* reports them authenticated. The vehicle for an RFC 3161 timestamp token
|
|
988
1035
|
* (`timeStampToken`); content-type / message-digest / signing-time are
|
|
@@ -1008,12 +1055,12 @@ var sign = cmsSign.sign;
|
|
|
1008
1055
|
*
|
|
1009
1056
|
* Add one or more countersignatures to an existing CMS SignedData (RFC 5652 sec. 11.4). A
|
|
1010
1057
|
* countersignature is itself a `SignerInfo` whose signature covers the CONTENTS of the countersigned
|
|
1011
|
-
* SignerInfo's signature value (
|
|
1058
|
+
* SignerInfo's signature value (not the content), attached as the `id-countersignature` unsigned
|
|
1012
1059
|
* attribute -- the construction Authenticode timestamps and signature-attestation workflows rest on.
|
|
1013
|
-
* `cms` is the SignedData (a DER `Buffer` or a PEM `CMS` string
|
|
1060
|
+
* `cms` is the SignedData (a DER `Buffer` or a PEM `CMS` string; the wire bytes are preserved, so
|
|
1014
1061
|
* the countersigned primary still verifies byte-for-byte). Each `signers[i]` is the same descriptor
|
|
1015
|
-
* `pki.cms.sign` takes
|
|
1016
|
-
* EdDSA / ML-DSA / SLH-DSA / composite key
|
|
1062
|
+
* `pki.cms.sign` takes (`{ cert, key, digestAlgorithm?, pss? }`, any RSA / RSASSA-PSS / ECDSA /
|
|
1063
|
+
* EdDSA / ML-DSA / SLH-DSA / composite key), and countersigns over the target signature octets;
|
|
1017
1064
|
* `pki.cms.verify` returns each countersignature verdict under `signers[i].countersignatures`. A
|
|
1018
1065
|
* countersignature never carries a content-type attribute (sec. 11.4); multiple countersignatures on
|
|
1019
1066
|
* one signer are multiple values of the one id-countersignature attribute.
|
|
@@ -1022,7 +1069,7 @@ var sign = cmsSign.sign;
|
|
|
1022
1069
|
* array of indices, or `"all"`.
|
|
1023
1070
|
* @opts countersignatureOf Countersign the Nth existing countersignature of the target signer
|
|
1024
1071
|
* instead of the primary signature (a nested countersignature).
|
|
1025
|
-
* @opts signedAttributes Include signed attributes (message-digest + signing-time;
|
|
1072
|
+
* @opts signedAttributes Include signed attributes (message-digest + signing-time; never a
|
|
1026
1073
|
* content-type). Default true; false signs the target signature octets
|
|
1027
1074
|
* directly.
|
|
1028
1075
|
* @opts signingTime A `Date` for the countersignature's signing-time, or false to omit it.
|
|
@@ -1051,11 +1098,11 @@ var countersign = cmsSign.countersign;
|
|
|
1051
1098
|
* @spec RFC 5652, RFC 5083, RFC 5084, RFC 3560, RFC 5753, RFC 8418, RFC 9629, RFC 9936, RFC 3211, RFC 8018
|
|
1052
1099
|
* @related pki.cms.decrypt, pki.schema.cms.parse
|
|
1053
1100
|
*
|
|
1054
|
-
* Encrypt content as a CMS EnvelopedData (CBC content), AuthEnvelopedData (AEAD content
|
|
1101
|
+
* Encrypt content as a CMS EnvelopedData (CBC content), AuthEnvelopedData (AEAD content, the
|
|
1055
1102
|
* default), or EncryptedData. `recipients` is an ARRAY of recipient descriptors for the enveloped
|
|
1056
|
-
* family, each wrapping the
|
|
1103
|
+
* family, each wrapping the same fresh content-encryption key: `{ cert }` auto-dispatches off the
|
|
1057
1104
|
* recipient certificate's public-key algorithm -- RSA yields a ktri with RSAES-OAEP-SHA256 (PKCS#1
|
|
1058
|
-
* v1.5 is
|
|
1105
|
+
* v1.5 is never emitted); EC P-256/384/521 a kari with ephemeral-static ECDH and the X9.63 KDF;
|
|
1059
1106
|
* X25519/X448 a kari per RFC 8418 (HKDF); ML-KEM-512/768/1024 an ori/KEMRecipientInfo per RFC
|
|
1060
1107
|
* 9629 + 9936. `{ password }` yields a pwri (PBKDF2 + RFC 3211 PWRI-KEK); `{ kek, kekId }` a kekri
|
|
1061
1108
|
* (AES key wrap). For EncryptedData (no RecipientInfos), pass a single non-array `{ cek }` or
|
|
@@ -1089,8 +1136,8 @@ var encrypt = cmsEncrypt.encrypt;
|
|
|
1089
1136
|
* @related pki.cms.decrypt, pki.schema.cms.parse
|
|
1090
1137
|
*
|
|
1091
1138
|
* Produce a CMS AuthenticatedData (RFC 5652 sec. 9): CLEARTEXT content plus a MAC (HMAC-SHA-2) --
|
|
1092
|
-
* authenticated but
|
|
1093
|
-
* fresh MAC key is minted and wrapped for every recipient with the
|
|
1139
|
+
* authenticated but not encrypted, the authenticated-only sibling of AuthEnvelopedData. A single
|
|
1140
|
+
* fresh MAC key is minted and wrapped for every recipient with the same RecipientInfo model
|
|
1094
1141
|
* `pki.cms.encrypt` uses for a content-encryption key, so `recipients` is the identical array of
|
|
1095
1142
|
* descriptors: `{ cert }` (RSA -> ktri RSAES-OAEP, EC/X25519/X448 -> kari, ML-KEM -> ori/KEMRI),
|
|
1096
1143
|
* `{ password }` (pwri), or `{ kek, kekId }` (kekri). By default the MAC covers the authenticated
|
|
@@ -1124,7 +1171,7 @@ var authenticate = cmsEncrypt.authenticate;
|
|
|
1124
1171
|
|
|
1125
1172
|
/**
|
|
1126
1173
|
* @primitive pki.cms.decrypt
|
|
1127
|
-
* @signature pki.cms.decrypt(input, keyMaterial, opts?) -> Promise<{ content, contentType, contentTypeName, recipientType, recipientIndex, contentEncryptionAlgorithm, authenticated }>
|
|
1174
|
+
* @signature pki.cms.decrypt(input, keyMaterial, opts?) -> Promise<{ content, contentType, contentTypeName, recipientType, recipientIndex, contentEncryptionAlgorithm, authenticated, authenticatedBy, originAuthenticated, originatorInfo }>
|
|
1128
1175
|
* @since 0.2.23
|
|
1129
1176
|
* @status stable
|
|
1130
1177
|
* @spec RFC 5652, RFC 5083, RFC 5084, RFC 3560, RFC 5753, RFC 8418, RFC 9629, RFC 9936, RFC 3211, RFC 8018, RFC 3218, RFC 2104
|
|
@@ -1136,16 +1183,28 @@ var authenticate = cmsEncrypt.authenticate;
|
|
|
1136
1183
|
* ECDH / X25519 / X448; kekri; pwri; ori/ML-KEM), and decrypts (or MAC-verifies) the content.
|
|
1137
1184
|
* `keyMaterial` is `{ key, cert }` (the recipient private key + its certificate, which drives recipient
|
|
1138
1185
|
* matching), `{ password }`, `{ kek, kekId? }`, or `{ cek }` (EncryptedData raw-key mode). Fail-closed
|
|
1139
|
-
* and oracle-free: every secret-dependent failure
|
|
1140
|
-
*
|
|
1141
|
-
*
|
|
1142
|
-
*
|
|
1143
|
-
* countermeasure so its failure is indistinguishable. For an AuthenticatedData the MAC (HMAC-SHA-2)
|
|
1186
|
+
* and oracle-free: every secret-dependent failure collapses to the SINGLE uniform
|
|
1187
|
+
* `cms/decrypt-failed` verdict (Bleichenbacher / EFAIL oracle freedom, and no unwrap-success bit
|
|
1188
|
+
* for the MAC path). That covers a bad key-wrap, a padding fault, a GCM tag mismatch, a PWRI
|
|
1189
|
+
* check-byte mismatch, and an AuthenticatedData MAC/message-digest mismatch. The PKCS#1 v1.5 arm
|
|
1190
|
+
* applies the RFC 3218 implicit-rejection countermeasure so its failure is indistinguishable. For an AuthenticatedData the MAC (HMAC-SHA-2)
|
|
1144
1191
|
* and, when authenticated attributes are present, the message-digest are verified before the content
|
|
1145
1192
|
* is released, and the result carries `macAlgorithm` / `digestAlgorithm` in place of
|
|
1146
1193
|
* `contentEncryptionAlgorithm`. `authenticated` is true for AuthEnvelopedData and AuthenticatedData; a
|
|
1147
1194
|
* CBC EnvelopedData surfaces `authenticated: false` (the EFAIL caveat in the verdict itself).
|
|
1148
1195
|
*
|
|
1196
|
+
* `authenticated` is a claim about the CONTENT and the key that opened it, never about who sent the
|
|
1197
|
+
* message, so the origin question is answered separately. `originAuthenticated` is `false` for every
|
|
1198
|
+
* recipient type: a `ktri` or ephemeral-static `kari` message is minted by anyone holding the recipient's
|
|
1199
|
+
* PUBLIC key, and a `pwri` or `kekri` message by any co-recipient sharing the secret. Read
|
|
1200
|
+
* `authenticated: true` as "these bytes were not altered after the key was chosen", and do not read it as
|
|
1201
|
+
* a sender identity -- to bind one, verify a signature (`pki.cms.verify`) over the plaintext.
|
|
1202
|
+
* `authenticatedBy` names what the integrity rests on (`"content-encryption-key"` for AEAD,
|
|
1203
|
+
* `"message-authentication-key"` for AuthenticatedData, `null` for an unauthenticated cipher).
|
|
1204
|
+
* `originatorInfo` is surfaced as the sender supplied it and is UNAUTHENTICATED: it sits outside the
|
|
1205
|
+
* AEAD's authenticated data, so it is a hint, never evidence, and any certificate it carries must be
|
|
1206
|
+
* validated before use.
|
|
1207
|
+
*
|
|
1149
1208
|
* @opts recipientIndex Explicitly select the recipient by index (overrides key-material matching).
|
|
1150
1209
|
* @opts maxIterations Lower the PBKDF2 iteration cap (a DoS bound; downward only).
|
|
1151
1210
|
* @example
|
|
@@ -1171,7 +1230,7 @@ var decrypt = cmsDecrypt.decrypt;
|
|
|
1171
1230
|
* ZLIB-compress `content` and wrap it as a CMS `CompressedData` ContentInfo (RFC 3274): version 0,
|
|
1172
1231
|
* `compressionAlgorithm` = id-alg-zlibCompress (parameters omitted), `encapContentInfo` = the inner
|
|
1173
1232
|
* content type plus the RFC 1950 ZLIB stream (RFC 1951 DEFLATE) as the eContent. There is exactly
|
|
1174
|
-
*
|
|
1233
|
+
* one compression algorithm in RFC 3274, ZLIB, so there is no algorithm selector. CompressedData
|
|
1175
1234
|
* is a size transform with NO integrity, confidentiality, or authentication (RFC 8551 sec. 2.4.5);
|
|
1176
1235
|
* compress it, then sign or encrypt it if you need protection. Malformed input throws a typed `CmsError`.
|
|
1177
1236
|
*
|
|
@@ -1194,7 +1253,7 @@ var compress = cmsCompress.compress;
|
|
|
1194
1253
|
* Parse a CMS `CompressedData` (DER Buffer or PEM) and recover its content. It requires version 0,
|
|
1195
1254
|
* `compressionAlgorithm` = id-alg-zlibCompress with absent-or-NULL parameters (RFC 3274 sec. 2), and a
|
|
1196
1255
|
* present eContent, then inflates the RFC 1950 ZLIB stream. The inflate is BOUNDED at
|
|
1197
|
-
* `C.LIMITS.COMPRESS_MAX_BYTES` (16 MiB) and stops before the output is materialized
|
|
1256
|
+
* `C.LIMITS.COMPRESS_MAX_BYTES` (16 MiB) and stops before the output is materialized, a
|
|
1198
1257
|
* decompression-bomb / resource-exhaustion defense (CWE-409): a cap breach throws
|
|
1199
1258
|
* `cms/decompress-too-large`, and every malformed / truncated / corrupt stream collapses to the uniform
|
|
1200
1259
|
* `cms/decompress-failed`. `opts.maxOutputBytes` tightens the cap DOWNWARD only. The verdict carries NO
|
package/lib/composite-sig.js
CHANGED
|
@@ -13,9 +13,9 @@
|
|
|
13
13
|
// mldsaSig || tradSig (sec. 4.3). Verification (sec. 2) reconstructs
|
|
14
14
|
// M' = Prefix || Label || len(ctx) || ctx || PH(M),
|
|
15
15
|
// verifies the ML-DSA component over M' with ctx = the composite Label, verifies the traditional
|
|
16
|
-
// component over M' under its own hash, and accepts
|
|
17
|
-
//
|
|
18
|
-
//
|
|
16
|
+
// component over M' under its own hash, and accepts if and only if both pass. Every component must
|
|
17
|
+
// verify; there is no AND-to-OR downgrade. The ML-DSA component is the fixed-length first half, and
|
|
18
|
+
// the split point is its length.
|
|
19
19
|
//
|
|
20
20
|
// Error-parameterized like the guard / validator families: each consumer passes its own typed
|
|
21
21
|
// error CONSTRUCTOR `E` and its domain codes (path validation passes PathError + path/*; CMS
|
|
@@ -30,8 +30,8 @@ var edwardsPoint = require("./edwards-point");
|
|
|
30
30
|
var subtle = webcrypto.webcrypto.subtle;
|
|
31
31
|
var _b = asn1.build;
|
|
32
32
|
|
|
33
|
-
// A caught error's
|
|
34
|
-
// e.g. path/ or cms/), else the fallback
|
|
33
|
+
// A caught error's own code if it shares the caller's domain prefix (the fallback's namespace,
|
|
34
|
+
// e.g. path/ or cms/), else the fallback. A foreign code (asn1/*) maps to the fallback so the
|
|
35
35
|
// verdict stays in the caller's error namespace. Mirrors path-validate's original pathCode when
|
|
36
36
|
// the fallback is a path/* code.
|
|
37
37
|
function _codeOf(e, fallback) {
|
|
@@ -55,8 +55,8 @@ var COMPOSITE_ALGS = {};
|
|
|
55
55
|
// _comp(name, mldsa, ph, label, trad). `trad` is exactly one component shape:
|
|
56
56
|
// { ec, hash } | { eddsa } | { rsaPss, hash, salt } | { rsaPkcs1, hash } |
|
|
57
57
|
// { unsupported } for the arms Node's WebCrypto surface cannot verify (the two
|
|
58
|
-
// brainpool curves)
|
|
59
|
-
// caller's unsupported-algorithm code
|
|
58
|
+
// brainpool curves): registered and params-guarded, deferred at verify to the
|
|
59
|
+
// caller's unsupported-algorithm code instead of silently accepted.
|
|
60
60
|
function _comp(name, mldsa, ph, label, trad) {
|
|
61
61
|
var sz = MLDSA_COMPONENT[mldsa];
|
|
62
62
|
COMPOSITE_ALGS[oid.byName(name)] = {
|
|
@@ -84,7 +84,7 @@ _comp("id-MLDSA87-RSA4096-PSS-SHA512", "ML-DSA-87", "SHA-512", "COMPSIG-MLDSA87-
|
|
|
84
84
|
_comp("id-MLDSA87-ECDSA-P521-SHA512", "ML-DSA-87", "SHA-512", "COMPSIG-MLDSA87-ECDSA-P521-SHA512", { ec: "P-521", hash: "SHA-512" });
|
|
85
85
|
|
|
86
86
|
// Wrap a raw component public key in the SPKI its WebCrypto import expects, so each half is
|
|
87
|
-
// verified through the
|
|
87
|
+
// verified through the same import and verify seam the classical path uses (no second parallel
|
|
88
88
|
// verify path). tradPK for RSA is the RSAPublicKey DER; for EC the uncompressed point; for EdDSA
|
|
89
89
|
// the raw public key.
|
|
90
90
|
function _spkiFor(algNode, keyBytes) { return _b.sequence([algNode, _b.bitString(keyBytes, 0)]); }
|
|
@@ -120,10 +120,10 @@ function _verifyTradComponent(trad, tradPK, tradSig, mprime, E, badSig) {
|
|
|
120
120
|
}
|
|
121
121
|
if (trad.eddsa) {
|
|
122
122
|
// node/OpenSSL imports any Ed25519/Ed448 SPKI without validating the point, and a low-order
|
|
123
|
-
// (e.g. all-zeroes) key verifies a forged signature
|
|
124
|
-
//
|
|
123
|
+
// (e.g. all-zeroes) key verifies a forged signature, collapsing the composite conjunction to
|
|
124
|
+
// ML-DSA alone. Reject a non-full-order point before verify, through the same shared
|
|
125
125
|
// edwards-point gate every other EdDSA verify path in the toolkit routes through, surfacing a
|
|
126
|
-
// coded fault (like the ECDSA order-bound belt)
|
|
126
|
+
// coded fault (like the ECDSA order-bound belt) instead of a silent false.
|
|
127
127
|
if (!edwardsPoint.validate(tradPK, trad.eddsa === "Ed25519" ? 6 : 7)) {
|
|
128
128
|
throw new E(badSig, "the composite EdDSA component public key is not a valid, full-order Edwards point");
|
|
129
129
|
}
|
|
@@ -132,7 +132,7 @@ function _verifyTradComponent(trad, tradPK, tradSig, mprime, E, badSig) {
|
|
|
132
132
|
}
|
|
133
133
|
if (trad.rsaPss || trad.rsaPkcs1) {
|
|
134
134
|
// The composite OID fixes the RSA modulus size: a downgraded or mismatched modulus under the
|
|
135
|
-
// declared OID (an id-MLDSA44-RSA2048-* whose component is really 1024-bit) is rejected
|
|
135
|
+
// declared OID (an id-MLDSA44-RSA2048-* whose component is really 1024-bit) is rejected before
|
|
136
136
|
// verify, so a weak RSA component cannot satisfy an arm that promises 2048/3072/4096 bits. A
|
|
137
137
|
// malformed RSAPublicKey rejects the same way.
|
|
138
138
|
var bits;
|
|
@@ -167,7 +167,7 @@ function compositeVerify(spkiBytes, sigBytes, message, d, E, unsupported, badSig
|
|
|
167
167
|
if (bs.unusedBits !== 0) throw new E(badSig, "composite subjectPublicKey has unused bits");
|
|
168
168
|
rawKey = bs.bytes;
|
|
169
169
|
} catch (e) { return Promise.resolve({ ok: false, code: _codeOf(e, badSig), error: e }); }
|
|
170
|
-
// The ML-DSA half is fixed-length and
|
|
170
|
+
// The ML-DSA half is fixed-length and comes first; the traditional half is the remainder. Both must be
|
|
171
171
|
// non-empty for a well-formed composite.
|
|
172
172
|
if (rawKey.length <= d.mldsaPk || sigBytes.length <= d.mldsaSig) {
|
|
173
173
|
return Promise.resolve({ ok: false, code: badSig,
|
package/lib/constants.js
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* Every scale helper is config-time / entry-point validation: it THROWS
|
|
20
20
|
* `ConstantsError` on a non-finite or negative argument, and on a product
|
|
21
21
|
* outside the safe-integer range, so an operator catches the typo at boot
|
|
22
|
-
*
|
|
22
|
+
* instead of shipping a silently-wrong window or an Infinity that would
|
|
23
23
|
* disable a size cap.
|
|
24
24
|
*
|
|
25
25
|
* @card
|
|
@@ -31,7 +31,7 @@ var frameworkError = require("./framework-error");
|
|
|
31
31
|
|
|
32
32
|
var ConstantsError = frameworkError.ConstantsError;
|
|
33
33
|
|
|
34
|
-
// _positive(n, who)
|
|
34
|
+
// _positive(n, who) is the shared guard every scale helper runs. Config-
|
|
35
35
|
// time tier: a bad scale argument is an authoring bug, so it throws.
|
|
36
36
|
function _positive(n, who) {
|
|
37
37
|
if (typeof n !== "number" || !isFinite(n) || n < 0) {
|
|
@@ -43,10 +43,10 @@ function _positive(n, who) {
|
|
|
43
43
|
return n;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
// _scale(n, who, factor)
|
|
46
|
+
// _scale(n, who, factor) validates the argument and the product. A finite
|
|
47
47
|
// operand can still overflow the multiplication (days(1e304) -> Infinity),
|
|
48
48
|
// and an Infinity handed onward silently disables any bound compared
|
|
49
|
-
// against it (`len > Infinity` is always false)
|
|
49
|
+
// against it (`len > Infinity` is always false), so a product outside the
|
|
50
50
|
// safe-integer range throws instead of returning.
|
|
51
51
|
function _scale(n, who, factor) {
|
|
52
52
|
var out = Math.round(_positive(n, who) * factor);
|