@blamejs/pki 0.5.7 → 0.5.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +405 -386
- package/MIGRATING.md +43 -0
- package/README.md +12 -12
- package/lib/acme.js +31 -31
- package/lib/asn1-der.js +10 -10
- package/lib/attrcert-sign.js +19 -19
- package/lib/byte-reader.js +6 -6
- package/lib/byte-writer.js +5 -5
- package/lib/cbor-det.js +27 -24
- package/lib/cmc-build.js +97 -92
- package/lib/cmc-verify.js +106 -30
- package/lib/cmp-build.js +25 -25
- package/lib/cmp-session.js +70 -70
- package/lib/cmp-verify.js +71 -57
- package/lib/cms-compress.js +7 -7
- package/lib/cms-decrypt.js +90 -72
- package/lib/cms-encrypt.js +32 -32
- package/lib/cms-sign.js +74 -55
- package/lib/cms-verify.js +97 -75
- package/lib/composite-sig.js +13 -13
- package/lib/constants.js +4 -4
- package/lib/crl-sign.js +22 -22
- package/lib/crl-verify.js +7 -6
- package/lib/crmf-sign.js +14 -14
- package/lib/csr-sign.js +8 -8
- package/lib/ct.js +37 -37
- package/lib/edwards-point.js +7 -7
- package/lib/est.js +98 -55
- package/lib/framework-error.js +5 -5
- package/lib/guard-all.js +3 -3
- package/lib/guard-async.js +4 -4
- package/lib/guard-bytes.js +79 -79
- package/lib/guard-compress.js +17 -17
- package/lib/guard-crypto.js +1 -1
- package/lib/guard-encoding.js +15 -15
- package/lib/guard-header.js +3 -3
- package/lib/guard-identifier.js +16 -16
- package/lib/guard-json.js +15 -15
- package/lib/guard-limits.js +7 -7
- package/lib/guard-name.js +81 -16
- package/lib/guard-parsed.js +80 -80
- package/lib/guard-range.js +19 -19
- package/lib/guard-secret.js +11 -10
- package/lib/guard-text.js +6 -6
- package/lib/guard-time.js +10 -10
- package/lib/hpke.js +18 -17
- package/lib/http-digest.js +35 -35
- package/lib/http-retry-after.js +13 -13
- package/lib/http-transport.js +20 -19
- package/lib/inspect.js +53 -53
- package/lib/ip-utils.js +2 -2
- package/lib/jose.js +13 -13
- package/lib/key.js +16 -16
- package/lib/lint.js +51 -51
- package/lib/merkle.js +51 -36
- package/lib/mime.js +18 -18
- package/lib/ocsp-verify.js +10 -10
- package/lib/ocsp.js +13 -13
- package/lib/oid.js +29 -29
- package/lib/path-validate.js +114 -113
- package/lib/pbes2.js +16 -16
- package/lib/pkcs12-build.js +53 -53
- package/lib/pki-build.js +21 -19
- package/lib/rc2.js +1 -1
- package/lib/rfc3339.js +5 -5
- package/lib/schema-all.js +31 -31
- package/lib/schema-attrcert.js +12 -12
- package/lib/schema-c509.js +144 -142
- package/lib/schema-cmc.js +58 -58
- package/lib/schema-cmp.js +43 -43
- package/lib/schema-cms.js +45 -45
- package/lib/schema-crl.js +7 -7
- package/lib/schema-crmf.js +28 -28
- package/lib/schema-csr.js +12 -12
- package/lib/schema-csrattrs.js +16 -16
- package/lib/schema-engine.js +18 -18
- package/lib/schema-ocsp.js +15 -15
- package/lib/schema-pkcs12.js +20 -20
- package/lib/schema-pkcs8.js +2 -2
- package/lib/schema-pkix.js +131 -126
- package/lib/schema-smime.js +19 -19
- package/lib/schema-tsp.js +12 -12
- package/lib/schema-x509.js +3 -3
- package/lib/shbs.js +18 -18
- package/lib/sign-scheme.js +13 -13
- package/lib/sigstore.js +10 -11
- package/lib/sleep.js +1 -1
- package/lib/smime.js +308 -96
- package/lib/tls-cert-compress.js +18 -18
- package/lib/trust.js +27 -27
- package/lib/tsp-sign.js +17 -17
- package/lib/validator-all.js +1 -1
- package/lib/validator-attcert.js +1 -1
- package/lib/validator-cose.js +43 -44
- package/lib/validator-keydesc.js +3 -3
- package/lib/validator-sig.js +13 -13
- package/lib/validator-tls.js +11 -11
- package/lib/validator-tpm.js +20 -19
- package/lib/webauthn-mds.js +66 -66
- package/lib/webauthn.js +33 -33
- package/lib/webcrypto.js +15 -15
- package/lib/x509-sign.js +13 -13
- package/package.json +3 -2
- package/sbom.cdx.json +6 -6
package/lib/cms-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 }; });
|
|
@@ -140,7 +140,7 @@ function _findSignerCerts(sid, parsedCerts) {
|
|
|
140
140
|
return out;
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
-
// Verify `sigBytes` over `signedBytes` against
|
|
143
|
+
// Verify `sigBytes` over `signedBytes` against each matching candidate certificate in turn; the
|
|
144
144
|
// signer is valid iff one candidate verifies. A candidate whose key is structurally unusable (a
|
|
145
145
|
// low-order EdDSA point, an unsupported curve) or whose signature simply does not verify falls
|
|
146
146
|
// through to the next -- so a colliding certificate cannot make a valid signature read invalid.
|
|
@@ -156,7 +156,7 @@ function _verifyAgainstCandidates(scheme, sigHash, sigBytes, signedBytes, sid, c
|
|
|
156
156
|
var c = candidates[idx];
|
|
157
157
|
// One-shot family (EdDSA/ML-DSA): the signer cert public-key algorithm OID MUST equal the
|
|
158
158
|
// SignerInfo signatureAlgorithm OID. A candidate whose SPKI disagrees is skipped with a precise
|
|
159
|
-
// verdict
|
|
159
|
+
// verdict; importing it under the wrong name would surface a foreign webcrypto/data throw.
|
|
160
160
|
if (expectedKeyOid && c.cert.subjectPublicKeyInfo.algorithm.oid !== expectedKeyOid) {
|
|
161
161
|
lastErr = _err("cms/unsupported-algorithm", "the signer certificate public-key algorithm does not match the SignerInfo signatureAlgorithm");
|
|
162
162
|
return attempt(idx + 1);
|
|
@@ -206,11 +206,11 @@ function _hashAlgOid(seq) {
|
|
|
206
206
|
}
|
|
207
207
|
|
|
208
208
|
// Resolve RSASSA-PSS-params (RFC 4055) to { hash, saltLength }, or null if the parameters
|
|
209
|
-
// 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
|
|
210
210
|
// hash, saltLength equal to the hash length, and trailerField 1. The SHA-1 DEFAULTs are
|
|
211
211
|
// rejected (params must be explicit), matching the certification-path validator, so a
|
|
212
|
-
// non-conformant PSS AlgorithmIdentifier is a fail-closed verdict
|
|
213
|
-
//
|
|
212
|
+
// non-conformant PSS AlgorithmIdentifier is a fail-closed verdict. Falling back to WebCrypto's
|
|
213
|
+
// own defaults would be a signatureAlgorithm bypass.
|
|
214
214
|
function _resolvePss(paramsBytes) {
|
|
215
215
|
if (!paramsBytes) return null;
|
|
216
216
|
var n;
|
|
@@ -264,7 +264,7 @@ function _verifySignature(scheme, hashName, sigBytes, spki, signedBytes, curveOi
|
|
|
264
264
|
var ec = EC_CURVE[curveOid];
|
|
265
265
|
if (!ec) throw _err("cms/unsupported-algorithm", "the signer key is on an unsupported EC curve");
|
|
266
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
|
|
267
|
-
// 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.
|
|
268
268
|
var raw = validator.sig.ecdsaDerToP1363(sigBytes, ec.curve, CmsError, "cms/bad-signature");
|
|
269
269
|
return subtle.importKey("spki", spki, { name: "ECDSA", namedCurve: ec.curve }, false, ["verify"])
|
|
270
270
|
.then(function (k) { return subtle.verify({ name: "ECDSA", hash: hashName }, k, raw, signedBytes); });
|
|
@@ -279,13 +279,13 @@ function _verifySignature(scheme, hashName, sigBytes, spki, signedBytes, curveOi
|
|
|
279
279
|
}
|
|
280
280
|
// EdDSA -- the WebCrypto name follows the signing key's SPKI OID (Ed25519 / Ed448). node/
|
|
281
281
|
// OpenSSL imports any Ed25519/Ed448 SPKI without validating the point, and a low-order (e.g.
|
|
282
|
-
// all-zeroes) key verifies a forged signature
|
|
282
|
+
// all-zeroes) key verifies a forged signature, so reject a non-full-order point first.
|
|
283
283
|
_requireValidEdPoint(spki, scheme.name);
|
|
284
284
|
return subtle.importKey("spki", spki, { name: scheme.name }, false, ["verify"])
|
|
285
285
|
.then(function (k) { return subtle.verify({ name: scheme.name }, k, sigBytes, signedBytes); });
|
|
286
286
|
}
|
|
287
287
|
|
|
288
|
-
// 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
|
|
289
289
|
// off-curve or low-order key before it verifies a signature (WebCrypto import does not check it).
|
|
290
290
|
// Routed through the shared edwards-point gate every EdDSA verify path uses; curve from the name.
|
|
291
291
|
function _requireValidEdPoint(spkiBytes, name) {
|
|
@@ -303,9 +303,9 @@ function _certCurveOid(cert) {
|
|
|
303
303
|
|
|
304
304
|
// Decode the SignedAttributes SET OF Attribute from the exact bytes the signature covers
|
|
305
305
|
// (the re-tagged signedAttrsBytes), returning [{ type, values: [valueNode] }]. Binding the
|
|
306
|
-
// content-type / message-digest checks to
|
|
307
|
-
//
|
|
308
|
-
//
|
|
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.
|
|
309
309
|
function _decodeSignedAttrs(setOfBytes) {
|
|
310
310
|
var set = asn1.decode(setOfBytes);
|
|
311
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");
|
|
@@ -317,8 +317,8 @@ function _decodeSignedAttrs(setOfBytes) {
|
|
|
317
317
|
});
|
|
318
318
|
}
|
|
319
319
|
|
|
320
|
-
// Verify one SignerInfo. Returns { ok, sid, cert, ... }
|
|
321
|
-
// 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.
|
|
322
322
|
// `csTarget`, when set (a Buffer), makes this a COUNTERSIGNATURE verify (RFC 5652 sec. 11.4): the
|
|
323
323
|
// preimage is the countersigned signature octets, not the sec. 5.4 content preimage, and no
|
|
324
324
|
// content-type attribute is required or checked (_computeCountersigBytes). content/eContentType are
|
|
@@ -343,7 +343,7 @@ function _verifyOne(si, content, eContentType, parsedCerts, csTarget) {
|
|
|
343
343
|
// The digestAlgorithm parameters, when present, MUST be a DER NULL (RFC 5754 sec. 2 accepts
|
|
344
344
|
// absent OR NULL; a present non-NULL is malformed and, being outside the signed preimage,
|
|
345
345
|
// must fail closed like the signatureAlgorithm parameters above). EXCEPT for an ML-DSA
|
|
346
|
-
// SignerInfo
|
|
346
|
+
// SignerInfo carrying no signed attributes: RFC 9882 sec. 3.3 says the whole digestAlgorithm field
|
|
347
347
|
// has no meaning there and MUST be ignored, so neither its parameters nor its name may reject.
|
|
348
348
|
var dp = si.digestAlgorithm.parameters;
|
|
349
349
|
var mldsaNoAttrs = scheme.kind === "mldsa" && !si.signedAttrsBytes;
|
|
@@ -398,7 +398,7 @@ function _computeCountersigBytes(si, csTarget) {
|
|
|
398
398
|
attrs = _decodeSignedAttrs(reTagged);
|
|
399
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); }
|
|
400
400
|
// sec. 11.4: a content-type attribute MUST NOT appear (the parser already rejects it at parse, but
|
|
401
|
-
// verify never relies on the parse alone
|
|
401
|
+
// verify never relies on the parse alone: a parsed-object input could carry one).
|
|
402
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)");
|
|
403
403
|
var mdAttr = attrs.filter(function (a) { return a.type === OID_MESSAGE_DIGEST; })[0];
|
|
404
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)");
|
|
@@ -437,9 +437,9 @@ function _computeSignedBytes(si, content, eContentType) {
|
|
|
437
437
|
}
|
|
438
438
|
return content;
|
|
439
439
|
}
|
|
440
|
-
// With signed attributes: decode them from the EXACT bytes the signature covers
|
|
441
|
-
// SignedAttributes SET OF
|
|
442
|
-
// (RFC 5652 sec. 5.4)
|
|
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
|
|
443
443
|
// that are verified. Reading the caller-mutable parsed si.signedAttrs instead would let a
|
|
444
444
|
// parsed-object input desync the checked attributes from the verified preimage.
|
|
445
445
|
var reTagged = Buffer.from(si.signedAttrsBytes);
|
|
@@ -478,7 +478,7 @@ function _computeSignedBytes(si, content, eContentType) {
|
|
|
478
478
|
// (sec. 3.4, defense-in-depth over the parse-time _PARAMS_ABSENT gate), the unsupported-arm refusal,
|
|
479
479
|
// and the sec. 3.4/sec. 5 coherence gate binding the SignerInfo digestAlgorithm to the arm's Table-1
|
|
480
480
|
// pre-hash. It reuses the sec. 5.4 preimage + message-digest binding UNCHANGED, then dispatches
|
|
481
|
-
// compositeVerify per candidate certificate (
|
|
481
|
+
// compositeVerify per candidate certificate (both components must verify, with no AND->OR
|
|
482
482
|
// downgrade).
|
|
483
483
|
function _verifyComposite(si, comp, content, eContentType, parsedCerts, csTarget) {
|
|
484
484
|
if (si.signatureAlgorithm.parameters !== null && si.signatureAlgorithm.parameters !== undefined) {
|
|
@@ -490,8 +490,8 @@ function _verifyComposite(si, comp, content, eContentType, parsedCerts, csTarget
|
|
|
490
490
|
return Promise.resolve({ ok: false, code: "cms/unsupported-algorithm", sid: si.sid, message: "composite " + comp.name + ": " + comp.trad.unsupported });
|
|
491
491
|
}
|
|
492
492
|
// draft sec. 3.4: for a composite SignerInfo the digestAlgorithm parameters MUST be OMITTED
|
|
493
|
-
// (id-sha256/id-sha512 and id-shake256)
|
|
494
|
-
// (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
|
|
495
495
|
// field omitted. A present parameter -- even a DER NULL -- is non-conformant and fails closed
|
|
496
496
|
// (it is outside the signed preimage, so a parser differential must not verify).
|
|
497
497
|
var dp = si.digestAlgorithm.parameters;
|
|
@@ -515,7 +515,7 @@ function _verifyComposite(si, comp, content, eContentType, parsedCerts, csTarget
|
|
|
515
515
|
});
|
|
516
516
|
}
|
|
517
517
|
|
|
518
|
-
// Dispatch compositeVerify against
|
|
518
|
+
// Dispatch compositeVerify against each matching candidate; the signer is valid iff one candidate
|
|
519
519
|
// verifies both components. A candidate whose SPKI composite OID != the SignerInfo signatureAlgorithm
|
|
520
520
|
// OID is skipped with a precise verdict (RFC 9814 sec. 4 key<->signature agreement); a candidate that
|
|
521
521
|
// returns a structural fault code (bad split length, unsupported) falls through, its code surfaced if
|
|
@@ -542,9 +542,10 @@ function _verifyCompositeAgainstCandidates(comp, sigBytes, signedBytes, sid, can
|
|
|
542
542
|
return attempt(0);
|
|
543
543
|
}
|
|
544
544
|
|
|
545
|
-
// The decoded unsigned attributes of a SignerInfo, surfaced UNAUTHENTICATED (they are outside
|
|
546
|
-
// signature) with each OID resolved to a name
|
|
547
|
-
// 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.
|
|
548
549
|
function _surfaceUnsignedAttrs(si) {
|
|
549
550
|
return (si.unsignedAttrs || []).map(function (a) {
|
|
550
551
|
return { type: a.type, typeName: oid.name(a.type) || null, values: a.values };
|
|
@@ -553,17 +554,17 @@ function _surfaceUnsignedAttrs(si) {
|
|
|
553
554
|
|
|
554
555
|
// Verify every countersignature attached to `si` (RFC 5652 sec. 11.4): each id-countersignature
|
|
555
556
|
// value is a SignerInfo over `si`'s signature octets. Returns per-countersignature verdicts; a
|
|
556
|
-
// countersignature's
|
|
557
|
-
// countersignature that
|
|
558
|
-
// 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
|
|
559
560
|
// SignerInfo does not reach here at all: the decoder validates every id-countersignature value by
|
|
560
|
-
// content
|
|
561
|
-
//
|
|
562
|
-
//
|
|
563
|
-
//
|
|
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.
|
|
564
565
|
function _verifyCountersignatures(si, parsedCerts) {
|
|
565
566
|
// si.signature is always a Buffer from the strict parser (and from walkCountersignature on the
|
|
566
|
-
// 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.
|
|
567
568
|
var targetSig = _toBuf(si.signature, "the countersigned signature");
|
|
568
569
|
var values = [];
|
|
569
570
|
(si.unsignedAttrs || []).forEach(function (a) {
|
|
@@ -575,6 +576,14 @@ function _verifyCountersignatures(si, parsedCerts) {
|
|
|
575
576
|
|
|
576
577
|
function _verifyOneCountersig(vDer, targetSig, parsedCerts) {
|
|
577
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.
|
|
578
587
|
try { csSi = cms.walkCountersignature(asn1.decode(Buffer.isBuffer(vDer) ? vDer : Buffer.from(vDer))); }
|
|
579
588
|
catch (e) { return Promise.resolve({ ok: false, code: (e instanceof CmsError ? e.code : "cms/bad-countersignature"), message: e && e.message }); }
|
|
580
589
|
return _verifyOne(csSi, targetSig, null, parsedCerts, targetSig).then(function (verdict) {
|
|
@@ -609,29 +618,29 @@ function _verifyOneCountersig(vDer, targetSig, parsedCerts) {
|
|
|
609
618
|
* than RFC 5652's. Signing WITH attributes and signing the content directly are different claims --
|
|
610
619
|
* attributes bind a content type and a signing time alongside the digest, content-only binds
|
|
611
620
|
* nothing but the bytes -- and one message may carry a signer of each. A profile that requires
|
|
612
|
-
* attributes (RFC 8551 S/MIME does) or a particular content type can enforce it from the verdict
|
|
613
|
-
*
|
|
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.
|
|
614
623
|
*
|
|
615
624
|
* `valid` and `trusted` are DIFFERENT claims and neither implies the other. A SignedData carries
|
|
616
|
-
* 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
|
|
617
626
|
* signature is sound under a certificate the message or `opts.certs` supplied. Anyone can mint a
|
|
618
627
|
* certificate, sign with it, and embed it, so that says nothing about WHO signed. `trusted` says
|
|
619
628
|
* every signer chained to a root named in `opts.trustAnchors`, validated through the same RFC 5280
|
|
620
629
|
* path engine `pki.path.validate` uses. Without anchors there is nothing to chain to and `trusted`
|
|
621
630
|
* is `false` -- a definite answer, not a missing one. Anchors that cannot be read are a
|
|
622
|
-
* configuration fault and throw
|
|
623
|
-
*
|
|
631
|
+
* configuration fault and throw. Absorbing them into `trusted: false` would report a verdict
|
|
632
|
+
* about the message for a check that never ran.
|
|
624
633
|
*
|
|
625
|
-
* Trust is decided from the certificate the SignerInfo selected
|
|
626
|
-
* `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
|
|
627
636
|
* `subjectKeyIdentifier` names a key, and several certificates can hold it with different
|
|
628
637
|
* validity windows, key usage and policies; deciding from a sibling would let an expired or
|
|
629
638
|
* wrong-purpose signer certificate be reported trusted because a different certificate chained.
|
|
630
639
|
* Supply the certificate you want used. RSA (PKCS#1 v1.5 and RSASSA-PSS), ECDSA, EdDSA, and the post-quantum
|
|
631
|
-
* ML-DSA (ML-DSA-44/65/87, RFC 9882) and SLH-DSA (the twelve FIPS 205 sets, RFC 9814)
|
|
632
|
-
*
|
|
633
|
-
* (draft-ietf-lamps-cms-composite-sigs), which pairs ML-DSA with a traditional
|
|
634
|
-
* 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).
|
|
635
644
|
*
|
|
636
645
|
* @opts content The detached content (a `Buffer`) when the SignedData carries no
|
|
637
646
|
* encapsulated eContent. Required for a detached signature.
|
|
@@ -643,7 +652,7 @@ function _verifyOneCountersig(vDer, targetSig, parsedCerts) {
|
|
|
643
652
|
* @opts time The instant to validate the signer's chain at (default now). Only read when
|
|
644
653
|
* `trustAnchors` is supplied.
|
|
645
654
|
* @opts requiredEku Key purposes the SIGNER certificate must carry, as OID names or dotted OIDs.
|
|
646
|
-
* @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
|
|
647
656
|
* question from `requiredEku`, since a root distributed with NSS trust bits can be
|
|
648
657
|
* marked untrusted for one purpose and good for another. Those bits and
|
|
649
658
|
* `distrustAfter` are consulted only when this names a purpose.
|
|
@@ -994,7 +1003,7 @@ function _addCert(out, der) {
|
|
|
994
1003
|
* @spec RFC 9814
|
|
995
1004
|
* @related pki.cms.verify, pki.schema.cms.parse
|
|
996
1005
|
*
|
|
997
|
-
* 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
|
|
998
1007
|
* S/MIME signed mail, RFC 3161 timestamp tokens, and code signing rest on, and exactly what
|
|
999
1008
|
* `pki.cms.verify` consumes and OpenSSL `cms -verify` validates. Each `signers[i]` is
|
|
1000
1009
|
* `{ cert, key, digestAlgorithm?, pss? }`: `cert` the signer certificate (PEM or DER), `key`
|
|
@@ -1004,9 +1013,10 @@ function _addCert(out, der) {
|
|
|
1004
1013
|
* message-digest attribute is bound to the content digest and the signature is over the
|
|
1005
1014
|
* canonical DER SET OF SignedAttributes; otherwise over the content directly. RSA (PKCS#1 v1.5
|
|
1006
1015
|
* and, with `pss`, RSASSA-PSS), ECDSA (P-256/384/521), Ed25519, Ed448, and the post-quantum ML-DSA
|
|
1007
|
-
* (ML-DSA-44/65/87, RFC 9882) and SLH-DSA (the twelve FIPS 205 sets, RFC 9814
|
|
1008
|
-
* pinned per parameter set) are covered, as is composite ML-DSA
|
|
1009
|
-
*
|
|
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.
|
|
1010
1020
|
*
|
|
1011
1021
|
* @opts detached Omit the encapsulated content (a detached signature; the verifier
|
|
1012
1022
|
* supplies the content). Default false.
|
|
@@ -1019,7 +1029,7 @@ function _addCert(out, der) {
|
|
|
1019
1029
|
* @opts pem Return a PEM string (`-----BEGIN CMS-----`) instead of a DER Buffer.
|
|
1020
1030
|
* @opts unsignedAttributes Unsigned attributes for every SignerInfo, each `{ type, values }`
|
|
1021
1031
|
* (`type` an OID name or dotted string, `values` an array of DER value
|
|
1022
|
-
* `Buffer`s). Placed in the SignerInfo `[1]` unsignedAttrs
|
|
1032
|
+
* `Buffer`s). Placed in the SignerInfo `[1]` unsignedAttrs, outside the
|
|
1023
1033
|
* signature, so they carry no cryptographic assurance and a verifier never
|
|
1024
1034
|
* reports them authenticated. The vehicle for an RFC 3161 timestamp token
|
|
1025
1035
|
* (`timeStampToken`); content-type / message-digest / signing-time are
|
|
@@ -1045,12 +1055,12 @@ var sign = cmsSign.sign;
|
|
|
1045
1055
|
*
|
|
1046
1056
|
* Add one or more countersignatures to an existing CMS SignedData (RFC 5652 sec. 11.4). A
|
|
1047
1057
|
* countersignature is itself a `SignerInfo` whose signature covers the CONTENTS of the countersigned
|
|
1048
|
-
* SignerInfo's signature value (
|
|
1058
|
+
* SignerInfo's signature value (not the content), attached as the `id-countersignature` unsigned
|
|
1049
1059
|
* attribute -- the construction Authenticode timestamps and signature-attestation workflows rest on.
|
|
1050
|
-
* `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
|
|
1051
1061
|
* the countersigned primary still verifies byte-for-byte). Each `signers[i]` is the same descriptor
|
|
1052
|
-
* `pki.cms.sign` takes
|
|
1053
|
-
* 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;
|
|
1054
1064
|
* `pki.cms.verify` returns each countersignature verdict under `signers[i].countersignatures`. A
|
|
1055
1065
|
* countersignature never carries a content-type attribute (sec. 11.4); multiple countersignatures on
|
|
1056
1066
|
* one signer are multiple values of the one id-countersignature attribute.
|
|
@@ -1059,7 +1069,7 @@ var sign = cmsSign.sign;
|
|
|
1059
1069
|
* array of indices, or `"all"`.
|
|
1060
1070
|
* @opts countersignatureOf Countersign the Nth existing countersignature of the target signer
|
|
1061
1071
|
* instead of the primary signature (a nested countersignature).
|
|
1062
|
-
* @opts signedAttributes Include signed attributes (message-digest + signing-time;
|
|
1072
|
+
* @opts signedAttributes Include signed attributes (message-digest + signing-time; never a
|
|
1063
1073
|
* content-type). Default true; false signs the target signature octets
|
|
1064
1074
|
* directly.
|
|
1065
1075
|
* @opts signingTime A `Date` for the countersignature's signing-time, or false to omit it.
|
|
@@ -1088,11 +1098,11 @@ var countersign = cmsSign.countersign;
|
|
|
1088
1098
|
* @spec RFC 5652, RFC 5083, RFC 5084, RFC 3560, RFC 5753, RFC 8418, RFC 9629, RFC 9936, RFC 3211, RFC 8018
|
|
1089
1099
|
* @related pki.cms.decrypt, pki.schema.cms.parse
|
|
1090
1100
|
*
|
|
1091
|
-
* Encrypt content as a CMS EnvelopedData (CBC content), AuthEnvelopedData (AEAD content
|
|
1101
|
+
* Encrypt content as a CMS EnvelopedData (CBC content), AuthEnvelopedData (AEAD content, the
|
|
1092
1102
|
* default), or EncryptedData. `recipients` is an ARRAY of recipient descriptors for the enveloped
|
|
1093
|
-
* family, each wrapping the
|
|
1103
|
+
* family, each wrapping the same fresh content-encryption key: `{ cert }` auto-dispatches off the
|
|
1094
1104
|
* recipient certificate's public-key algorithm -- RSA yields a ktri with RSAES-OAEP-SHA256 (PKCS#1
|
|
1095
|
-
* 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;
|
|
1096
1106
|
* X25519/X448 a kari per RFC 8418 (HKDF); ML-KEM-512/768/1024 an ori/KEMRecipientInfo per RFC
|
|
1097
1107
|
* 9629 + 9936. `{ password }` yields a pwri (PBKDF2 + RFC 3211 PWRI-KEK); `{ kek, kekId }` a kekri
|
|
1098
1108
|
* (AES key wrap). For EncryptedData (no RecipientInfos), pass a single non-array `{ cek }` or
|
|
@@ -1126,8 +1136,8 @@ var encrypt = cmsEncrypt.encrypt;
|
|
|
1126
1136
|
* @related pki.cms.decrypt, pki.schema.cms.parse
|
|
1127
1137
|
*
|
|
1128
1138
|
* Produce a CMS AuthenticatedData (RFC 5652 sec. 9): CLEARTEXT content plus a MAC (HMAC-SHA-2) --
|
|
1129
|
-
* authenticated but
|
|
1130
|
-
* 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
|
|
1131
1141
|
* `pki.cms.encrypt` uses for a content-encryption key, so `recipients` is the identical array of
|
|
1132
1142
|
* descriptors: `{ cert }` (RSA -> ktri RSAES-OAEP, EC/X25519/X448 -> kari, ML-KEM -> ori/KEMRI),
|
|
1133
1143
|
* `{ password }` (pwri), or `{ kek, kekId }` (kekri). By default the MAC covers the authenticated
|
|
@@ -1161,7 +1171,7 @@ var authenticate = cmsEncrypt.authenticate;
|
|
|
1161
1171
|
|
|
1162
1172
|
/**
|
|
1163
1173
|
* @primitive pki.cms.decrypt
|
|
1164
|
-
* @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 }>
|
|
1165
1175
|
* @since 0.2.23
|
|
1166
1176
|
* @status stable
|
|
1167
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
|
|
@@ -1173,16 +1183,28 @@ var authenticate = cmsEncrypt.authenticate;
|
|
|
1173
1183
|
* ECDH / X25519 / X448; kekri; pwri; ori/ML-KEM), and decrypts (or MAC-verifies) the content.
|
|
1174
1184
|
* `keyMaterial` is `{ key, cert }` (the recipient private key + its certificate, which drives recipient
|
|
1175
1185
|
* matching), `{ password }`, `{ kek, kekId? }`, or `{ cek }` (EncryptedData raw-key mode). Fail-closed
|
|
1176
|
-
* and oracle-free: every secret-dependent failure
|
|
1177
|
-
*
|
|
1178
|
-
*
|
|
1179
|
-
*
|
|
1180
|
-
* 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)
|
|
1181
1191
|
* and, when authenticated attributes are present, the message-digest are verified before the content
|
|
1182
1192
|
* is released, and the result carries `macAlgorithm` / `digestAlgorithm` in place of
|
|
1183
1193
|
* `contentEncryptionAlgorithm`. `authenticated` is true for AuthEnvelopedData and AuthenticatedData; a
|
|
1184
1194
|
* CBC EnvelopedData surfaces `authenticated: false` (the EFAIL caveat in the verdict itself).
|
|
1185
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
|
+
*
|
|
1186
1208
|
* @opts recipientIndex Explicitly select the recipient by index (overrides key-material matching).
|
|
1187
1209
|
* @opts maxIterations Lower the PBKDF2 iteration cap (a DoS bound; downward only).
|
|
1188
1210
|
* @example
|
|
@@ -1208,7 +1230,7 @@ var decrypt = cmsDecrypt.decrypt;
|
|
|
1208
1230
|
* ZLIB-compress `content` and wrap it as a CMS `CompressedData` ContentInfo (RFC 3274): version 0,
|
|
1209
1231
|
* `compressionAlgorithm` = id-alg-zlibCompress (parameters omitted), `encapContentInfo` = the inner
|
|
1210
1232
|
* content type plus the RFC 1950 ZLIB stream (RFC 1951 DEFLATE) as the eContent. There is exactly
|
|
1211
|
-
*
|
|
1233
|
+
* one compression algorithm in RFC 3274, ZLIB, so there is no algorithm selector. CompressedData
|
|
1212
1234
|
* is a size transform with NO integrity, confidentiality, or authentication (RFC 8551 sec. 2.4.5);
|
|
1213
1235
|
* compress it, then sign or encrypt it if you need protection. Malformed input throws a typed `CmsError`.
|
|
1214
1236
|
*
|
|
@@ -1231,7 +1253,7 @@ var compress = cmsCompress.compress;
|
|
|
1231
1253
|
* Parse a CMS `CompressedData` (DER Buffer or PEM) and recover its content. It requires version 0,
|
|
1232
1254
|
* `compressionAlgorithm` = id-alg-zlibCompress with absent-or-NULL parameters (RFC 3274 sec. 2), and a
|
|
1233
1255
|
* present eContent, then inflates the RFC 1950 ZLIB stream. The inflate is BOUNDED at
|
|
1234
|
-
* `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
|
|
1235
1257
|
* decompression-bomb / resource-exhaustion defense (CWE-409): a cap breach throws
|
|
1236
1258
|
* `cms/decompress-too-large`, and every malformed / truncated / corrupt stream collapses to the uniform
|
|
1237
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);
|