@blamejs/pki 0.7.45 → 0.8.0
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 +30 -0
- package/lib/asn1-der.js +5 -1
- package/lib/cms-verify.js +36 -10
- package/lib/constants.js +3 -0
- package/lib/framework-error.js +17 -5
- package/lib/guard-bytes.js +4 -1
- package/lib/guard-identifier.js +5 -1
- package/lib/lint.js +12 -6
- package/lib/oid.js +1 -0
- package/lib/path-validate.js +62 -3
- package/lib/schema-crl.js +12 -6
- package/lib/smime.js +2 -1
- package/lib/tsp-sign.js +2 -2
- package/lib/webcrypto.js +14 -0
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,36 @@ All notable changes to `@blamejs/pki` are documented here. The format
|
|
|
4
4
|
follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this
|
|
5
5
|
project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## v0.8.0 — 2026-09-19
|
|
8
|
+
|
|
9
|
+
Verification is held to the rules signing already applied.
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- `pki.C.LIMITS.OID_MAX_SUBIDENTIFIERS` (128) bounds how many sub-identifiers an OBJECT IDENTIFIER may carry, alongside the existing per-sub-identifier octet bound.
|
|
14
|
+
- `pki.C.LIMITS.RSA_MIN_MODULUS_BITS` (2048) and `pki.path.validate` option `minRsaModulusBits`, which may only raise the floor.
|
|
15
|
+
- `allowWeakDigests` on `pki.cms.verify`, `pki.smime.verify` and `pki.tsp.verify`, for verifying an archived signature made under a digest that is no longer collision resistant.
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
- `pki.cms.verify` reports a SignerInfo whose message digest is SHA-1 as `cms/weak-digest` instead of verifying it, and does the same for a countersignature at any nesting depth. `pki.smime.verify` and `pki.tsp.verify` inherit this through the same signature check. Pass `allowWeakDigests: true` to verify an archived signature under it. A message digest of MD5, MD4 or MD2 is reported `cms/unsupported-algorithm` and the opt-in does not apply, there being no verification path for those digests. The RFC 3161 message imprint is unaffected: that digest is the requester's choice and an archived timestamp under SHA-1 remains readable.
|
|
20
|
+
- `pki.path.validate` refuses an RSA issuer key below 2048 bits with `path/weak-key`. Raise the floor with `minRsaModulusBits`; it cannot be lowered.
|
|
21
|
+
- A key the crypto engine cannot import now reports `path/unsupported-algorithm` instead of `path/bad-signature`, so a certificate on a curve the engine does not carry no longer reads as forged.
|
|
22
|
+
- `pki.webcrypto.subtle.importKey("spki", ...)` refuses an ECDSA or ECDH key whose parameters are explicit domain parameters or the implicitlyCA form, as RFC 5480 section 2.1.1 requires a namedCurve. Certificate parsing still accepts such a key so it can be inspected and linted.
|
|
23
|
+
- `lint/rfc5280/ski-missing` and `lint/rfc5280/aki-missing` are graded `error` rather than `notice`, matching the RFC 5280 section 4.2.1.1 and 4.2.1.2 MUST clauses and the grade the CRL profile already gives the same absence. `lint/rfc5280/ski-missing-ee` is graded `warn`, its clause being a SHOULD. A pipeline gating on `--severity error` will see findings it did not see before.
|
|
24
|
+
- A `PkiError` whose reason is `timeout` carries `permanent: false`, so a caller can decide to retry from the error itself rather than from a code list of its own. The reason is read without its domain, so a timeout raised by a protocol client under its own prefix (`acme/timeout`, `est/timeout`) carries the flag too. Every other code stays `permanent: true`, including a proxy that refuses CONNECT with a definite status.
|
|
25
|
+
|
|
26
|
+
### Fixed
|
|
27
|
+
|
|
28
|
+
- Reading an OBJECT IDENTIFIER with more than 128 sub-identifiers throws `oid/too-many-subidentifiers` rather than expanding it. Each sub-identifier costs a bignum and a decimal string, so a 400 KB certificate carrying one such OID cost 55 MB of memory to parse and the ceiling scaled with the 16 MiB input bound. Every format parser reads OIDs through the bounded reader. Encoding is held to the same count, so a value the reader refuses cannot be produced. The generic `pki.asn1.decode` still returns such a value as an opaque primitive node, which allocates nothing beyond the input.
|
|
29
|
+
- `pki.cms.sign` and `pki.cms.countersign` over more than 16 MiB of buffered content threw an untyped `RangeError` instead of a `PkiError`.
|
|
30
|
+
- A CRL extension is decoded only in the scope RFC 5280 defines it for: `cRLNumber` at CRL scope (section 5.2.3), `reasonCode` and `invalidityDate` in a revoked-certificate entry (sections 5.3.1 and 5.3.2). An extension carried in the other scope is left with its raw value, as an unrecognized non-critical extension should be, instead of failing the whole CRL when its value does not parse as that type.
|
|
31
|
+
- The `pki.cms.verify` reference example built a signature with `detached` in the signer descriptor, where it is ignored; the example produced an attached SignedData while describing a detached one.
|
|
32
|
+
|
|
33
|
+
### Documentation
|
|
34
|
+
|
|
35
|
+
- SECURITY.md states that the toolkit matches no reference identity: name constraints reach the name forms a certificate carries plus the `emailAddress` synthesis RFC 5280 section 4.2.1.10 requires, and a `dNSName` constraint does not reach a subject `commonName`. RFC 9525 section 1.3 retires `commonName` as a source of hostname identity.
|
|
36
|
+
|
|
7
37
|
## v0.7.45 — 2026-09-13
|
|
8
38
|
|
|
9
39
|
A signature-protected CMP message fills an omitted sender with the signer's subject.
|
package/lib/asn1-der.js
CHANGED
|
@@ -483,6 +483,10 @@ function decodeOidContent(buf) {
|
|
|
483
483
|
"OID sub-identifier exceeds " + constants.LIMITS.OID_MAX_SUBIDENTIFIER_BYTES + " octets");
|
|
484
484
|
}
|
|
485
485
|
if ((b & 0x80) === 0) {
|
|
486
|
+
if (arcs.length >= constants.LIMITS.OID_MAX_SUBIDENTIFIERS) {
|
|
487
|
+
throw new OidError("oid/too-many-subidentifiers",
|
|
488
|
+
"OBJECT IDENTIFIER carries more than " + constants.LIMITS.OID_MAX_SUBIDENTIFIERS + " sub-identifiers");
|
|
489
|
+
}
|
|
486
490
|
var value = 0n;
|
|
487
491
|
for (var k = arcStart; k <= i; k++) value = value * 128n + BigInt(buf[k] & 0x7f);
|
|
488
492
|
arcs.push(value);
|
|
@@ -777,7 +781,7 @@ function intToDer(v) {
|
|
|
777
781
|
}
|
|
778
782
|
|
|
779
783
|
function encodeOidContent(dotted) {
|
|
780
|
-
guard.identifier.assertCanonicalOid(dotted, _oidErr, "oid/bad-input", "OID", "oid/bad-arc");
|
|
784
|
+
guard.identifier.assertCanonicalOid(dotted, _oidErr, "oid/bad-input", "OID", "oid/bad-arc", "oid/too-many-subidentifiers");
|
|
781
785
|
var arcs = dotted.split(".").map(function (p) { return BigInt(p); });
|
|
782
786
|
var a1 = arcs[0], a2 = arcs[1];
|
|
783
787
|
var subids = [a1 * 40n + a2].concat(arcs.slice(2));
|
package/lib/cms-verify.js
CHANGED
|
@@ -69,6 +69,17 @@ var DIGEST_HASH = intrinsic.assign(intrinsic.create(null), {
|
|
|
69
69
|
shake128: "SHAKE128", shake256: "SHAKE256",
|
|
70
70
|
});
|
|
71
71
|
var SIG_HASH = intrinsic.assign(intrinsic.create(null), { sha1: "SHA-1", sha256: "SHA-256", sha384: "SHA-384", sha512: "SHA-512" });
|
|
72
|
+
function _digestOidTable(named) {
|
|
73
|
+
var t = intrinsic.create(null);
|
|
74
|
+
intrinsic.forEach(named, function (pair) {
|
|
75
|
+
var dotted = oid.byName(pair[0]);
|
|
76
|
+
if (dotted) t[dotted] = pair[1];
|
|
77
|
+
});
|
|
78
|
+
return t;
|
|
79
|
+
}
|
|
80
|
+
var WEAK_DIGEST_OIDS = _digestOidTable([["sha1", "SHA-1"]]);
|
|
81
|
+
var UNVERIFIABLE_DIGEST_OIDS = _digestOidTable([["md5", "MD5"], ["md4", "MD4"], ["md2", "MD2"]]);
|
|
82
|
+
var WEAK_SIGNATURE_HASHES = intrinsic.assign(intrinsic.create(null), { "SHA-1": 1 });
|
|
72
83
|
function _supportedDigest(name) { return intrinsic.hasOwn(DIGEST_HASH, name) && !!DIGEST_HASH[name]; }
|
|
73
84
|
async function _computeDigest(name, content) {
|
|
74
85
|
return intrinsic.bufferFrom(await subtle.digest(DIGEST_HASH[name], content));
|
|
@@ -247,11 +258,21 @@ function _decodeSignedAttrs(setOfBytes) {
|
|
|
247
258
|
});
|
|
248
259
|
}
|
|
249
260
|
|
|
250
|
-
async function _verifyOne(si, content, eContentType, parsedCerts, csTarget, digestByName) {
|
|
261
|
+
async function _verifyOne(si, content, eContentType, parsedCerts, csTarget, digestByName, allowWeakDigests) {
|
|
251
262
|
var composite = compositeSig.COMPOSITE_ALGS[si.signatureAlgorithm.oid];
|
|
252
263
|
if (composite) return _verifyComposite(si, composite, content, eContentType, parsedCerts, csTarget, digestByName);
|
|
253
264
|
var scheme = SIG_SCHEME[si.signatureAlgorithm.name];
|
|
254
265
|
if (!scheme) return _pResolve({ ok: false, code: "cms/unsupported-algorithm", sid: si.sid, message: "unsupported signature algorithm " + JSON.stringify(si.signatureAlgorithm.name) });
|
|
266
|
+
var unverifiableDigest = UNVERIFIABLE_DIGEST_OIDS[si.digestAlgorithm.oid];
|
|
267
|
+
if (unverifiableDigest) {
|
|
268
|
+
return _pResolve({ ok: false, code: "cms/unsupported-algorithm", sid: si.sid,
|
|
269
|
+
message: "the " + unverifiableDigest + " message digest is broken and this toolkit does not verify a signature under it" });
|
|
270
|
+
}
|
|
271
|
+
var weakDigest = WEAK_DIGEST_OIDS[si.digestAlgorithm.oid];
|
|
272
|
+
if (!allowWeakDigests && weakDigest) {
|
|
273
|
+
return _pResolve({ ok: false, code: "cms/weak-digest", sid: si.sid,
|
|
274
|
+
message: "the " + weakDigest + " message digest is not collision resistant; pass allowWeakDigests to verify an archived signature under it" });
|
|
275
|
+
}
|
|
255
276
|
var digestHash = SIG_HASH[si.digestAlgorithm.name];
|
|
256
277
|
var pss = scheme.kind === "rsapss" ? _resolvePss(si.signatureAlgorithm.parameters) : null;
|
|
257
278
|
if (scheme.kind === "rsapss" && !pss) return _pResolve({ ok: false, code: "cms/unsupported-algorithm", sid: si.sid, message: "unsupported or non-conformant RSASSA-PSS parameters (RFC 4055)" });
|
|
@@ -261,6 +282,10 @@ async function _verifyOne(si, content, eContentType, parsedCerts, csTarget, dige
|
|
|
261
282
|
if (!mldsaNoAttrs && dp !== null && dp !== undefined && !_isDerNull(dp)) return _pResolve({ ok: false, code: "cms/unsupported-algorithm", sid: si.sid, message: "the " + si.digestAlgorithm.name + " digest algorithm parameters must be absent or DER NULL (RFC 5754 sec. 2)" });
|
|
262
283
|
if ((scheme.kind === "mldsa" || scheme.kind === "slhdsa") && si.signedAttrsBytes && (si.digestAlgorithm.name === "shake128" || si.digestAlgorithm.name === "shake256") && dp !== null && dp !== undefined) return _pResolve({ ok: false, code: "cms/unsupported-algorithm", sid: si.sid, message: "a SHAKE digestAlgorithm carries no parameters (RFC 8702 sec. 3.1)" });
|
|
263
284
|
var sigHash = pss ? pss.hash : (scheme.hash || digestHash);
|
|
285
|
+
if (!allowWeakDigests && WEAK_SIGNATURE_HASHES[sigHash]) {
|
|
286
|
+
return _pResolve({ ok: false, code: "cms/weak-digest", sid: si.sid,
|
|
287
|
+
message: "the signature algorithm selects the " + sigHash + " digest, which is not collision resistant; pass allowWeakDigests to verify an archived signature under it" });
|
|
288
|
+
}
|
|
264
289
|
if ((scheme.kind !== "eddsa" && scheme.kind !== "mldsa" && scheme.kind !== "slhdsa" && !sigHash) || (si.signedAttrsBytes && !_supportedDigest(si.digestAlgorithm.name))) return _pResolve({ ok: false, code: "cms/unsupported-algorithm", sid: si.sid, message: "unsupported digest algorithm " + JSON.stringify(si.digestAlgorithm.name) });
|
|
265
290
|
if (scheme.kind === "mldsa" && si.signedAttrsBytes && !_mldsaDigestSuitable(scheme.name, si.digestAlgorithm.name)) return _pResolve({ ok: false, code: "cms/unsupported-algorithm", sid: si.sid, message: "the " + si.digestAlgorithm.name + " message digest is below the security strength of " + scheme.name + " (RFC 9882 sec. 3.3)" });
|
|
266
291
|
if (scheme.kind === "slhdsa" && si.signedAttrsBytes && si.digestAlgorithm.name !== scheme.digest) return _pResolve({ ok: false, code: "cms/unsupported-algorithm", sid: si.sid, message: "SLH-DSA " + scheme.name + " requires the " + scheme.digest + " message digest (RFC 9814 sec. 4)" });
|
|
@@ -386,7 +411,7 @@ function _surfaceSignedAttrs(si) {
|
|
|
386
411
|
});
|
|
387
412
|
}
|
|
388
413
|
|
|
389
|
-
async function _verifyCountersignatures(si, parsedCerts) {
|
|
414
|
+
async function _verifyCountersignatures(si, parsedCerts, allowWeakDigests) {
|
|
390
415
|
var targetSig = _toBuf(si.signature, "the countersigned signature");
|
|
391
416
|
var values = [];
|
|
392
417
|
intrinsic.forEach(si.unsignedAttrs || [], function (a) {
|
|
@@ -394,16 +419,16 @@ async function _verifyCountersignatures(si, parsedCerts) {
|
|
|
394
419
|
intrinsic.forEach(a.values, function (v) { guard.list.append(values, v && v.bytes ? v.bytes : v); });
|
|
395
420
|
});
|
|
396
421
|
var out = [];
|
|
397
|
-
for (var _cp = 0; _cp < values.length; _cp++) guard.list.append(out, await _verifyOneCountersig(values[_cp], targetSig, parsedCerts));
|
|
422
|
+
for (var _cp = 0; _cp < values.length; _cp++) guard.list.append(out, await _verifyOneCountersig(values[_cp], targetSig, parsedCerts, allowWeakDigests));
|
|
398
423
|
return out;
|
|
399
424
|
}
|
|
400
425
|
|
|
401
|
-
async function _verifyOneCountersig(vDer, targetSig, parsedCerts) {
|
|
426
|
+
async function _verifyOneCountersig(vDer, targetSig, parsedCerts, allowWeakDigests) {
|
|
402
427
|
var csSi;
|
|
403
428
|
try { csSi = cms.walkCountersignature(asn1.decode(intrinsic.isBuffer(vDer) ? vDer : intrinsic.bufferFrom(vDer))); }
|
|
404
429
|
catch (e) { return _row({ ok: false, code: (e instanceof CmsError ? e.code : "cms/bad-countersignature"), message: e && e.message }); }
|
|
405
|
-
var verdict = await _verifyOne(csSi, targetSig, null, parsedCerts, targetSig);
|
|
406
|
-
var nested = await _verifyCountersignatures(csSi, parsedCerts);
|
|
430
|
+
var verdict = await _verifyOne(csSi, targetSig, null, parsedCerts, targetSig, undefined, allowWeakDigests);
|
|
431
|
+
var nested = await _verifyCountersignatures(csSi, parsedCerts, allowWeakDigests);
|
|
407
432
|
var head = { ok: verdict.ok, sid: verdict.sid, cert: verdict.cert, digestAlgorithm: csSi.digestAlgorithm.name };
|
|
408
433
|
return guard.verdict.of(
|
|
409
434
|
verdict.code ? guard.verdict.of(head, { code: verdict.code }) : head,
|
|
@@ -478,7 +503,7 @@ async function _verifyOneCountersig(vDer, targetSig, parsedCerts) {
|
|
|
478
503
|
* var cert = await pki.x509.sign({ subject: "Signer", subjectPublicKey: await pki.key.export(pair.publicKey),
|
|
479
504
|
* notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z") }, { key: key });
|
|
480
505
|
* var detachedBytes = Buffer.from("hello");
|
|
481
|
-
* var p7sDer = await pki.cms.sign(detachedBytes, { cert: cert, key: key, detached: true });
|
|
506
|
+
* var p7sDer = await pki.cms.sign(detachedBytes, { cert: cert, key: key }, { detached: true });
|
|
482
507
|
* var res = await pki.cms.verify(p7sDer, { content: detachedBytes });
|
|
483
508
|
* res.valid; // boolean
|
|
484
509
|
* res.signers[0].ok; // per-signer verdict
|
|
@@ -509,7 +534,7 @@ async function _resolveContentDigests(parsed, streamSource) {
|
|
|
509
534
|
return byName;
|
|
510
535
|
}
|
|
511
536
|
|
|
512
|
-
var _VERIFY_OPTS = intrinsic.assign(intrinsic.create(null), { certs: 1, content: 1, trustAnchors: 1, time: 1, requiredEku: 1, checkPurpose: 1 });
|
|
537
|
+
var _VERIFY_OPTS = intrinsic.assign(intrinsic.create(null), { certs: 1, content: 1, trustAnchors: 1, time: 1, requiredEku: 1, checkPurpose: 1, allowWeakDigests: 1 });
|
|
513
538
|
|
|
514
539
|
function verify(input, opts) {
|
|
515
540
|
return guard.async.deferred(function () { return _verify(input, opts); });
|
|
@@ -523,6 +548,7 @@ async function _verify(input, opts) {
|
|
|
523
548
|
}, _err, "cms/bad-input", "the SignedData");
|
|
524
549
|
if (!Array.isArray(parsed.signerInfos)) throw _err("cms/bad-input", "input is not a CMS SignedData");
|
|
525
550
|
var trustCfg = _snapshotTrust(opts);
|
|
551
|
+
var allowWeakDigests = opts.allowWeakDigests === true;
|
|
526
552
|
var parsedCerts = [];
|
|
527
553
|
(parsed.certificates || []).forEach(function (c) { _addCert(parsedCerts, c && c.bytes ? c.bytes : c); });
|
|
528
554
|
(opts.certs || []).forEach(function (c) { _addCert(parsedCerts, c); });
|
|
@@ -544,8 +570,8 @@ async function _verify(input, opts) {
|
|
|
544
570
|
var signers = [];
|
|
545
571
|
for (var _sp = 0; _sp < parsed.signerInfos.length; _sp++) {
|
|
546
572
|
var siOne = parsed.signerInfos[_sp];
|
|
547
|
-
var verdict = await _verifyOne(siOne, content, eContentType, parsedCerts, null, digestByName);
|
|
548
|
-
var counters = await _verifyCountersignatures(siOne, parsedCerts);
|
|
573
|
+
var verdict = await _verifyOne(siOne, content, eContentType, parsedCerts, null, digestByName, allowWeakDigests);
|
|
574
|
+
var counters = await _verifyCountersignatures(siOne, parsedCerts, allowWeakDigests);
|
|
549
575
|
guard.list.append(signers, guard.verdict.of(verdict, {
|
|
550
576
|
countersignatures: counters,
|
|
551
577
|
unsignedAttrs: _surfaceUnsignedAttrs(siOne),
|
package/lib/constants.js
CHANGED
|
@@ -112,14 +112,17 @@ var LIMITS = Object.assign(Object.create(null), {
|
|
|
112
112
|
DER_MAX_BYTES: BYTES.mib(16),
|
|
113
113
|
DER_MAX_DEPTH: 64,
|
|
114
114
|
DER_MAX_ITEMS: 4000000,
|
|
115
|
+
NAMED_SCAN_MAX_ELEMENTS: 16777216,
|
|
115
116
|
MAX_DECODE_DEPTH_CEILING: 256,
|
|
116
117
|
PEM_MAX_BYTES: BYTES.mib(16),
|
|
117
118
|
DER_MAX_INTEGER_BYTES: BYTES.kib(16),
|
|
118
119
|
DH_MAX_MODULUS_BYTES: 512,
|
|
119
120
|
DH_MIN_SUBGROUP_BYTES: 20,
|
|
120
121
|
DH_MIN_MODULUS_BYTES: 256,
|
|
122
|
+
RSA_MIN_MODULUS_BITS: 2048,
|
|
121
123
|
DH_PRIME_MEMO_ENTRIES: 64,
|
|
122
124
|
OID_MAX_SUBIDENTIFIER_BYTES: 32,
|
|
125
|
+
OID_MAX_SUBIDENTIFIERS: 128,
|
|
123
126
|
HTTP_MAX_RESPONSE_BYTES: BYTES.mib(24),
|
|
124
127
|
HTTP_AUTH_HEADER_MAX_BYTES: BYTES.kib(8),
|
|
125
128
|
CBOR_MAX_BYTES: BYTES.mib(16),
|
package/lib/framework-error.js
CHANGED
|
@@ -18,10 +18,14 @@
|
|
|
18
18
|
*
|
|
19
19
|
* `code` is a stable, greppable `domain/reason` string
|
|
20
20
|
* (`asn1/indefinite-length`, `x509/not-a-certificate`): safe to switch
|
|
21
|
-
* on and safe to log.
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
21
|
+
* on and safe to log. A failure that is a deterministic verdict on the
|
|
22
|
+
* bytes in hand (a malformed length, an unknown OID shape, a truncated
|
|
23
|
+
* certificate) is `permanent: true`: the same input will never parse on
|
|
24
|
+
* retry. A failure whose reason is `timeout` is `permanent: false`, so a
|
|
25
|
+
* caller can retry on the error rather than on a code list of its own.
|
|
26
|
+
* The reason is read without its domain, so the protocol clients that
|
|
27
|
+
* raise the same failure under their own prefix (`acme/timeout`,
|
|
28
|
+
* `est/timeout`) carry the flag too.
|
|
25
29
|
*
|
|
26
30
|
* @card
|
|
27
31
|
* `PkiError` base class + `defineClass` factory + the per-domain error
|
|
@@ -30,9 +34,17 @@
|
|
|
30
34
|
|
|
31
35
|
var intrinsic = require("./guard-intrinsic");
|
|
32
36
|
var _charCodeAt = intrinsic.uncurry(String.prototype.charCodeAt);
|
|
37
|
+
var _strSlice = intrinsic.uncurry(String.prototype.slice);
|
|
33
38
|
var _String = intrinsic.String;
|
|
34
39
|
var _defineProperty = intrinsic.defineProperty;
|
|
35
40
|
|
|
41
|
+
var _TRANSIENT_REASONS = intrinsic.assign(intrinsic.create(null), { timeout: 1 });
|
|
42
|
+
|
|
43
|
+
function _reasonOf(c) {
|
|
44
|
+
for (var i = 0; i < c.length; i++) if (_charCodeAt(c, i) === 47) return _strSlice(c, i + 1);
|
|
45
|
+
return c;
|
|
46
|
+
}
|
|
47
|
+
|
|
36
48
|
function _isCodeShape(c) {
|
|
37
49
|
var n = c.length, i, ch, slash = -1;
|
|
38
50
|
if (n < 3) return false;
|
|
@@ -79,7 +91,7 @@ class PkiError extends Error {
|
|
|
79
91
|
}
|
|
80
92
|
this.code = c;
|
|
81
93
|
this.isPkiError = true;
|
|
82
|
-
this.permanent =
|
|
94
|
+
this.permanent = !_TRANSIENT_REASONS[_reasonOf(c)];
|
|
83
95
|
}
|
|
84
96
|
}
|
|
85
97
|
|
package/lib/guard-bytes.js
CHANGED
|
@@ -6,6 +6,7 @@ var async = require("./guard-async");
|
|
|
6
6
|
var identifier = require("./guard-identifier");
|
|
7
7
|
var time = require("./guard-time");
|
|
8
8
|
var intrinsic = require("./guard-intrinsic");
|
|
9
|
+
var _NAMED_SCAN_MAX_ELEMENTS = require("./constants").LIMITS.NAMED_SCAN_MAX_ELEMENTS;
|
|
9
10
|
var util = intrinsic.assign(intrinsic.create(null), { types: intrinsic.types });
|
|
10
11
|
var _isArray = intrinsic.isArray;
|
|
11
12
|
var _bufferFrom = intrinsic.bufferFrom;
|
|
@@ -358,7 +359,9 @@ function _deep(v, ErrorClass, code, label, cap, depth, collect) {
|
|
|
358
359
|
}
|
|
359
360
|
if (_isBuffer(v) || _isView(v) || _isArrayBuffer(v)) {
|
|
360
361
|
var bytesCopy = _copyBytesSameKind(v, ErrorClass, code, label, collect);
|
|
361
|
-
|
|
362
|
+
if (bytesCopy.byteLength <= _NAMED_SCAN_MAX_ELEMENTS) {
|
|
363
|
+
_copyNamed(v, bytesCopy, ErrorClass, code, label, cap, depth, collect);
|
|
364
|
+
}
|
|
362
365
|
return bytesCopy;
|
|
363
366
|
}
|
|
364
367
|
if (require("./guard-parsed").isRecordedAsProduced(v)) return v; // allow:inline-require -- circular load: guard-parsed requires this module
|
package/lib/guard-identifier.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// Copyright (c) blamejs contributors
|
|
3
3
|
"use strict";
|
|
4
4
|
var intrinsic = require("./guard-intrinsic");
|
|
5
|
+
var constants = require("./constants");
|
|
5
6
|
var util = intrinsic.assign(intrinsic.create(null), { types: intrinsic.types });
|
|
6
7
|
// @internal
|
|
7
8
|
|
|
@@ -60,7 +61,7 @@ function _isDottedDecimal(str) {
|
|
|
60
61
|
}
|
|
61
62
|
|
|
62
63
|
// @enforced-by behavioral -- string-form OID canonicalization has no rename-proof
|
|
63
|
-
function assertCanonicalOid(str, E, code, label, boundsCode) {
|
|
64
|
+
function assertCanonicalOid(str, E, code, label, boundsCode, countCode) {
|
|
64
65
|
var who = label || "OID";
|
|
65
66
|
if (typeof str !== "string" || !_isDottedDecimal(str)) {
|
|
66
67
|
throw E(code, who + " must be a canonical dotted-decimal OID string of two or more arcs with no leading-zero component");
|
|
@@ -68,6 +69,9 @@ function assertCanonicalOid(str, E, code, label, boundsCode) {
|
|
|
68
69
|
if (boundsCode === null) return str;
|
|
69
70
|
var bcode = boundsCode === undefined ? code : boundsCode;
|
|
70
71
|
var parts = _strSplit(str, ".");
|
|
72
|
+
if (parts.length - 1 > constants.LIMITS.OID_MAX_SUBIDENTIFIERS) {
|
|
73
|
+
throw E(countCode || bcode, who + " carries more than " + constants.LIMITS.OID_MAX_SUBIDENTIFIERS + " sub-identifiers");
|
|
74
|
+
}
|
|
71
75
|
var root = _BigInt(parts[0]);
|
|
72
76
|
var second = _BigInt(parts[1]);
|
|
73
77
|
if (root > 2n) throw E(bcode, who + " root arc must be 0, 1, or 2 (X.660)");
|
package/lib/lint.js
CHANGED
|
@@ -46,6 +46,12 @@ var MS_PER_DAY = C.TIME.days(1);
|
|
|
46
46
|
var LintError = frameworkError.LintError;
|
|
47
47
|
function _cfg(code, message, cause) { return new LintError(code, message, cause); }
|
|
48
48
|
|
|
49
|
+
function _sameName(a, b) {
|
|
50
|
+
if (!a || !b || !a.rdns || !b.rdns) return false;
|
|
51
|
+
try { return guard.name.dnEqual(a.rdns, b.rdns, _cfg, "lint/bad-input", "distinguished name") === true; }
|
|
52
|
+
catch (_e) { return a.dn === b.dn; }
|
|
53
|
+
}
|
|
54
|
+
|
|
49
55
|
var NS = pkix.makeNS("lint", LintError, oid);
|
|
50
56
|
var EXT_DECODERS = pkix.certExtensionDecoders(NS).byOid;
|
|
51
57
|
|
|
@@ -368,15 +374,15 @@ var RFC5280_RULES = [
|
|
|
368
374
|
},
|
|
369
375
|
},
|
|
370
376
|
{
|
|
371
|
-
id: "lint/rfc5280/ski-missing", severity: "
|
|
372
|
-
message: "a CA certificate
|
|
377
|
+
id: "lint/rfc5280/ski-missing", severity: "error", source: "rfc5280", citation: "RFC 5280 4.2.1.2",
|
|
378
|
+
message: "a CA certificate must carry a subjectKeyIdentifier extension",
|
|
373
379
|
appliesTo: function (cert, ctx) { var bc = ctx.decode("basicConstraints"); return !!(bc && bc.value && bc.value.cA === true); },
|
|
374
380
|
check: function (cert, ctx) { return ctx.raw("subjectKeyIdentifier") ? null : true; },
|
|
375
381
|
},
|
|
376
382
|
{
|
|
377
|
-
id: "lint/rfc5280/aki-missing", severity: "
|
|
378
|
-
message: "a non-self-issued certificate
|
|
379
|
-
appliesTo: function (cert) { return !!(cert.issuer && cert.subject && cert.issuer
|
|
383
|
+
id: "lint/rfc5280/aki-missing", severity: "error", source: "rfc5280", citation: "RFC 5280 4.2.1.1",
|
|
384
|
+
message: "a non-self-issued certificate must carry an authorityKeyIdentifier extension",
|
|
385
|
+
appliesTo: function (cert) { return !!(cert.issuer && cert.subject) && !_sameName(cert.issuer, cert.subject); },
|
|
380
386
|
check: function (cert, ctx) { return ctx.raw("authorityKeyIdentifier") ? null : true; },
|
|
381
387
|
},
|
|
382
388
|
{
|
|
@@ -466,7 +472,7 @@ var RFC5280_RULES = [
|
|
|
466
472
|
},
|
|
467
473
|
},
|
|
468
474
|
{
|
|
469
|
-
id: "lint/rfc5280/ski-missing-ee", severity: "
|
|
475
|
+
id: "lint/rfc5280/ski-missing-ee", severity: "warn", source: "rfc5280", citation: "RFC 5280 4.2.1.2",
|
|
470
476
|
message: "an end-entity certificate should carry a subjectKeyIdentifier extension",
|
|
471
477
|
appliesTo: function (cert, ctx) { var bc = ctx.decode("basicConstraints"); return !(bc && bc.value && bc.value.cA === true); },
|
|
472
478
|
check: function (cert, ctx) { return ctx.raw("subjectKeyIdentifier") ? null : true; },
|
package/lib/oid.js
CHANGED
|
@@ -208,6 +208,7 @@ var FAMILIES = Object.assign(Object.create(null), {
|
|
|
208
208
|
"pbeWithSHAAnd128BitRC2-CBC": 5, "pbeWithSHAAnd40BitRC2-CBC": 6 } },
|
|
209
209
|
|
|
210
210
|
rsadsiDigest: { base: [1, 2, 840, 113549, 2], of: {
|
|
211
|
+
md2: 2, md4: 4, md5: 5,
|
|
211
212
|
hmacWithSHA1: 7, hmacWithSHA224: 8, hmacWithSHA256: 9,
|
|
212
213
|
hmacWithSHA384: 10, hmacWithSHA512: 11 } },
|
|
213
214
|
|
package/lib/path-validate.js
CHANGED
|
@@ -70,7 +70,10 @@ var edwardsPoint = require("./edwards-point");
|
|
|
70
70
|
var PathError = errors.PathError;
|
|
71
71
|
function E(code, message, cause) { return new PathError(code, message, cause); }
|
|
72
72
|
function pathCode(e, fallback) {
|
|
73
|
-
|
|
73
|
+
if (!e || typeof e.code !== "string") return fallback;
|
|
74
|
+
if (e.code.indexOf("path/") === 0) return e.code;
|
|
75
|
+
if (e.code === "webcrypto/not-supported" || e.code === "webcrypto/data") return "path/unsupported-algorithm";
|
|
76
|
+
return fallback;
|
|
74
77
|
}
|
|
75
78
|
|
|
76
79
|
var contains = guard.list.contains;
|
|
@@ -151,6 +154,7 @@ _sig("id-ml-dsa-87", { name: "ML-DSA-87" }, { name: "ML-DSA-87" }, "absent", fal
|
|
|
151
154
|
_sig("id-alg-hss-lms-hashsig", { name: "HSS-LMS" }, null, "absent", false, true).shbs = true;
|
|
152
155
|
|
|
153
156
|
var OID_RSA_PSS = oid.byName("rsassaPss");
|
|
157
|
+
var OID_RSA_ENCRYPTION = oid.byName("rsaEncryption");
|
|
154
158
|
var OID_MGF1 = oid.byName("mgf1");
|
|
155
159
|
var HASH_BY_OID = intrinsic.create(null);
|
|
156
160
|
HASH_BY_OID[oid.byName("sha256")] = "SHA-256";
|
|
@@ -344,6 +348,49 @@ function _shbsVerify(spkiBytes, sigBytes, message) {
|
|
|
344
348
|
} catch (e) { return guard.verdict.of({ ok: false, code: pathCode(e, "path/bad-signature"), error: e }); }
|
|
345
349
|
}
|
|
346
350
|
|
|
351
|
+
function _weakIssuerKey(state) {
|
|
352
|
+
var spkiBytes = state.workingPublicKey;
|
|
353
|
+
var isRsa;
|
|
354
|
+
try { isRsa = _issuerKeyIsRsa(spkiBytes); }
|
|
355
|
+
catch (_e) { return null; }
|
|
356
|
+
if (!isRsa) return null;
|
|
357
|
+
var bits;
|
|
358
|
+
try { bits = _rsaModulusBits(spkiBytes); }
|
|
359
|
+
catch (_e) { bits = null; }
|
|
360
|
+
if (bits === null) {
|
|
361
|
+
return E("path/weak-key",
|
|
362
|
+
"the issuer's key names an RSA algorithm and its modulus cannot be read, so its strength cannot be established");
|
|
363
|
+
}
|
|
364
|
+
var floor = state.minRsaModulusBits || constants.LIMITS.RSA_MIN_MODULUS_BITS;
|
|
365
|
+
if (bits >= floor) return null;
|
|
366
|
+
return E("path/weak-key", "the issuer's RSA modulus is " + bits + " bits, below the " + floor + "-bit minimum");
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function _issuerKeyIsRsa(spkiBytes) {
|
|
370
|
+
if (!spkiBytes || !spkiBytes.length) return false;
|
|
371
|
+
var kids = pkix.rootSequenceChildren(asn1.decode(spkiBytes), 2, 2);
|
|
372
|
+
if (!kids) return false;
|
|
373
|
+
var algKids = pkix.rootSequenceChildren(kids[0], 1);
|
|
374
|
+
if (!algKids) return false;
|
|
375
|
+
var keyAlg = asn1.read.oid(algKids[0]);
|
|
376
|
+
return keyAlg === OID_RSA_ENCRYPTION || keyAlg === OID_RSA_PSS;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
function _rsaModulusBits(spkiBytes) {
|
|
380
|
+
var spki = asn1.decode(spkiBytes);
|
|
381
|
+
var keyBits = asn1.read.bitString(spki.children[1]);
|
|
382
|
+
var innerKids = pkix.rootSequenceChildren(asn1.decode(keyBits.bytes), 1);
|
|
383
|
+
if (!innerKids) return null;
|
|
384
|
+
var modulus = innerKids[0].content;
|
|
385
|
+
if (!modulus || !modulus.length) return null;
|
|
386
|
+
var i = 0;
|
|
387
|
+
while (i < modulus.length && modulus[i] === 0) i += 1;
|
|
388
|
+
if (i >= modulus.length) return 0;
|
|
389
|
+
var top = modulus[i], topBits = 0;
|
|
390
|
+
while (top > 0) { topBits += 1; top >>= 1; }
|
|
391
|
+
return (modulus.length - i - 1) * 8 + topBits;
|
|
392
|
+
}
|
|
393
|
+
|
|
347
394
|
function builtinVerify(state, cert) {
|
|
348
395
|
var d;
|
|
349
396
|
try {
|
|
@@ -702,6 +749,7 @@ function initialize(certs, params, seeds, anchor) {
|
|
|
702
749
|
validPolicyTree: rootNode(),
|
|
703
750
|
policyNodeCount: 1,
|
|
704
751
|
maxPolicyNodes: params.maxPolicyNodes !== undefined ? params.maxPolicyNodes : constants.LIMITS.PATH_MAX_POLICY_NODES,
|
|
752
|
+
minRsaModulusBits: params.minRsaModulusBits !== undefined ? params.minRsaModulusBits : constants.LIMITS.RSA_MIN_MODULUS_BITS,
|
|
705
753
|
permittedGenerations: intrinsic.filter(seeds.permittedGenerations, function (g) { return g.length > 0; }),
|
|
706
754
|
excludedSubtrees: seeds.excluded,
|
|
707
755
|
explicitPolicy: params.initialExplicitPolicy ? 0 : n + 1,
|
|
@@ -1054,7 +1102,7 @@ function validateCriticalExtensionStructure(cert) {
|
|
|
1054
1102
|
var _VALIDATE_OPTS = intrinsic.assign(intrinsic.create(null), {
|
|
1055
1103
|
checkPurpose: 1, ctLogList: 1, ctPolicy: 1, historicalMode: 1, initialAnyPolicyInhibit: 1,
|
|
1056
1104
|
initialExcludedSubtrees: 1, initialExplicitPolicy: 1, initialPermittedSubtrees: 1,
|
|
1057
|
-
initialPolicyMappingInhibit: 1, maxPathCerts: 1, maxPolicyNodes: 1, requireRevocation: 1,
|
|
1105
|
+
initialPolicyMappingInhibit: 1, maxPathCerts: 1, maxPolicyNodes: 1, minRsaModulusBits: 1, requireRevocation: 1,
|
|
1058
1106
|
requiredEku: 1, revocationChecker: 1, softFail: 1, time: 1, trustAnchors: 1,
|
|
1059
1107
|
userInitialPolicySet: 1, verifier: 1
|
|
1060
1108
|
});
|
|
@@ -1100,6 +1148,14 @@ async function validate(path, opts) {
|
|
|
1100
1148
|
if (!intrinsic.isArray(path)) throw E("path/bad-input", "validate: path must be an array of certificates");
|
|
1101
1149
|
var maxCerts = guard.limits.cap(opts.maxPathCerts, "validate: opts.maxPathCerts", constants.LIMITS.PATH_MAX_CERTS, { E: E, code: "path/bad-input", min: 1 });
|
|
1102
1150
|
if (path.length > maxCerts) throw E("path/bad-input", "validate: the certification path has " + path.length + " certificates, exceeding the maxPathCerts limit (" + maxCerts + ")");
|
|
1151
|
+
if (opts.minRsaModulusBits != null) {
|
|
1152
|
+
if (typeof opts.minRsaModulusBits !== "number" || !intrinsic.isInteger(opts.minRsaModulusBits)) {
|
|
1153
|
+
throw E("path/bad-input", "validate: opts.minRsaModulusBits must be an integer");
|
|
1154
|
+
}
|
|
1155
|
+
if (opts.minRsaModulusBits < constants.LIMITS.RSA_MIN_MODULUS_BITS) {
|
|
1156
|
+
throw E("path/bad-input", "validate: opts.minRsaModulusBits may only raise the " + constants.LIMITS.RSA_MIN_MODULUS_BITS + "-bit minimum, not lower it");
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1103
1159
|
var certs = path.map(function (c, ci) { return coerceCert(c, "validate: path[" + ci + "]"); });
|
|
1104
1160
|
var n = certs.length;
|
|
1105
1161
|
if (n < 1) throw E("path/empty-path", "validate: the certification path is empty");
|
|
@@ -1178,7 +1234,10 @@ async function validate(path, opts) {
|
|
|
1178
1234
|
var checks = [];
|
|
1179
1235
|
|
|
1180
1236
|
var sigRes;
|
|
1181
|
-
|
|
1237
|
+
var weak = _weakIssuerKey(state);
|
|
1238
|
+
if (weak) {
|
|
1239
|
+
sigRes = { ok: false, code: "path/weak-key", error: weak };
|
|
1240
|
+
} else if (verifier) {
|
|
1182
1241
|
var vv;
|
|
1183
1242
|
try {
|
|
1184
1243
|
vv = await verifier.verify({
|
package/lib/schema-crl.js
CHANGED
|
@@ -51,17 +51,19 @@ var TIME = pkix.time(NS);
|
|
|
51
51
|
|
|
52
52
|
var CRL_VERSION = pkix.versionReader(NS, { "1": 2 });
|
|
53
53
|
|
|
54
|
-
function decodeExt(ext) {
|
|
54
|
+
function decodeExt(ext, scope) {
|
|
55
55
|
var value = ext.value;
|
|
56
|
+
var atCrl = scope !== "entry";
|
|
57
|
+
var atEntry = scope !== "crl";
|
|
56
58
|
try {
|
|
57
|
-
if (ext.oid === OID_CRL_NUMBER) {
|
|
59
|
+
if (ext.oid === OID_CRL_NUMBER && atCrl) {
|
|
58
60
|
value = asn1.read.integer(asn1.decode(ext.value));
|
|
59
61
|
if (value < 0n) throw new Error("cRLNumber must be non-negative (INTEGER 0..MAX)");
|
|
60
|
-
} else if (ext.oid === OID_REASON_CODE) {
|
|
62
|
+
} else if (ext.oid === OID_REASON_CODE && atEntry) {
|
|
61
63
|
var reason = asn1.read.enumerated(asn1.decode(ext.value));
|
|
62
64
|
if (!_hasOwn(CRL_REASONS, reason.toString())) throw new Error("undefined CRLReason " + reason.toString());
|
|
63
65
|
value = Number(reason);
|
|
64
|
-
} else if (ext.oid === OID_INVALIDITY_DATE) {
|
|
66
|
+
} else if (ext.oid === OID_INVALIDITY_DATE && atEntry) {
|
|
65
67
|
var n = asn1.decode(ext.value);
|
|
66
68
|
if (n.tagClass !== "universal" || n.tagNumber !== TAGS.GENERALIZED_TIME) {
|
|
67
69
|
throw new Error("invalidityDate must be a GeneralizedTime");
|
|
@@ -85,7 +87,9 @@ var REVOKED_ENTRY = schema.seq([
|
|
|
85
87
|
serialNumber: m.fields.userCertificate.value,
|
|
86
88
|
serialNumberHex: m.fields.userCertificate.node.content.toString("hex"),
|
|
87
89
|
revocationDate: m.fields.revocationDate.value,
|
|
88
|
-
crlEntryExtensions: m.fields.crlEntryExtensions.present
|
|
90
|
+
crlEntryExtensions: m.fields.crlEntryExtensions.present
|
|
91
|
+
? m.fields.crlEntryExtensions.value.result.map(function (e) { return decodeExt(e, "entry"); })
|
|
92
|
+
: [],
|
|
89
93
|
};
|
|
90
94
|
},
|
|
91
95
|
});
|
|
@@ -114,7 +118,9 @@ var TBS_CERTLIST = schema.seq([
|
|
|
114
118
|
thisUpdate: m.fields.thisUpdate.value,
|
|
115
119
|
nextUpdate: m.fields.nextUpdate.present ? m.fields.nextUpdate.value : null,
|
|
116
120
|
revokedCertificates: m.fields.revokedCertificates.present ? m.fields.revokedCertificates.value.result : [],
|
|
117
|
-
crlExtensions: m.fields.crlExtensions.present
|
|
121
|
+
crlExtensions: m.fields.crlExtensions.present
|
|
122
|
+
? m.fields.crlExtensions.value.result.map(function (e) { return decodeExt(e, "crl"); })
|
|
123
|
+
: [],
|
|
118
124
|
crlExtensionsPresent: m.fields.crlExtensions.present,
|
|
119
125
|
};
|
|
120
126
|
},
|
package/lib/smime.js
CHANGED
|
@@ -56,7 +56,7 @@ var SIGN_OPTS = Object.assign(Object.create(null), {
|
|
|
56
56
|
});
|
|
57
57
|
var VERIFY_OPTS = Object.assign(Object.create(null), {
|
|
58
58
|
certs: 1, trustAnchors: 1, time: 1, requiredEku: 1, checkPurpose: 1, strictMicalg: 1,
|
|
59
|
-
legacyHeaderProtection: 1, expectedSender: 1,
|
|
59
|
+
legacyHeaderProtection: 1, expectedSender: 1, allowWeakDigests: 1,
|
|
60
60
|
});
|
|
61
61
|
var ENCRYPT_OPTS = Object.assign(Object.create(null), {
|
|
62
62
|
entity: 1, contentType: 1, protectHeaders: 1, headers: 1, hcp: 1,
|
|
@@ -758,6 +758,7 @@ async function verify(message, opts) {
|
|
|
758
758
|
vOpts.checkPurpose = opts.checkPurpose != null ? opts.checkPurpose : "emailProtection";
|
|
759
759
|
}
|
|
760
760
|
if (opts.time !== undefined) vOpts.time = opts.time;
|
|
761
|
+
if (opts.allowWeakDigests === true) vOpts.allowWeakDigests = true;
|
|
761
762
|
if (_isPkcs7(ct.type, "mime")) {
|
|
762
763
|
if (ct.params["smime-type"] && ct.params["smime-type"] !== "signed-data") throw _err("smime/unsupported-type", "unsupported smime-type " + JSON.stringify(ct.params["smime-type"]) + " (only signed-data)");
|
|
763
764
|
var p7m = _decodeCms(ent);
|
package/lib/tsp-sign.js
CHANGED
|
@@ -603,7 +603,7 @@ function _buildTsaChains(leaf, pool) {
|
|
|
603
603
|
* res.valid; // boolean; pass opts.trustAnchors to also chain the TSA cert to a root
|
|
604
604
|
* res.genTime; // Date, read from the verified eContent
|
|
605
605
|
*/
|
|
606
|
-
var _VERIFY_OPTS = intrinsic.assign(intrinsic.create(null), { certs: 1, trustAnchors: 1, nonce: 1, reqPolicy: 1, revocationChecker: 1 });
|
|
606
|
+
var _VERIFY_OPTS = intrinsic.assign(intrinsic.create(null), { certs: 1, trustAnchors: 1, nonce: 1, reqPolicy: 1, revocationChecker: 1, allowWeakDigests: 1 });
|
|
607
607
|
|
|
608
608
|
function _anyCritical(extensions) {
|
|
609
609
|
return guard.list.anyMatches(extensions, function (e) { return !!e && !!e.critical; });
|
|
@@ -631,7 +631,7 @@ async function verify(token, data, opts) {
|
|
|
631
631
|
function fail(code, reason) {
|
|
632
632
|
return guard.verdict.of({ valid: false, trusted: false, revocationChecked: revocationChecked, anchorConstraints: anchorConstraints, code: code, reason: reason || null, genTime: tst.genTime, accuracy: tst.accuracy, serialNumber: tst.serialNumber, serialNumberHex: tst.serialNumberHex, policy: tst.policy, policyName: tst.policyName, nonce: tst.nonce, tsa: tst.tsa, tstInfo: tst, signer: null });
|
|
633
633
|
}
|
|
634
|
-
var cmsRes = await cmsVerify.verify(tokenDer, { certs: opts.certs });
|
|
634
|
+
var cmsRes = await cmsVerify.verify(tokenDer, { certs: opts.certs, allowWeakDigests: opts.allowWeakDigests === true });
|
|
635
635
|
var signer = cmsRes.signers[0];
|
|
636
636
|
if (!cmsRes.valid) return fail("tsp/bad-signature", signer && (signer.code || signer.message));
|
|
637
637
|
var tsaCertDer = signer && signer.cert;
|
package/lib/webcrypto.js
CHANGED
|
@@ -907,6 +907,19 @@ function _preValidatePkcs8(p8, name) {
|
|
|
907
907
|
validate(innerBytes);
|
|
908
908
|
}
|
|
909
909
|
|
|
910
|
+
function _preValidateSpki(spki, name) {
|
|
911
|
+
if (name !== "ECDSA" && name !== "ECDH") return;
|
|
912
|
+
var params;
|
|
913
|
+
try {
|
|
914
|
+
var alg = asn1.decode(spki).children[0];
|
|
915
|
+
params = alg.children.length > 1 ? alg.children[1] : null;
|
|
916
|
+
} catch (e) { throw new WebCryptoError("webcrypto/data", "importKey spki: the SubjectPublicKeyInfo is not well-formed", e); }
|
|
917
|
+
if (params && params.tagClass === "universal" && !params.constructed && params.tagNumber === asn1.TAGS.OBJECT_IDENTIFIER) return;
|
|
918
|
+
throw new WebCryptoError("webcrypto/data",
|
|
919
|
+
"importKey spki: an " + name + " key must name its curve as a namedCurve OBJECT IDENTIFIER, " +
|
|
920
|
+
"and this SubjectPublicKeyInfo carries " + (params ? "explicit domain parameters" : "none") + " (RFC 5480 sec. 2.1.1)");
|
|
921
|
+
}
|
|
922
|
+
|
|
910
923
|
SubtleCrypto.prototype.importKey = async function importKey(format, keyData, algorithm, extractable, keyUsages) {
|
|
911
924
|
var alg = _normalizeAlg(algorithm, "importKey");
|
|
912
925
|
var usages = keyUsages || [];
|
|
@@ -929,6 +942,7 @@ SubtleCrypto.prototype.importKey = async function importKey(format, keyData, alg
|
|
|
929
942
|
|
|
930
943
|
if (format === "spki") {
|
|
931
944
|
var spkiBuf = _toBuf(keyData, "importKey spki");
|
|
945
|
+
_preValidateSpki(spkiBuf, name);
|
|
932
946
|
var pub = _nodeKey(function () { return nodeCrypto.createPublicKey({ key: spkiBuf, format: "der", type: "spki" }); }, "importKey spki");
|
|
933
947
|
return new CryptoKey("public", true, _algFromImport(name, alg, pub), usages, pub);
|
|
934
948
|
}
|
package/package.json
CHANGED
package/sbom.cdx.json
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
"$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json",
|
|
3
3
|
"bomFormat": "CycloneDX",
|
|
4
4
|
"specVersion": "1.5",
|
|
5
|
-
"serialNumber": "urn:uuid:
|
|
5
|
+
"serialNumber": "urn:uuid:3474a203-558f-4bf4-8d04-ca4aec66e1f7",
|
|
6
6
|
"version": 1,
|
|
7
7
|
"metadata": {
|
|
8
|
-
"timestamp": "2026-09-
|
|
8
|
+
"timestamp": "2026-09-19T19:43:19.653Z",
|
|
9
9
|
"lifecycles": [
|
|
10
10
|
{
|
|
11
11
|
"phase": "build"
|
|
@@ -19,14 +19,14 @@
|
|
|
19
19
|
}
|
|
20
20
|
],
|
|
21
21
|
"component": {
|
|
22
|
-
"bom-ref": "@blamejs/pki@0.
|
|
22
|
+
"bom-ref": "@blamejs/pki@0.8.0",
|
|
23
23
|
"type": "application",
|
|
24
24
|
"name": "pki",
|
|
25
|
-
"version": "0.
|
|
25
|
+
"version": "0.8.0",
|
|
26
26
|
"scope": "required",
|
|
27
27
|
"author": "blamejs contributors",
|
|
28
28
|
"description": "Pure-JavaScript PKI toolkit that owns its stack — X.509, ASN.1/DER, CMS, PQC-first.",
|
|
29
|
-
"purl": "pkg:npm/%40blamejs/pki@0.
|
|
29
|
+
"purl": "pkg:npm/%40blamejs/pki@0.8.0",
|
|
30
30
|
"properties": [],
|
|
31
31
|
"externalReferences": [
|
|
32
32
|
{
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"components": [],
|
|
55
55
|
"dependencies": [
|
|
56
56
|
{
|
|
57
|
-
"ref": "@blamejs/pki@0.
|
|
57
|
+
"ref": "@blamejs/pki@0.8.0",
|
|
58
58
|
"dependsOn": []
|
|
59
59
|
}
|
|
60
60
|
]
|