@blamejs/pki 0.1.29 → 0.1.31

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/lib/jose.js CHANGED
@@ -38,6 +38,7 @@
38
38
  var constants = require("./constants");
39
39
  var webcrypto = require("./webcrypto").webcrypto;
40
40
  var frameworkError = require("./framework-error");
41
+ var guard = require("./guard-all");
41
42
 
42
43
  var JoseError = frameworkError.JoseError;
43
44
  function E(code, message, cause) { return new JoseError(code, message, cause); }
@@ -52,7 +53,7 @@ var B64U_ALPHABET = /^[A-Za-z0-9_-]*$/;
52
53
  * @primitive pki.jose.base64url.encode
53
54
  * @signature pki.jose.base64url.encode(bytes) -> string
54
55
  * @since 0.1.25
55
- * @status experimental
56
+ * @status stable
56
57
  * @spec RFC 7515, RFC 4648
57
58
  * @related pki.jose.base64url.decode
58
59
  *
@@ -71,7 +72,7 @@ function b64uEncode(bytes) {
71
72
  * @primitive pki.jose.base64url.decode
72
73
  * @signature pki.jose.base64url.decode(text) -> Buffer
73
74
  * @since 0.1.25
74
- * @status experimental
75
+ * @status stable
75
76
  * @spec RFC 7515, RFC 4648, RFC 8555
76
77
  * @related pki.jose.base64url.encode
77
78
  *
@@ -213,7 +214,7 @@ function _jsonReader(str) {
213
214
  * @primitive pki.jose.parseJson
214
215
  * @signature pki.jose.parseJson(input) -> value
215
216
  * @since 0.1.25
216
- * @status experimental
217
+ * @status stable
217
218
  * @spec RFC 7515, RFC 8259
218
219
  * @related pki.jose.base64url.decode
219
220
  *
@@ -228,17 +229,11 @@ function _jsonReader(str) {
228
229
  * pki.jose.parseJson('{"a":1}'); // -> { a: 1 }
229
230
  */
230
231
  function parseJson(input) {
231
- var str;
232
- if (Buffer.isBuffer(input)) {
233
- if (input.length > LIMITS.JSON_MAX_BYTES) throw E("jose/too-large", "the JSON document exceeds the size cap");
234
- try { str = new TextDecoder("utf-8", { fatal: true }).decode(input); }
235
- catch (e) { throw E("jose/bad-json", "the JSON document is not valid UTF-8", e); }
236
- } else if (typeof input === "string") {
237
- if (Buffer.byteLength(input, "utf8") > LIMITS.JSON_MAX_BYTES) throw E("jose/too-large", "the JSON document exceeds the size cap");
238
- str = input;
239
- } else {
240
- throw E("jose/bad-input", "parseJson requires a Buffer or string");
241
- }
232
+ // guard.text.decode caps the raw byte length BEFORE the (strict, fatal) UTF-8
233
+ // decode, so an oversized document is rejected before it is materialized.
234
+ var str = guard.text.decode(input, LIMITS.JSON_MAX_BYTES, JoseError, {
235
+ charset: "utf-8", fatal: true, tooLarge: "jose/too-large", badDecode: "jose/bad-json", badInput: "jose/bad-input", label: "the JSON document",
236
+ });
242
237
  return _jsonReader(str);
243
238
  }
244
239
 
@@ -413,7 +408,7 @@ var PRIVATE_JWK_MEMBERS = ["d", "p", "q", "dp", "dq", "qi", "k", "priv"];
413
408
  * @primitive pki.jose.assertPublicJwk
414
409
  * @signature pki.jose.assertPublicJwk(jwk) -> jwk
415
410
  * @since 0.1.25
416
- * @status experimental
411
+ * @status stable
417
412
  * @spec RFC 7517, RFC 7518
418
413
  * @related pki.jose.sign, pki.jose.thumbprint
419
414
  *
@@ -440,7 +435,7 @@ function assertPublicJwk(jwk) {
440
435
  * @primitive pki.jose.verify
441
436
  * @signature pki.jose.verify(jws, opts) -> Promise<{ header, payload }>
442
437
  * @since 0.1.25
443
- * @status experimental
438
+ * @status stable
444
439
  * @spec RFC 7515, RFC 7518, RFC 8555
445
440
  * @related pki.jose.sign, pki.jose.parseJson
446
441
  *
@@ -493,7 +488,7 @@ async function verify(jws, opts) {
493
488
  * @primitive pki.jose.sign
494
489
  * @signature pki.jose.sign(opts) -> Promise<{ protected, payload, signature }>
495
490
  * @since 0.1.25
496
- * @status experimental
491
+ * @status stable
497
492
  * @spec RFC 7515, RFC 7518, RFC 8555
498
493
  * @related pki.jose.verify
499
494
  *
@@ -579,7 +574,7 @@ var THUMBPRINT_MEMBERS = {
579
574
  * @primitive pki.jose.thumbprint
580
575
  * @signature pki.jose.thumbprint(jwk) -> Promise<string>
581
576
  * @since 0.1.25
582
- * @status experimental
577
+ * @status stable
583
578
  * @spec RFC 7638, RFC 8037, RFC 9964
584
579
  * @related pki.jose.verify
585
580
  *
package/lib/merkle.js CHANGED
@@ -44,6 +44,7 @@
44
44
  var nodeCrypto = require("node:crypto");
45
45
  var constants = require("./constants");
46
46
  var frameworkError = require("./framework-error");
47
+ var guard = require("./guard-all");
47
48
 
48
49
  var MerkleError = frameworkError.MerkleError;
49
50
 
@@ -58,17 +59,7 @@ function _sha256(buf) {
58
59
  }
59
60
 
60
61
  function _toBuffer(v, field) {
61
- if (!Buffer.isBuffer(v) && !(v instanceof Uint8Array)) {
62
- throw new MerkleError("merkle/bad-input", field + " must be a Buffer or Uint8Array");
63
- }
64
- try {
65
- // A zero-copy view over the same bytes; this also surfaces a detached
66
- // backing ArrayBuffer (a transferred / structuredClone'd view) as a typed
67
- // error here rather than a raw TypeError from a later concat / digest.
68
- return Buffer.from(v.buffer, v.byteOffset, v.byteLength);
69
- } catch (e) {
70
- throw new MerkleError("merkle/bad-input", field + " is not a usable byte view", e);
71
- }
62
+ return guard.bytes.view(v, MerkleError, "merkle/bad-input", field);
72
63
  }
73
64
 
74
65
  function _node32(v, field) {
@@ -113,7 +104,7 @@ function _proofNodes(proof) {
113
104
 
114
105
  // Constant-time equality of two already-validated equal-length 32-byte buffers.
115
106
  function _ctEq(a, b) {
116
- return a.length === b.length && nodeCrypto.timingSafeEqual(a, b);
107
+ return guard.crypto.constantTimeEqual(a, b);
117
108
  }
118
109
 
119
110
  /**
package/lib/oid.js CHANGED
@@ -35,6 +35,7 @@
35
35
 
36
36
  var asn1 = require("./asn1-der");
37
37
  var frameworkError = require("./framework-error");
38
+ var guard = require("./guard-all");
38
39
 
39
40
  var OidError = frameworkError.OidError;
40
41
 
@@ -454,7 +455,7 @@ function fromArcs(arcs) {
454
455
  // one namespace when they have a dotted string in hand.
455
456
  function toDER(dotted) { _assertDotted(dotted, "toDER"); return asn1.build.oid(dotted); }
456
457
  function fromDER(input) {
457
- var buf = Buffer.isBuffer(input) ? input : Buffer.from(input);
458
+ var buf = guard.bytes.view(input, OidError, "oid/bad-input", "fromDER");
458
459
  var node = asn1.decode(buf);
459
460
  return asn1.read.oid(node);
460
461
  }
@@ -37,6 +37,7 @@ var asn1 = require("./asn1-der");
37
37
  var schema = require("./schema-engine");
38
38
  var x509 = require("./schema-x509");
39
39
  var crl = require("./schema-crl");
40
+ var guard = require("./guard-all");
40
41
  var constants = require("./constants");
41
42
 
42
43
  var PathError = errors.PathError;
@@ -226,14 +227,11 @@ function resolveRsaPss(paramsBytes) {
226
227
  // verifier binds to the salt length the certificate states, so a value
227
228
  // that would round is not verifiable material (no real salt exceeds the
228
229
  // modulus size, let alone this).
229
- if (sl < 0n || sl > 2147483647n) throw E("path/unsupported-algorithm", "RSASSA-PSS saltLength must be a non-negative integer within the salt-length range");
230
- saltLength = Number(sl);
230
+ saltLength = guard.range.uint31(sl, E, "path/unsupported-algorithm", "RSASSA-PSS saltLength");
231
231
  } else if (f.tagNumber === 3) {
232
232
  // Compared for equality with 1 below -- bound before conversion so an
233
233
  // oversized value cannot round on its way to the comparison.
234
- var tf = asn1.read.integer(f.children[0]);
235
- if (tf < 0n || tf > 2147483647n) throw E("path/unsupported-algorithm", "unsupported RSASSA-PSS trailerField " + tf.toString());
236
- trailer = Number(tf);
234
+ trailer = guard.range.uint31(asn1.read.integer(f.children[0]), E, "path/unsupported-algorithm", "RSASSA-PSS trailerField");
237
235
  }
238
236
  });
239
237
  if (hash === null) throw E("path/unsupported-algorithm", "RSASSA-PSS hashAlgorithm must be stated explicitly (the SHA-1 default is rejected)");
@@ -347,10 +345,7 @@ function normalizeAttrValue(v) {
347
345
  // Reject embedded NUL / control bytes so a truncation attack (CVE-2009-2408)
348
346
  // cannot make two different names compare equal.
349
347
  if (typeof v !== "string") return v;
350
- for (var i = 0; i < v.length; i++) {
351
- var c = v.charCodeAt(i);
352
- if (c === 0 || (c < 0x20 && c !== 0x09)) throw E("path/name-chaining", "distinguished name contains an embedded control byte (CVE-2009-2408)");
353
- }
348
+ guard.name.assertNoControlBytes(v, E, "path/name-chaining", "distinguished name");
354
349
  return v.trim().replace(/\s+/g, " ").toLowerCase();
355
350
  }
356
351
 
package/lib/schema-all.js CHANGED
@@ -249,7 +249,7 @@ var FORMATS = [
249
249
  * @primitive pki.schema.all
250
250
  * @signature pki.schema.all() -> string[]
251
251
  * @since 0.1.7
252
- * @status experimental
252
+ * @status stable
253
253
  * @spec RFC 5280
254
254
  * @related pki.schema.parse
255
255
  *
@@ -264,7 +264,7 @@ function all() { return FORMATS.map(function (f) { return f.name; }); }
264
264
  * @primitive pki.schema.parse
265
265
  * @signature pki.schema.parse(input) -> parsed
266
266
  * @since 0.1.7
267
- * @status experimental
267
+ * @status stable
268
268
  * @spec RFC 5280
269
269
  * @related pki.schema.x509, pki.schema.all
270
270
  *
@@ -299,7 +299,7 @@ var ATTRIBUTE_CERTIFICATE = pkix.signedEnvelope(NS, ACINFO, {
299
299
  * @primitive pki.schema.attrcert.parse
300
300
  * @signature pki.schema.attrcert.parse(input) -> attributeCertificate
301
301
  * @since 0.1.14
302
- * @status experimental
302
+ * @status stable
303
303
  * @spec RFC 5755, X.509
304
304
  * @related pki.schema.parse, pki.schema.x509.parse
305
305
  *
@@ -348,7 +348,7 @@ function parseV1(input) {
348
348
  * @primitive pki.schema.attrcert.pemDecode
349
349
  * @signature pki.schema.attrcert.pemDecode(text, label?) -> Buffer
350
350
  * @since 0.1.14
351
- * @status experimental
351
+ * @status stable
352
352
  * @spec RFC 7468, RFC 5755
353
353
  * @related pki.schema.attrcert.parse
354
354
  *
@@ -367,7 +367,7 @@ function pemDecode(text, label) { return pkix.pemDecode(text, label === null ? n
367
367
  * @primitive pki.schema.attrcert.pemEncode
368
368
  * @signature pki.schema.attrcert.pemEncode(der, label?) -> string
369
369
  * @since 0.1.23
370
- * @status experimental
370
+ * @status stable
371
371
  * @spec RFC 7468, RFC 5755
372
372
  * @related pki.schema.attrcert.pemDecode
373
373
  *
package/lib/schema-cmp.js CHANGED
@@ -42,6 +42,7 @@ var pkix = require("./schema-pkix.js");
42
42
  var cms = require("./schema-cms.js");
43
43
  var crmf = require("./schema-crmf.js");
44
44
  var frameworkError = require("./framework-error.js");
45
+ var guard = require("./guard-all.js");
45
46
 
46
47
  var CmpError = frameworkError.CmpError;
47
48
  var PemError = frameworkError.PemError;
@@ -99,9 +100,7 @@ var PKI_FAILURE_INFO = schema.decode(function (n, ctx) {
99
100
  // PollRep checkAfter is a delay in seconds -- negative poisons scheduling, and
100
101
  // the value surfaces as an exact number or not at all.
101
102
  var CHECK_AFTER = schema.decode(function (n, ctx) {
102
- var v = asn1.read.integer(n);
103
- if (v < 0n || v > 2147483647n) throw ctx.E("cmp/bad-poll-rep", "checkAfter must be a non-negative delay in seconds (RFC 9810 sec. 5.3.22)");
104
- return Number(v);
103
+ return guard.range.uint31(asn1.read.integer(n), ctx.E, "cmp/bad-poll-rep", "checkAfter delay in seconds (RFC 9810 sec. 5.3.22)");
105
104
  });
106
105
 
107
106
  // A raw certificate / CRL / publication-info element -- surfaced byte-exact for
@@ -841,7 +840,7 @@ var PKI_MESSAGE = schema.seq([
841
840
  * @primitive pki.schema.cmp.parse
842
841
  * @signature pki.schema.cmp.parse(input) -> pkiMessage
843
842
  * @since 0.1.19
844
- * @status experimental
843
+ * @status stable
845
844
  * @spec RFC 9810, RFC 9483, RFC 9481
846
845
  * @related pki.schema.parse, pki.schema.crmf.parse, pki.schema.cms.parse
847
846
  *
@@ -895,7 +894,7 @@ var parse = pkix.makeParser({
895
894
  * @primitive pki.schema.cmp.pemDecode
896
895
  * @signature pki.schema.cmp.pemDecode(text, label?) -> Buffer
897
896
  * @since 0.1.19
898
- * @status experimental
897
+ * @status stable
899
898
  * @spec RFC 7468, RFC 9810
900
899
  * @related pki.schema.cmp.parse
901
900
  *
@@ -912,7 +911,7 @@ function pemDecode(text, label) { return pkix.pemDecode(text, label || "CMP", Pe
912
911
  * @primitive pki.schema.cmp.pemEncode
913
912
  * @signature pki.schema.cmp.pemEncode(der, label?) -> string
914
913
  * @since 0.1.19
915
- * @status experimental
914
+ * @status stable
916
915
  * @spec RFC 7468
917
916
  * @related pki.schema.cmp.pemDecode
918
917
  *
@@ -455,7 +455,7 @@ var CERT_REQ_MESSAGES = schema.seqOf(CERT_REQ_MSG, {
455
455
  * @primitive pki.schema.crmf.parse
456
456
  * @signature pki.schema.crmf.parse(input) -> certReqMessages
457
457
  * @since 0.1.17
458
- * @status experimental
458
+ * @status stable
459
459
  * @spec RFC 4211
460
460
  * @related pki.schema.parse, pki.schema.csr.parse
461
461
  *
@@ -496,7 +496,7 @@ var parse = pkix.makeParser({
496
496
  * @primitive pki.schema.crmf.pemDecode
497
497
  * @signature pki.schema.crmf.pemDecode(text, label?) -> Buffer
498
498
  * @since 0.1.17
499
- * @status experimental
499
+ * @status stable
500
500
  * @spec RFC 7468, RFC 4211
501
501
  * @related pki.schema.crmf.parse
502
502
  *
@@ -513,7 +513,7 @@ function pemDecode(text, label) { return pkix.pemDecode(text, label || null, Pem
513
513
  * @primitive pki.schema.crmf.pemEncode
514
514
  * @signature pki.schema.crmf.pemEncode(der, label) -> string
515
515
  * @since 0.1.23
516
- * @status experimental
516
+ * @status stable
517
517
  * @spec RFC 7468, RFC 4211
518
518
  * @related pki.schema.crmf.pemDecode
519
519
  *
@@ -320,7 +320,7 @@ var CSR_ATTRS = schema.seqOf(ATTR_OR_OID, {
320
320
  * @primitive pki.schema.csrattrs.parse
321
321
  * @signature pki.schema.csrattrs.parse(der) -> { items }
322
322
  * @since 0.1.24
323
- * @status experimental
323
+ * @status stable
324
324
  * @spec RFC 8951, RFC 9908, RFC 7030
325
325
  * @related pki.schema.parse, pki.est.buildEnrollAttributes
326
326
  *
@@ -46,6 +46,7 @@ var pkix = require("./schema-pkix.js");
46
46
  var cms = require("./schema-cms.js");
47
47
  var pkcs8 = require("./schema-pkcs8.js");
48
48
  var frameworkError = require("./framework-error.js");
49
+ var guard = require("./guard-all.js");
49
50
 
50
51
  var Pkcs12Error = frameworkError.Pkcs12Error;
51
52
  var PemError = frameworkError.PemError;
@@ -102,16 +103,10 @@ var PBKDF2_PARAMS = schema.seq([
102
103
  if (!m.fields.keyLength.present) {
103
104
  throw ctx.E("pkcs12/bad-mac-data", "PBMAC1 PBKDF2-params must carry keyLength (RFC 9579 sec. 5)");
104
105
  }
105
- // Both counters surface as exact JS numbers -- a value past the bound
106
- // would round silently on conversion and hand a verifier wrong inputs.
107
- var iterations = m.fields.iterationCount.value;
108
- if (iterations < 1n || iterations > 2147483647n) {
109
- throw ctx.E("pkcs12/bad-mac-data", "PBKDF2 iterationCount must be a positive integer within the iteration-count range");
110
- }
111
- var keyLength = m.fields.keyLength.value;
112
- if (keyLength < 1n || keyLength > 2147483647n) {
113
- throw ctx.E("pkcs12/bad-mac-data", "PBKDF2 keyLength must be a positive integer within the key-length range");
114
- }
106
+ // guard.range.positiveInt31 bounds + narrows each counter atomically -- a
107
+ // value past the bound would round silently and hand a verifier wrong inputs.
108
+ var iterationCount = guard.range.positiveInt31(m.fields.iterationCount.value, ctx.E, "pkcs12/bad-mac-data", "PBKDF2 iterationCount");
109
+ var keyLength = guard.range.positiveInt31(m.fields.keyLength.value, ctx.E, "pkcs12/bad-mac-data", "PBKDF2 keyLength");
115
110
  var prf = m.fields.prf.present ? m.fields.prf.value.result : null;
116
111
  // X.690 sec. 11.5 -- a DEFAULT-valued component must be omitted from a DER
117
112
  // encoding, and the prf DEFAULT is algid-hmacWithSHA1: hmacWithSHA1 with
@@ -124,8 +119,8 @@ var PBKDF2_PARAMS = schema.seq([
124
119
  }
125
120
  return {
126
121
  salt: m.fields.salt.value,
127
- iterationCount: Number(iterations),
128
- keyLength: Number(keyLength),
122
+ iterationCount: iterationCount,
123
+ keyLength: keyLength,
129
124
  prfOid: prf ? prf.oid : OID_HMAC_SHA1,
130
125
  prfName: prf ? prf.name : "hmacWithSHA1",
131
126
  };
@@ -172,8 +167,7 @@ var MAC_DATA = schema.seq([
172
167
  if (it.present) {
173
168
  var v = it.value;
174
169
  if (v === 1n) throw ctx.E("pkcs12/bad-mac-iterations", "iterations equal to its DEFAULT 1 must be omitted (X.690 sec. 11.5)");
175
- if (v < 1n || v > 2147483647n) throw ctx.E("pkcs12/bad-mac-iterations", "iterations must be a positive integer within the iteration-count range");
176
- iterations = Number(v);
170
+ iterations = guard.range.positiveInt31(v, ctx.E, "pkcs12/bad-mac-iterations", "MacData iterations");
177
171
  }
178
172
  var di = m.fields.mac.value.result;
179
173
  var pbmac1 = null;
@@ -529,7 +523,7 @@ function _buildBag(rec, state, ctx) {
529
523
  * @primitive pki.schema.pkcs12.parse
530
524
  * @signature pki.schema.pkcs12.parse(input) -> pfx
531
525
  * @since 0.1.18
532
- * @status experimental
526
+ * @status stable
533
527
  * @spec RFC 7292, RFC 9579
534
528
  * @defends ASN.1-parser-DoS (CWE-400)
535
529
  * @related pki.schema.parse, pki.schema.pkcs8.parse, pki.schema.cms.parse
@@ -577,7 +571,7 @@ var parse = pkix.makeParser({
577
571
  * @primitive pki.schema.pkcs12.pemDecode
578
572
  * @signature pki.schema.pkcs12.pemDecode(text, label?) -> Buffer
579
573
  * @since 0.1.18
580
- * @status experimental
574
+ * @status stable
581
575
  * @spec RFC 7468, RFC 7292
582
576
  * @related pki.schema.pkcs12.parse
583
577
  *
@@ -594,7 +588,7 @@ function pemDecode(text, label) { return pkix.pemDecode(text, label || "PKCS12",
594
588
  * @primitive pki.schema.pkcs12.pemEncode
595
589
  * @signature pki.schema.pkcs12.pemEncode(der, label?) -> string
596
590
  * @since 0.1.18
597
- * @status experimental
591
+ * @status stable
598
592
  * @spec RFC 7468
599
593
  * @related pki.schema.pkcs12.pemDecode
600
594
  *
@@ -16,6 +16,7 @@
16
16
  var asn1 = require("./asn1-der");
17
17
  var constants = require("./constants");
18
18
  var schema = require("./schema-engine");
19
+ var guard = require("./guard-all");
19
20
  // ct is a leaf format module (requires only the codec / oid / constants / error
20
21
  // core, never pkix) so the SCT-list extension VALUE decoder registers here as a
21
22
  // registry row, not a switch. The one-directional edge keeps the layering acyclic.
@@ -35,16 +36,13 @@ var PEM_RE = /-----BEGIN ([A-Z0-9 ]+)-----([\s\S]*?)-----END \1-----/;
35
36
  // lenient Buffer.from would otherwise let several distinct PEM texts alias one
36
37
  // DER (PEM-layer malleability) and silently drop trailing content bits.
37
38
  function pemDecode(text, label, PemError) {
38
- // The size cap is enforced on the raw byte length BEFORE the latin1 string
39
- // copy: converting first would allocate a full-size string for an input the
40
- // cap is about to reject, and a buffer above Node's max string length would
41
- // escape as an untyped ERR_STRING_TOO_LONG instead of pem/too-large.
42
- if (Buffer.isBuffer(text)) {
43
- if (text.length > constants.LIMITS.PEM_MAX_BYTES) throw new PemError("pem/too-large", "PEM input exceeds size cap");
44
- text = text.toString("latin1");
45
- }
46
- if (typeof text !== "string") throw new PemError("pem/bad-input", "pemDecode expects a string or Buffer");
47
- if (text.length > constants.LIMITS.PEM_MAX_BYTES) throw new PemError("pem/too-large", "PEM input exceeds size cap");
39
+ // guard.text.decode caps the raw byte length BEFORE the latin1 string copy:
40
+ // converting first would allocate a full-size string for input the cap is
41
+ // about to reject, and a buffer above Node's max string length would escape as
42
+ // an untyped ERR_STRING_TOO_LONG instead of pem/too-large.
43
+ text = guard.text.decode(text, constants.LIMITS.PEM_MAX_BYTES, PemError, {
44
+ charset: "latin1", tooLarge: "pem/too-large", badInput: "pem/bad-input", label: "PEM input",
45
+ });
48
46
  var m = PEM_RE.exec(text);
49
47
  if (!m) throw new PemError("pem/no-block", "no PEM block found");
50
48
  if (label && m[1] !== label) throw new PemError("pem/label-mismatch", "expected " + JSON.stringify(label) + " block, got " + JSON.stringify(m[1]));
@@ -64,12 +62,9 @@ function pemDecode(text, label, PemError) {
64
62
  // base64 body is canonical (the pemDecode rule); returns an array of DER buffers.
65
63
  function pemDecodeAll(text, label, PemError) {
66
64
  label = label || "CERTIFICATE";
67
- if (Buffer.isBuffer(text)) {
68
- if (text.length > constants.LIMITS.PEM_MAX_BYTES) throw new PemError("pem/too-large", "PEM input exceeds size cap");
69
- text = text.toString("latin1");
70
- }
71
- if (typeof text !== "string") throw new PemError("pem/bad-input", "pemDecodeAll expects a string or Buffer");
72
- if (text.length > constants.LIMITS.PEM_MAX_BYTES) throw new PemError("pem/too-large", "PEM input exceeds size cap");
65
+ text = guard.text.decode(text, constants.LIMITS.PEM_MAX_BYTES, PemError, {
66
+ charset: "latin1", tooLarge: "pem/too-large", badInput: "pem/bad-input", label: "PEM input",
67
+ });
73
68
  var re = /-----BEGIN ([A-Z0-9 ]+)-----([\s\S]*?)-----END \1-----/g;
74
69
  var blocks = [];
75
70
  var lastEnd = 0, m;
@@ -107,9 +102,12 @@ function pemEncode(der, label, PemError) {
107
102
  // Uint8Array is taken as bytes. opts: { pemLabel, PemError, ErrorClass, prefix }.
108
103
  function coerceToDer(input, opts) {
109
104
  if (typeof input === "string") return pemDecode(input, opts.pemLabel, opts.PemError);
110
- if (input instanceof Uint8Array && !Buffer.isBuffer(input)) input = Buffer.from(input);
111
- if (Buffer.isBuffer(input)) {
112
- return _isPemArmor(input) ? pemDecode(input, opts.pemLabel, opts.PemError) : input;
105
+ if (Buffer.isBuffer(input) || input instanceof Uint8Array) {
106
+ // Route the DER Buffer / Uint8Array through the shared byte guard so a
107
+ // detached backing store fails closed here for EVERY format that composes
108
+ // this boundary (x509/crl/csr/pkcs8/cms/pkcs12), not per-format.
109
+ var buf = guard.bytes.view(input, opts.ErrorClass, opts.prefix + "/bad-input", "parse");
110
+ return _isPemArmor(buf) ? pemDecode(buf, opts.pemLabel, opts.PemError) : buf;
113
111
  }
114
112
  throw new opts.ErrorClass(opts.prefix + "/bad-input", "parse expects a DER Buffer or a PEM string");
115
113
  }
@@ -502,12 +500,9 @@ function generalName(ns, opts) {
502
500
  if (constructed) throw ctx.E(code, "GeneralName [" + t + "] must be primitive (X.690 sec. 10.2)");
503
501
  if (GN_IA5[t]) {
504
502
  if (n.content.length === 0) throw ctx.E(code, "GeneralName [" + t + "] must be a non-empty IA5String");
505
- for (var i = 0; i < n.content.length; i++) {
506
- // 7-bit IA5, and no C0/DEL control byte -- an embedded NUL/control in a
507
- // dNSName/rfc822Name/URI enables a name-truncation/confusion bypass
508
- // (CVE-2009-2408 class) downstream, so reject it at decode.
509
- if (n.content[i] < 0x20 || n.content[i] > 0x7e) throw ctx.E(code, "GeneralName [" + t + "] must be a printable IA5String (no control bytes)");
510
- }
503
+ // 7-bit IA5, no C0/DEL control byte -- an embedded NUL/control in a
504
+ // dNSName/rfc822Name/URI enables a name-truncation bypass (CVE-2009-2408).
505
+ guard.name.assertPrintableIa5(n.content, ctx.E, code, "GeneralName [" + t + "]");
511
506
  if (decodeValue) value = n.content.toString("latin1");
512
507
  } else if (t === 7) {
513
508
  if (subtreeBase) {
@@ -621,11 +616,10 @@ function certExtensionDecoders(ns) {
621
616
  if (kids[i] && kids[i].tagClass === "universal" && kids[i].tagNumber === _T.INTEGER) {
622
617
  if (!cA) throw ns.E(C, "BasicConstraints pathLenConstraint is only permitted when cA is TRUE (RFC 5280 sec. 4.2.1.9)");
623
618
  var pl = readInt(kids[i], C, "pathLenConstraint");
624
- // Bounded before Number() narrowing -- a value past the safe-integer range
625
- // would round silently and be compared as a path-length ceiling, so it is
626
- // exact-or-rejected (the same rule the RSASSA-PSS/PKCS#12 counters follow).
627
- if (pl < 0n || pl > 2147483647n) throw ns.E(C, "BasicConstraints pathLenConstraint must be a non-negative integer within range");
628
- pathLen = Number(pl); i++;
619
+ // guard.range.uint31 bounds the decoded BigInt and narrows atomically -- a
620
+ // value past the safe-integer range would otherwise round silently and be
621
+ // compared as a path-length ceiling.
622
+ pathLen = guard.range.uint31(pl, ns.E, C, "BasicConstraints pathLenConstraint (RFC 5280 sec. 4.2.1.9)"); i++;
629
623
  }
630
624
  if (i !== kids.length) throw ns.E(C, "BasicConstraints has unexpected trailing fields");
631
625
  return { cA: cA, pathLenConstraint: pathLen };
@@ -748,11 +742,11 @@ function certExtensionDecoders(ns) {
748
742
  pcLastTag = k.tagNumber;
749
743
  var v;
750
744
  try { v = asn1.read.integerImplicit(k, k.tagNumber); } catch (e) { throw ns.E(C, "PolicyConstraints field must be an INTEGER", e); }
751
- // Bounded before Number() narrowing (a skip count past the safe-integer
752
- // range would round silently) -- exact-or-rejected.
753
- if (v < 0n || v > 2147483647n) throw ns.E(C, "PolicyConstraints skip count must be a non-negative integer within range");
754
- if (k.tagNumber === 0) rep = Number(v);
755
- else if (k.tagNumber === 1) ipm = Number(v);
745
+ // guard.range.uint31 bounds + narrows atomically (a skip count past the
746
+ // safe-integer range would round silently).
747
+ var skip = guard.range.uint31(v, ns.E, C, "PolicyConstraints skip count (RFC 5280 sec. 4.2.1.11)");
748
+ if (k.tagNumber === 0) rep = skip;
749
+ else if (k.tagNumber === 1) ipm = skip;
756
750
  else throw ns.E(C, "PolicyConstraints has an unexpected field [" + k.tagNumber + "]");
757
751
  });
758
752
  return { requireExplicitPolicy: rep, inhibitPolicyMapping: ipm };
@@ -764,10 +758,9 @@ function certExtensionDecoders(ns) {
764
758
  var n = decodeTop(buf, C, "InhibitAnyPolicy");
765
759
  if (n.tagClass !== "universal" || n.tagNumber !== _T.INTEGER) throw ns.E(C, "InhibitAnyPolicy must be an INTEGER (RFC 5280 sec. 4.2.1.14)");
766
760
  var v = readInt(n, C, "InhibitAnyPolicy");
767
- // Bounded before Number() narrowing (a skip count past the safe-integer
768
- // range would round silently) -- exact-or-rejected.
769
- if (v < 0n || v > 2147483647n) throw ns.E(C, "InhibitAnyPolicy skip count must be a non-negative integer within range");
770
- return Number(v);
761
+ // guard.range.uint31 bounds + narrows atomically (a skip count past the
762
+ // safe-integer range would round silently).
763
+ return guard.range.uint31(v, ns.E, C, "InhibitAnyPolicy skip count (RFC 5280 sec. 4.2.1.14)");
771
764
  }
772
765
 
773
766
  // subjectAltName / issuerAltName ::= GeneralNames -- decoded values surfaced.
package/lib/webcrypto.js CHANGED
@@ -35,6 +35,7 @@
35
35
 
36
36
  var nodeCrypto = require("node:crypto");
37
37
  var frameworkError = require("./framework-error");
38
+ var guard = require("./guard-all");
38
39
 
39
40
  // Single-owner error class -- co-located with its module (framework-error
40
41
  // stays the cross-module home; this is webcrypto-private). withCause: a
@@ -47,21 +48,7 @@ var MAX_RANDOM_BYTES = 65536;
47
48
  // ---- value helpers ---------------------------------------------------
48
49
 
49
50
  function _toBuf(data, who) {
50
- var isAb = data instanceof ArrayBuffer;
51
- // A Buffer is itself an ArrayBuffer view, so it is handled by the view arm --
52
- // NOT a `Buffer.isBuffer` fast-path that would return it as-is. A detached
53
- // backing ArrayBuffer (a transferred / structuredClone'd Buffer, view, or
54
- // ArrayBuffer) has had its bytes removed and reads as zero-length; the
55
- // fast-path would silently hash / sign EMPTY (a fail-OPEN). Always re-view
56
- // through Buffer.from so a detached input fails closed here as a typed error.
57
- if (isAb || ArrayBuffer.isView(data)) {
58
- try {
59
- return isAb ? Buffer.from(data) : Buffer.from(data.buffer, data.byteOffset, data.byteLength);
60
- } catch (e) {
61
- throw new WebCryptoError("webcrypto/data", (who || "input") + ": input is not a usable byte source (detached backing buffer?)", e);
62
- }
63
- }
64
- throw new WebCryptoError("webcrypto/data", (who || "input") + ": expected BufferSource (ArrayBuffer / TypedArray / Buffer)");
51
+ return guard.bytes.source(data, WebCryptoError, "webcrypto/data", who || "input");
65
52
  }
66
53
 
67
54
  function _toArrayBuffer(buf) {
@@ -354,7 +341,7 @@ SubtleCrypto.prototype.verify = async function verify(algorithm, key, signature,
354
341
  // in constant time. The length check leaks nothing a constant-time compare
355
342
  // would protect; the secret-dependent byte comparison is timingSafeEqual.
356
343
  var digest = hm.digest();
357
- return digest.length === sig.length && nodeCrypto.timingSafeEqual(digest, sig);
344
+ return guard.crypto.constantTimeEqual(digest, sig);
358
345
  }
359
346
  throw new WebCryptoError("webcrypto/not-supported", "verify: unsupported algorithm " + JSON.stringify(name));
360
347
  };
@@ -729,15 +716,11 @@ Crypto.prototype.getRandomValues = function getRandomValues(typedArray) {
729
716
  if (typedArray.byteLength > MAX_RANDOM_BYTES) {
730
717
  throw new WebCryptoError("webcrypto/data", "getRandomValues: byteLength exceeds " + MAX_RANDOM_BYTES);
731
718
  }
732
- var view;
733
- try {
734
- // A detached backing ArrayBuffer surfaces as a typed error rather than a
735
- // raw TypeError from Buffer.from over the transferred/cloned view.
736
- view = Buffer.from(typedArray.buffer, typedArray.byteOffset, typedArray.byteLength);
737
- } catch (e) {
738
- throw new WebCryptoError("webcrypto/data", "getRandomValues: byte view is not usable (detached backing buffer?)", e);
739
- }
740
- nodeCrypto.randomFillSync(view);
719
+ // Re-view the (already integer-TypedArray-validated) output buffer via
720
+ // guard.bytes.source -- any TypedArray, not just Uint8Array -- so a detached
721
+ // backing store fails closed here; the returned Buffer aliases typedArray, so
722
+ // randomFillSync writes through to the caller's array.
723
+ nodeCrypto.randomFillSync(guard.bytes.source(typedArray, WebCryptoError, "webcrypto/data", "getRandomValues"));
741
724
  return typedArray;
742
725
  };
743
726
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blamejs/pki",
3
- "version": "0.1.29",
3
+ "version": "0.1.31",
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:26032baa-c793-470f-863c-569dc97c186e",
5
+ "serialNumber": "urn:uuid:2ed2aa23-5194-4472-96f3-c4013f134834",
6
6
  "version": 1,
7
7
  "metadata": {
8
- "timestamp": "2026-07-11T05:58:59.165Z",
8
+ "timestamp": "2026-07-11T16:09:44.713Z",
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.29",
22
+ "bom-ref": "@blamejs/pki@0.1.31",
23
23
  "type": "application",
24
24
  "name": "pki",
25
- "version": "0.1.29",
25
+ "version": "0.1.31",
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",
29
+ "purl": "pkg:npm/%40blamejs/pki@0.1.31",
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.29",
57
+ "ref": "@blamejs/pki@0.1.31",
58
58
  "dependsOn": []
59
59
  }
60
60
  ]