@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.
@@ -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
- try { return asn1.decode(der); }
81
- catch (e) { throw new opts.ErrorClass(opts.prefix + "/bad-der", (opts.what || "input") + " DER did not decode: " + ((e && e.message) || String(e)), e); }
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
- if (pl < 0n) throw ns.E(C, "BasicConstraints pathLenConstraint must be non-negative");
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: DER (X.690 §11.2.2) strips all trailing zero
408
- // bits, so the last content octet must be non-zero and the lowest USED bit
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
- if (v < 0n) throw ns.E(C, "PolicyConstraints skip count must be non-negative");
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
- if (v < 0n) throw ns.E(C, "InhibitAnyPolicy skip count must be non-negative");
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, code: ns.prefix + "/bad-attribute-values", what: "attribute values" })),
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 (!root || root.tagClass !== "universal" || root.tagNumber !== asn1.TAGS.SEQUENCE) return null;
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 || tbs.tagClass !== "universal" || tbs.tagNumber !== asn1.TAGS.SEQUENCE) return null;
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
- if (bytes.length === 0) {
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 experimental
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 experimental
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 experimental
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.walk(TST_INFO, asn1.decode(encap.eContent), NS); }
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 experimental
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
- if (!root || root.tagClass !== "universal" || root.tagNumber !== TAGS.SEQUENCE || !root.children) return false;
396
- var k = root.children;
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 (!(si.tagClass === "universal" && si.tagNumber === TAGS.SEQUENCE && si.children && si.children.length >= 1)) return false;
400
- if (!(si.children[0].tagClass === "universal" && si.children[0].tagNumber === TAGS.INTEGER)) return false;
401
- if (k.length === 2 && !(k[1].tagClass === "universal" && k[1].tagNumber === TAGS.SEQUENCE)) return false;
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
 
@@ -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] && kids[0].tagClass === "context" && kids[0].tagNumber === 0) ? 1 : 0; // optional [0] version
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 !!validity && validity.tagClass === "universal" && validity.tagNumber === TAGS.SEQUENCE &&
225
+ return schema.isUniversal(validity, TAGS.SEQUENCE) &&
226
226
  !!validity.children && validity.children.length === 2 &&
227
227
  validity.children.every(function (t) {
228
- return t.tagClass === "universal" && (t.tagNumber === TAGS.UTC_TIME || t.tagNumber === TAGS.GENERALIZED_TIME);
228
+ return schema.isUniversalOneOf(t, [TAGS.UTC_TIME, TAGS.GENERALIZED_TIME]);
229
229
  });
230
230
  }
231
231
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blamejs/pki",
3
- "version": "0.1.17",
3
+ "version": "0.1.19",
4
4
  "description": "Pure-JavaScript PKI toolkit that owns its stack — X.509, ASN.1/DER, CMS, PQC-first.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "blamejs contributors",
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:b6db5d53-2a3e-46f9-9187-9c2f6fd9249b",
5
+ "serialNumber": "urn:uuid:c8d0b092-58fa-4019-9da1-b2b636e61608",
6
6
  "version": 1,
7
7
  "metadata": {
8
- "timestamp": "2026-07-07T05:30:55.753Z",
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.17",
22
+ "bom-ref": "@blamejs/pki@0.1.19",
23
23
  "type": "application",
24
24
  "name": "pki",
25
- "version": "0.1.17",
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.17",
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.17",
57
+ "ref": "@blamejs/pki@0.1.19",
58
58
  "dependsOn": []
59
59
  }
60
60
  ]