@blamejs/pki 0.1.17 → 0.1.19
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 +45 -1
- package/README.md +9 -7
- package/lib/asn1-der.js +109 -22
- package/lib/constants.js +16 -0
- package/lib/framework-error.js +14 -0
- package/lib/oid.js +48 -2
- package/lib/path-validate.js +14 -2
- package/lib/schema-all.js +45 -8
- package/lib/schema-attrcert.js +6 -6
- package/lib/schema-cmp.js +928 -0
- package/lib/schema-cms.js +20 -13
- package/lib/schema-crl.js +2 -2
- package/lib/schema-crmf.js +63 -29
- package/lib/schema-csr.js +4 -4
- package/lib/schema-engine.js +114 -8
- package/lib/schema-ocsp.js +10 -14
- package/lib/schema-pkcs12.js +614 -0
- package/lib/schema-pkcs8.js +16 -7
- package/lib/schema-pkix.js +40 -15
- package/lib/schema-tsp.js +11 -18
- package/lib/schema-x509.js +3 -3
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/lib/schema-pkix.js
CHANGED
|
@@ -77,8 +77,17 @@ function _isPemArmor(buf) {
|
|
|
77
77
|
|
|
78
78
|
// Decode the DER root, wrapping a codec fault in the caller's <prefix>/bad-der.
|
|
79
79
|
function decodeRoot(der, opts) {
|
|
80
|
-
|
|
81
|
-
|
|
80
|
+
// A format whose wire encoding is normatively BER (RFC 7292 PKCS#12) opts
|
|
81
|
+
// in to the codec's ber mode for the WHOLE decode. BER-vs-DER divergence is
|
|
82
|
+
// not always a decode-time throw — a definite-length constructed IMPLICIT
|
|
83
|
+
// OCTET STRING decodes strictly and only diverges at the typed reader — so
|
|
84
|
+
// a strict-first-retry-on-throw boundary would miss legal BER shapes. A
|
|
85
|
+
// strict-DER input decodes identically under the ber mode, every other
|
|
86
|
+
// strictness verdict included.
|
|
87
|
+
try { return asn1.decode(der, opts.ber ? { ber: true } : undefined); }
|
|
88
|
+
catch (e) {
|
|
89
|
+
throw new opts.ErrorClass(opts.prefix + "/bad-der", (opts.what || "input") + " DER did not decode: " + ((e && e.message) || String(e)), e);
|
|
90
|
+
}
|
|
82
91
|
}
|
|
83
92
|
|
|
84
93
|
// The shared parse entry: coerce -> decode -> walk the top schema. A format's
|
|
@@ -383,7 +392,10 @@ function certExtensionDecoders(ns) {
|
|
|
383
392
|
if (kids[i] && kids[i].tagClass === "universal" && kids[i].tagNumber === _T.INTEGER) {
|
|
384
393
|
if (!cA) throw ns.E(C, "BasicConstraints pathLenConstraint is only permitted when cA is TRUE (RFC 5280 §4.2.1.9)");
|
|
385
394
|
var pl = readInt(kids[i], C, "pathLenConstraint");
|
|
386
|
-
|
|
395
|
+
// Bounded before Number() narrowing — a value past the safe-integer range
|
|
396
|
+
// would round silently and be compared as a path-length ceiling, so it is
|
|
397
|
+
// exact-or-rejected (the same rule the RSASSA-PSS/PKCS#12 counters follow).
|
|
398
|
+
if (pl < 0n || pl > 2147483647n) throw ns.E(C, "BasicConstraints pathLenConstraint must be a non-negative integer within range");
|
|
387
399
|
pathLen = Number(pl); i++;
|
|
388
400
|
}
|
|
389
401
|
if (i !== kids.length) throw ns.E(C, "BasicConstraints has unexpected trailing fields");
|
|
@@ -404,13 +416,8 @@ function certExtensionDecoders(ns) {
|
|
|
404
416
|
var anyBit = false;
|
|
405
417
|
for (var z = 0; z < bs.bytes.length; z++) { if (bs.bytes[z] !== 0) { anyBit = true; break; } }
|
|
406
418
|
if (!anyBit) throw ns.E(C, "KeyUsage must assert at least one bit (RFC 5280 §4.2.1.3)");
|
|
407
|
-
// KeyUsage is a NamedBitList
|
|
408
|
-
|
|
409
|
-
// (at position unusedBits) must be set — one canonical encoding per value.
|
|
410
|
-
var last = bs.bytes[bs.bytes.length - 1];
|
|
411
|
-
if (last === 0 || ((last >> bs.unusedBits) & 1) !== 1) {
|
|
412
|
-
throw ns.E(C, "KeyUsage must use the minimal DER NamedBitList encoding (no trailing zero bits, X.690 §11.2.2)");
|
|
413
|
-
}
|
|
419
|
+
// KeyUsage is a NamedBitList — enforce the shared X.690 §11.2.2 minimal-DER rule.
|
|
420
|
+
schema.assertMinimalNamedBits(bs.unusedBits, bs.bytes, function (m) { throw ns.E(C, m); });
|
|
414
421
|
var out = {};
|
|
415
422
|
KU_BITS.forEach(function (nm, bit) {
|
|
416
423
|
var byte = bit >> 3, mask = 0x80 >> (bit & 7);
|
|
@@ -523,7 +530,9 @@ function certExtensionDecoders(ns) {
|
|
|
523
530
|
pcLastTag = k.tagNumber;
|
|
524
531
|
var v;
|
|
525
532
|
try { v = asn1.read.integerImplicit(k, k.tagNumber); } catch (e) { throw ns.E(C, "PolicyConstraints field must be an INTEGER", e); }
|
|
526
|
-
|
|
533
|
+
// Bounded before Number() narrowing (a skip count past the safe-integer
|
|
534
|
+
// range would round silently) — exact-or-rejected.
|
|
535
|
+
if (v < 0n || v > 2147483647n) throw ns.E(C, "PolicyConstraints skip count must be a non-negative integer within range");
|
|
527
536
|
if (k.tagNumber === 0) rep = Number(v);
|
|
528
537
|
else if (k.tagNumber === 1) ipm = Number(v);
|
|
529
538
|
else throw ns.E(C, "PolicyConstraints has an unexpected field [" + k.tagNumber + "]");
|
|
@@ -537,7 +546,9 @@ function certExtensionDecoders(ns) {
|
|
|
537
546
|
var n = decodeTop(buf, C, "InhibitAnyPolicy");
|
|
538
547
|
if (n.tagClass !== "universal" || n.tagNumber !== _T.INTEGER) throw ns.E(C, "InhibitAnyPolicy must be an INTEGER (RFC 5280 §4.2.1.14)");
|
|
539
548
|
var v = readInt(n, C, "InhibitAnyPolicy");
|
|
540
|
-
|
|
549
|
+
// Bounded before Number() narrowing (a skip count past the safe-integer
|
|
550
|
+
// range would round silently) — exact-or-rejected.
|
|
551
|
+
if (v < 0n || v > 2147483647n) throw ns.E(C, "InhibitAnyPolicy skip count must be a non-negative integer within range");
|
|
541
552
|
return Number(v);
|
|
542
553
|
}
|
|
543
554
|
|
|
@@ -692,7 +703,8 @@ function spki(ns, opts) {
|
|
|
692
703
|
function attribute(ns) {
|
|
693
704
|
return schema.seq([
|
|
694
705
|
schema.field("type", schema.oidLeaf()),
|
|
695
|
-
schema.field("values", schema.setOf(schema.any(), { assert: "set", min: 1,
|
|
706
|
+
schema.field("values", schema.setOf(schema.any(), { assert: "set", min: 1, max: constants.LIMITS.ATTRIBUTE_MAX_VALUES,
|
|
707
|
+
code: ns.prefix + "/bad-attribute-values", what: "attribute values" })),
|
|
696
708
|
], {
|
|
697
709
|
assert: "sequence", arity: { exact: 2 }, code: ns.prefix + "/bad-attribute", what: "Attribute",
|
|
698
710
|
build: function (m, ctx) {
|
|
@@ -715,13 +727,25 @@ function attribute(ns) {
|
|
|
715
727
|
// detectors cannot drift on it (the CRL detector historically omitted the
|
|
716
728
|
// tbs-is-universal check this recovers).
|
|
717
729
|
function signedEnvelopeTbs(root) {
|
|
718
|
-
if (!
|
|
730
|
+
if (!schema.isUniversal(root, asn1.TAGS.SEQUENCE)) return null;
|
|
719
731
|
if (!root.children || root.children.length !== 3) return null;
|
|
720
732
|
var tbs = root.children[0];
|
|
721
|
-
if (!tbs.children ||
|
|
733
|
+
if (!tbs.children || !schema.isUniversal(tbs, asn1.TAGS.SEQUENCE)) return null;
|
|
722
734
|
return tbs;
|
|
723
735
|
}
|
|
724
736
|
|
|
737
|
+
// rootSequenceChildren(root, minLen, maxLen) — the shared root-shape guard the
|
|
738
|
+
// format matches() detectors re-inline: returns root.children when root is a
|
|
739
|
+
// NON-EMPTY universal SEQUENCE whose child count is within [minLen, maxLen]
|
|
740
|
+
// (maxLen optional/Infinity), else null. Sibling of signedEnvelopeTbs.
|
|
741
|
+
function rootSequenceChildren(root, minLen, maxLen) {
|
|
742
|
+
if (!schema.isUniversal(root, asn1.TAGS.SEQUENCE) || !root.children) return null;
|
|
743
|
+
var n = root.children.length;
|
|
744
|
+
if (n < (minLen || 0)) return null;
|
|
745
|
+
if (maxLen != null && n > maxLen) return null;
|
|
746
|
+
return root.children;
|
|
747
|
+
}
|
|
748
|
+
|
|
725
749
|
// Every format's `parse` is the shared runParse bound to that format's identity
|
|
726
750
|
// (PEM label, error class, error-code prefix, top-level schema). This returns the
|
|
727
751
|
// bound parser so a format declares its configuration once and never re-writes the
|
|
@@ -775,6 +799,7 @@ module.exports = {
|
|
|
775
799
|
spki: spki,
|
|
776
800
|
makeParser: makeParser,
|
|
777
801
|
signedEnvelopeTbs: signedEnvelopeTbs,
|
|
802
|
+
rootSequenceChildren: rootSequenceChildren,
|
|
778
803
|
signedEnvelope: signedEnvelope,
|
|
779
804
|
attrValueToString: attrValueToString,
|
|
780
805
|
attributeTypeAndValue: attributeTypeAndValue,
|
package/lib/schema-tsp.js
CHANGED
|
@@ -217,13 +217,7 @@ var PKI_STATUS_INFO = schema.seq([
|
|
|
217
217
|
// declared unusedBits must sit exactly below the lowest set bit of the last octet (so
|
|
218
218
|
// the encoding is minimal). The empty value encodes as 0 content octets, 0 unusedBits.
|
|
219
219
|
function _assertMinimalNamedBits(unusedBits, bytes) {
|
|
220
|
-
|
|
221
|
-
if (unusedBits !== 0) throw NS.E("tsp/bad-failinfo", "an empty PKIFailureInfo must encode with 0 unused bits (X.690 §11.2.2)");
|
|
222
|
-
return;
|
|
223
|
-
}
|
|
224
|
-
var last = bytes[bytes.length - 1];
|
|
225
|
-
if (last === 0) throw NS.E("tsp/bad-failinfo", "PKIFailureInfo NamedBitList must not have a trailing all-zero octet (X.690 §11.2.2)");
|
|
226
|
-
if (((last >> unusedBits) & 1) !== 1) throw NS.E("tsp/bad-failinfo", "PKIFailureInfo NamedBitList must have all trailing zero bits removed (X.690 §11.2.2)");
|
|
220
|
+
schema.assertMinimalNamedBits(unusedBits, bytes, function (m) { throw NS.E("tsp/bad-failinfo", m); });
|
|
227
221
|
}
|
|
228
222
|
|
|
229
223
|
// Decode the set NamedBitList bits to their RFC 3161 names (bit 0 = MSB of byte 0).
|
|
@@ -277,7 +271,7 @@ var TIME_STAMP_RESP = schema.seq([
|
|
|
277
271
|
* @primitive pki.schema.tsp.parseTstInfo
|
|
278
272
|
* @signature pki.schema.tsp.parseTstInfo(input) -> tstInfo
|
|
279
273
|
* @since 0.1.13
|
|
280
|
-
* @status
|
|
274
|
+
* @status stable
|
|
281
275
|
* @spec RFC 3161
|
|
282
276
|
* @related pki.schema.tsp.parseToken, pki.schema.tsp.parse
|
|
283
277
|
*
|
|
@@ -299,7 +293,7 @@ var parseTstInfo = pkix.makeParser({ pemLabel: null, PemError: PemError, ErrorCl
|
|
|
299
293
|
* @primitive pki.schema.tsp.parse
|
|
300
294
|
* @signature pki.schema.tsp.parse(input) -> timeStampResp
|
|
301
295
|
* @since 0.1.13
|
|
302
|
-
* @status
|
|
296
|
+
* @status stable
|
|
303
297
|
* @spec RFC 3161
|
|
304
298
|
* @related pki.schema.tsp.parseToken, pki.schema.parse
|
|
305
299
|
*
|
|
@@ -320,7 +314,7 @@ var parse = pkix.makeParser({ pemLabel: null, PemError: PemError, ErrorClass: Ts
|
|
|
320
314
|
* @primitive pki.schema.tsp.parseToken
|
|
321
315
|
* @signature pki.schema.tsp.parseToken(input) -> tstInfo
|
|
322
316
|
* @since 0.1.13
|
|
323
|
-
* @status
|
|
317
|
+
* @status stable
|
|
324
318
|
* @spec RFC 3161, RFC 5652
|
|
325
319
|
* @related pki.schema.tsp.parse, pki.schema.cms.parse
|
|
326
320
|
*
|
|
@@ -359,7 +353,7 @@ function parseToken(input) {
|
|
|
359
353
|
throw new TspError("tsp/multi-signer", "a TimeStampToken must contain exactly one (TSA) signerInfo (RFC 3161 §2.4.2)");
|
|
360
354
|
}
|
|
361
355
|
var tstInfo;
|
|
362
|
-
try { tstInfo = schema.
|
|
356
|
+
try { tstInfo = schema.embeddedDer(TST_INFO, encap.eContent, NS, { code: "tsp/bad-der", what: "the encapsulated TSTInfo" }); }
|
|
363
357
|
catch (e) {
|
|
364
358
|
if (e instanceof TspError) throw e;
|
|
365
359
|
throw new TspError("tsp/bad-der", "the encapsulated TSTInfo did not decode: " + ((e && e.message) || String(e)), e);
|
|
@@ -374,7 +368,7 @@ function parseToken(input) {
|
|
|
374
368
|
* @primitive pki.schema.tsp.pemDecode
|
|
375
369
|
* @signature pki.schema.tsp.pemDecode(text, label?) -> Buffer
|
|
376
370
|
* @since 0.1.13
|
|
377
|
-
* @status
|
|
371
|
+
* @status stable
|
|
378
372
|
* @spec RFC 7468, RFC 3161
|
|
379
373
|
* @related pki.schema.tsp.parse
|
|
380
374
|
*
|
|
@@ -392,13 +386,12 @@ function pemDecode(text, label) { return pkix.pemDecode(text, label || null, Pem
|
|
|
392
386
|
// CMS ContentInfo, the INTEGER-first PKCS#8 key, and the exactly-3 signed-envelope
|
|
393
387
|
// trio, so it detects unambiguously regardless of registry order.
|
|
394
388
|
function matches(root) {
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
if (k.length < 1 || k.length > 2) return false;
|
|
389
|
+
var k = pkix.rootSequenceChildren(root, 1, 2);
|
|
390
|
+
if (!k) return false;
|
|
398
391
|
var si = k[0];
|
|
399
|
-
if (!(
|
|
400
|
-
if (!(si.children[0]
|
|
401
|
-
if (k.length === 2 && !(k[1]
|
|
392
|
+
if (!(schema.isUniversal(si, TAGS.SEQUENCE) && si.children && si.children.length >= 1)) return false;
|
|
393
|
+
if (!schema.isUniversal(si.children[0], TAGS.INTEGER)) return false;
|
|
394
|
+
if (k.length === 2 && !schema.isUniversal(k[1], TAGS.SEQUENCE)) return false;
|
|
402
395
|
return true;
|
|
403
396
|
}
|
|
404
397
|
|
package/lib/schema-x509.js
CHANGED
|
@@ -220,12 +220,12 @@ function matches(root) {
|
|
|
220
220
|
var tbs = pkix.signedEnvelopeTbs(root);
|
|
221
221
|
if (!tbs) return false;
|
|
222
222
|
var kids = tbs.children;
|
|
223
|
-
var i = (kids[0]
|
|
223
|
+
var i = schema.isContext(kids[0], 0) ? 1 : 0; // optional [0] version
|
|
224
224
|
var validity = kids[i + 3]; // serial(i), signature(i+1), issuer(i+2), validity(i+3)
|
|
225
|
-
return
|
|
225
|
+
return schema.isUniversal(validity, TAGS.SEQUENCE) &&
|
|
226
226
|
!!validity.children && validity.children.length === 2 &&
|
|
227
227
|
validity.children.every(function (t) {
|
|
228
|
-
return
|
|
228
|
+
return schema.isUniversalOneOf(t, [TAGS.UTC_TIME, TAGS.GENERALIZED_TIME]);
|
|
229
229
|
});
|
|
230
230
|
}
|
|
231
231
|
|
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:c8d0b092-58fa-4019-9da1-b2b636e61608",
|
|
6
6
|
"version": 1,
|
|
7
7
|
"metadata": {
|
|
8
|
-
"timestamp": "2026-07-
|
|
8
|
+
"timestamp": "2026-07-09T11:21:47.988Z",
|
|
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.1.
|
|
22
|
+
"bom-ref": "@blamejs/pki@0.1.19",
|
|
23
23
|
"type": "application",
|
|
24
24
|
"name": "pki",
|
|
25
|
-
"version": "0.1.
|
|
25
|
+
"version": "0.1.19",
|
|
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.1.
|
|
29
|
+
"purl": "pkg:npm/%40blamejs/pki@0.1.19",
|
|
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.1.
|
|
57
|
+
"ref": "@blamejs/pki@0.1.19",
|
|
58
58
|
"dependsOn": []
|
|
59
59
|
}
|
|
60
60
|
]
|