@blamejs/pki 0.5.30 → 0.5.32

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.
Files changed (53) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.md +2 -2
  3. package/lib/acme.js +347 -90
  4. package/lib/asn1-der.js +97 -33
  5. package/lib/attrcert-sign.js +1 -1
  6. package/lib/cmc-build.js +19 -8
  7. package/lib/cmc-verify.js +4 -4
  8. package/lib/cmp-build.js +36 -6
  9. package/lib/cmp-session.js +5 -5
  10. package/lib/cmp-verify.js +86 -16
  11. package/lib/cms-compress.js +2 -2
  12. package/lib/cms-decrypt.js +3 -3
  13. package/lib/cms-encrypt.js +1 -1
  14. package/lib/cms-sign.js +7 -7
  15. package/lib/cms-verify.js +9 -4
  16. package/lib/crl-sign.js +5 -5
  17. package/lib/crmf-sign.js +2 -1
  18. package/lib/csr-sign.js +1 -4
  19. package/lib/ct.js +8 -4
  20. package/lib/est.js +234 -34
  21. package/lib/framework-error.js +20 -6
  22. package/lib/guard-bytes.js +24 -3
  23. package/lib/http-digest.js +69 -16
  24. package/lib/http-retry-after.js +68 -24
  25. package/lib/http-transport.js +60 -6
  26. package/lib/inspect.js +13 -2
  27. package/lib/ip-utils.js +102 -25
  28. package/lib/key.js +2 -2
  29. package/lib/lint.js +1 -1
  30. package/lib/mime.js +27 -4
  31. package/lib/ocsp.js +7 -7
  32. package/lib/oid.js +16 -0
  33. package/lib/path-validate.js +76 -18
  34. package/lib/pbes2.js +2 -2
  35. package/lib/pkcs12-build.js +2 -2
  36. package/lib/pki-build.js +320 -21
  37. package/lib/rfc3339.js +75 -17
  38. package/lib/schema-c509.js +36 -9
  39. package/lib/schema-cmc.js +3 -3
  40. package/lib/schema-pkix.js +174 -25
  41. package/lib/schema-tsp.js +10 -1
  42. package/lib/sign-scheme.js +27 -7
  43. package/lib/sigstore.js +52 -9
  44. package/lib/smime.js +86 -6
  45. package/lib/trust.js +114 -14
  46. package/lib/tsp-sign.js +39 -27
  47. package/lib/validator-tpm.js +2 -2
  48. package/lib/webauthn-mds.js +24 -6
  49. package/lib/webauthn.js +3 -3
  50. package/lib/webcrypto.js +1 -1
  51. package/lib/x509-sign.js +9 -5
  52. package/package.json +1 -1
  53. package/sbom.cdx.json +6 -6
package/lib/mime.js CHANGED
@@ -36,15 +36,38 @@ function _splitPoint(bytes) {
36
36
  return null;
37
37
  }
38
38
 
39
+ // Line-ending + whitespace helpers, decided character by character (no regex): _toLf collapses any
40
+ // CRLF / bare CR / bare LF to a single LF; _toCrlf then expands every LF to CRLF; _stripLeadingHtab
41
+ // drops a leading run of SP/HTAB. They match the /\r\n/g, /\r/g, /\n/g and /^[ \t]+/ replacements.
42
+ function _toLf(s) {
43
+ var out = "";
44
+ for (var i = 0; i < s.length; i++) {
45
+ var c = s.charAt(i);
46
+ if (c === "\r") { if (s.charAt(i + 1) === "\n") continue; out += "\n"; } // CRLF -> its LF next; bare CR -> LF
47
+ else out += c;
48
+ }
49
+ return out;
50
+ }
51
+ function _toCrlf(s) {
52
+ var lf = _toLf(s), out = "";
53
+ for (var i = 0; i < lf.length; i++) { var c = lf.charAt(i); out += (c === "\n") ? "\r\n" : c; }
54
+ return out;
55
+ }
56
+ function _stripLeadingHtab(s) {
57
+ var i = 0;
58
+ while (i < s.length && (s.charAt(i) === " " || s.charAt(i) === "\t")) i += 1;
59
+ return s.slice(i);
60
+ }
61
+
39
62
  // Split a header block into unfolded logical lines (RFC 5322 folding: a continuation line begins with
40
63
  // SP/HTAB and joins the previous). Returns the raw header strings (each "Name: value").
41
64
  function _unfoldHeaders(headerText) {
42
- var lines = headerText.replace(/\r\n/g, "\n").replace(/\r/g, "\n").split("\n");
65
+ var lines = _toLf(headerText).split("\n");
43
66
  var out = [];
44
67
  for (var i = 0; i < lines.length; i++) {
45
68
  var ln = lines[i];
46
69
  if (ln === "") continue;
47
- if ((ln[0] === " " || ln[0] === "\t") && out.length) out[out.length - 1] += " " + ln.replace(/^[ \t]+/, "");
70
+ if ((ln[0] === " " || ln[0] === "\t") && out.length) out[out.length - 1] += " " + _stripLeadingHtab(ln);
48
71
  else out.push(ln);
49
72
  }
50
73
  return out;
@@ -130,7 +153,7 @@ function parse(input, E, code) {
130
153
  // with only the single conventional ": " delimiter space removed. Leading/trailing whitespace inside
131
154
  // the body is preserved, so a consumer that must reproduce the exact authenticated field body (S/MIME
132
155
  // header protection) is not handed a lossily-trimmed value.
133
- headers.push({ name: nm, lname: nm.toLowerCase(), value: body.trim(), rawValue: body.replace(/^ /, "") });
156
+ headers.push({ name: nm, lname: nm.toLowerCase(), value: body.trim(), rawValue: body.charAt(0) === " " ? body.slice(1) : body });
134
157
  }
135
158
  function header(name) { var l = name.toLowerCase(); for (var j = 0; j < headers.length; j++) if (headers[j].lname === l) return headers[j].value; return null; }
136
159
  var ctv = header("content-type");
@@ -146,7 +169,7 @@ function parse(input, E, code) {
146
169
  // Normalize a text body's line endings to the RFC 8551 sec. 3.1.1 canonical CRLF pair (any bare CR or
147
170
  // LF becomes CRLF; an existing CRLF is preserved).
148
171
  function canonicalizeText(bodyBytes) {
149
- var s = bodyBytes.toString("latin1").replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\n/g, "\r\n");
172
+ var s = _toCrlf(bodyBytes.toString("latin1"));
150
173
  return Buffer.from(s, "latin1");
151
174
  }
152
175
 
package/lib/ocsp.js CHANGED
@@ -84,7 +84,7 @@ function _responseFromBytes(response) {
84
84
  }
85
85
 
86
86
  function _toDer(input, what) {
87
- if (Buffer.isBuffer(input) || input instanceof Uint8Array) return guard.bytes.snapshot(input, OcspError, "ocsp/bad-input", what || "input");
87
+ if (guard.bytes.isByteSource(input)) return guard.bytes.snapshotSource(input, OcspError, "ocsp/bad-input", what || "input");
88
88
  if (typeof input === "string") { try { return ocspSchema.pemDecode(input); } catch (e) { throw _err("ocsp/bad-input", (what || "input") + " PEM could not be decoded", e); } }
89
89
  throw _err("ocsp/bad-input", (what || "input") + " must be a DER Buffer, Uint8Array, or PEM string");
90
90
  }
@@ -221,16 +221,16 @@ function _buildRequest(query, opts) {
221
221
  }
222
222
  function _emitReq(der, opts) { return opts.pem ? ocspSchema.pemEncode(der, "OCSP REQUEST") : der; }
223
223
  function _nameDer(name) {
224
- if (Buffer.isBuffer(name)) return guard.bytes.snapshot(name, OcspError, "ocsp/bad-input", "requestorName");
224
+ if (guard.bytes.isByteSource(name)) return guard.bytes.snapshotSource(name, OcspError, "ocsp/bad-input", "requestorName");
225
225
  if (name && name.bytes) return name.bytes; // a parsed Name
226
- throw _err("ocsp/bad-input", "requestorName must be a DER Name Buffer or a parsed Name");
226
+ throw _err("ocsp/bad-input", "requestorName must be a DER Name BufferSource or a parsed Name");
227
227
  }
228
228
  // The RAW DER of a certificate that will be embedded verbatim (the responder cert in a
229
229
  // BasicOCSPResponse, the signer cert in a signed OCSPRequest). It must be supplied as bytes:
230
230
  // a parsed certificate does not retain its full DER encoding, and re-encoding it would risk
231
231
  // byte drift from the signed original, so a parsed cert is rejected rather than reconstructed.
232
232
  function _normCertDer(cert, what) {
233
- if (Buffer.isBuffer(cert) || cert instanceof Uint8Array) return guard.bytes.snapshot(cert, OcspError, "ocsp/bad-input", what || "a certificate");
233
+ if (guard.bytes.isByteSource(cert)) return guard.bytes.snapshotSource(cert, OcspError, "ocsp/bad-input", what || "a certificate");
234
234
  if (typeof cert === "string") { try { return x509.pemDecode(cert); } catch (e) { throw _err("ocsp/bad-input", (what || "a certificate") + " PEM could not be decoded", e); } }
235
235
  if (cert && cert.tbsBytes && cert.subjectPublicKeyInfo) {
236
236
  throw _err("ocsp/bad-input", (what || "a certificate") + " to embed must be supplied as DER bytes or a PEM string, not a parsed certificate (the parser does not retain the full DER encoding needed to embed it verbatim)");
@@ -378,7 +378,7 @@ function _asDate(d) {
378
378
  // the aliasing window is wiped rather than left to the garbage collector. Only our own
379
379
  // allocations are listed: the caller's key is never written to.
380
380
  function _snapshotSignerKey(key, owned) {
381
- if (Buffer.isBuffer(key) || key instanceof Uint8Array) {
381
+ if (Buffer.isBuffer(key) || key instanceof Uint8Array) { // allow:byte-source-narrow -- responder private key on the key-ownership contract (Buffer / Uint8Array / PEM / CryptoKey); widening keys is a separate root decision
382
382
  var copy = guard.bytes.snapshot(key, OcspError, "ocsp/bad-input", "the responder key");
383
383
  owned.push(copy);
384
384
  return copy;
@@ -550,8 +550,8 @@ function verify(response, opts) {
550
550
  // A client that sent a nonce binds it (RFC 9654 / RFC 5019 sec. 4): a missing or mismatched
551
551
  // response nonce fails the verdict closed, even if the status/signature were otherwise good.
552
552
  var respNonce = _responseNonce(parsed);
553
- var reqNonce = (Buffer.isBuffer(opts.requestNonce) || opts.requestNonce instanceof Uint8Array)
554
- ? guard.bytes.snapshot(opts.requestNonce, OcspError, "ocsp/bad-input", "opts.requestNonce") : null;
553
+ var reqNonce = guard.bytes.isByteSource(opts.requestNonce)
554
+ ? guard.bytes.snapshotSource(opts.requestNonce, OcspError, "ocsp/bad-input", "opts.requestNonce") : null;
555
555
  var matched = respNonce != null && reqNonce != null && guard.crypto.constantTimeEqual(respNonce, reqNonce);
556
556
  var out = Object.assign({}, verdict, { nonceMatched: matched });
557
557
  // The downgrade applies to `good` ONLY. `unknown` is the closed direction for a
package/lib/oid.js CHANGED
@@ -652,6 +652,21 @@ function byName(n) {
652
652
  return _byName.get(n);
653
653
  }
654
654
 
655
+ // Does `s` look like a dotted-decimal OID -- one or more digit groups joined by single dots (at least two
656
+ // groups, no leading/trailing/doubled dot)? Distinguishes a numeric OID from a symbolic name so a caller may
657
+ // pass either. Decided by character scan (no regex); the arc-value bounds are the encoder's authority.
658
+ function isDottedDecimal(s) {
659
+ if (typeof s !== "string" || s.length === 0) return false;
660
+ var i, c, sawDot = false, atomEmpty = true;
661
+ for (i = 0; i < s.length; i += 1) {
662
+ c = s.charCodeAt(i);
663
+ if (c === 46) { if (atomEmpty) return false; sawDot = true; atomEmpty = true; } // "."
664
+ else if (c >= 48 && c <= 57) { atomEmpty = false; } // 0-9
665
+ else return false;
666
+ }
667
+ return sawDot && !atomEmpty;
668
+ }
669
+
655
670
  function has(dotted) {
656
671
  _assertDotted(dotted, "has");
657
672
  return _byOid.has(dotted);
@@ -876,6 +891,7 @@ function kemParams(oidOrName) { return KEM_PARAMS[oidOrName]; }
876
891
  module.exports = {
877
892
  name: name,
878
893
  byName: byName,
894
+ isDottedDecimal: isDottedDecimal,
879
895
  has: has,
880
896
  paramsMustBeAbsent: paramsMustBeAbsent,
881
897
  kemParams: kemParams,
@@ -49,6 +49,7 @@ var cmsVerify = require("./cms-verify");
49
49
  var cmpSession = require("./cmp-session");
50
50
  var guard = require("./guard-all");
51
51
  var intrinsic = require("./guard-intrinsic");
52
+ var _charCodeAt = intrinsic.uncurry(String.prototype.charCodeAt);
52
53
  var constants = require("./constants");
53
54
  var validator = require("./validator-all");
54
55
  var cms = require("./schema-cms");
@@ -568,11 +569,54 @@ function hostConstraintMatch(constraint, host) {
568
569
  // or a value carrying non-hostname characters (a scheme "://", a path "/", a
569
570
  // port ":") is not a FQDN and cannot be matched against a domain-suffix
570
571
  // constraint. Only [A-Za-z0-9.-] with at least one dot qualifies.
572
+ // A code-point membership table built from a string of the allowed characters, and the shared scan that
573
+ // checks every character of a non-empty string against one -- replacing the hostname-charset regexes
574
+ // through one loop (no per-predicate character-class chain).
575
+ function _charTable(chars) { var t = []; for (var i = 0; i < chars.length; i++) t[_charCodeAt(chars, i)] = true; return t; }
576
+ var _FQDN_TABLE = _charTable("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-"); // /^[a-z0-9.-]+$/i
577
+ var _DIGDOT_TABLE = _charTable("0123456789."); // /^[0-9.]+$/
578
+ function _allCharsIn(s, table) {
579
+ if (s.length === 0) return false;
580
+ for (var i = 0; i < s.length; i++) if (!table[_charCodeAt(s, i)]) return false;
581
+ return true;
582
+ }
583
+ // /^[0-9]/ : begins with an ASCII digit (a dotted-OID attempt, vs an EKU / purpose name).
584
+ function _startsWithDigit(s) { if (s.length === 0) return false; var c = _charCodeAt(s, 0); return c >= 48 && c <= 57; }
585
+
586
+ var _SCHEME_TABLE = _charTable("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+.-"); // [a-z0-9+.-]
587
+ // /^[a-z][a-z0-9+.-]*:\/\/([^/?#]*)/i : the [start, end) range of the authority (up to the first / ? #
588
+ // or end), or null when the input is not a scheme "//" URI. Returns a range so the caller slices once
589
+ // (the slice that replaces the removed .exec), not a fresh substring here.
590
+ function _uriAuthorityRange(uri) {
591
+ var n = uri.length;
592
+ if (n === 0) return null;
593
+ var c0 = _charCodeAt(uri, 0);
594
+ if (!((c0 >= 65 && c0 <= 90) || (c0 >= 97 && c0 <= 122))) return null; // scheme starts with a letter
595
+ var p = 1;
596
+ while (p < n && _SCHEME_TABLE[_charCodeAt(uri, p)]) p += 1;
597
+ if (_charCodeAt(uri, p) !== 58 || _charCodeAt(uri, p + 1) !== 47 || _charCodeAt(uri, p + 2) !== 47) return null; // "://"
598
+ p += 3;
599
+ var authStart = p;
600
+ while (p < n) { var d = _charCodeAt(uri, p); if (d === 47 || d === 63 || d === 35) break; p += 1; } // [^/?#]*
601
+ return [authStart, p];
602
+ }
603
+ // /:\d+$/ : the index at which a trailing ":" + digits port starts (to slice off), or s.length when absent.
604
+ function _portStripIndex(s) {
605
+ var i = s.length;
606
+ while (i > 0) { var c = _charCodeAt(s, i - 1); if (c >= 48 && c <= 57) i -= 1; else break; }
607
+ return (i < s.length && i > 0 && _charCodeAt(s, i - 1) === 58) ? i - 1 : s.length;
608
+ }
609
+ // /^\[(.*)\]$/ -> "$1" : an IPv6 literal's surrounding "[" "]" removed, else the string unchanged.
610
+ function _stripBrackets(s) {
611
+ var n = s.length;
612
+ return (n >= 2 && _charCodeAt(s, 0) === 91 && _charCodeAt(s, n - 1) === 93) ? s.slice(1, n - 1) : s;
613
+ }
614
+
571
615
  function isFqdnHost(host) {
572
616
  var h = stripTrailingDot(host);
573
617
  if (h === "" || h.indexOf(".") === -1) return false; // empty or single-label (localhost)
574
- if (!/^[a-z0-9.-]+$/i.test(h)) return false; // scheme/path/port/IPv6 chars, "@", etc.
575
- if (/^[0-9.]+$/.test(h)) return false; // IPv4 dotted-quad literal
618
+ if (!_allCharsIn(h, _FQDN_TABLE)) return false; // scheme/path/port/IPv6 chars, "@", etc.
619
+ if (_allCharsIn(h, _DIGDOT_TABLE)) return false; // IPv4 dotted-quad literal
576
620
  return true;
577
621
  }
578
622
 
@@ -595,9 +639,9 @@ function uriMatch(constraint, uri) {
595
639
  }
596
640
 
597
641
  function uriHost(uri) {
598
- var m = /^[a-z][a-z0-9+.-]*:\/\/([^/?#]*)/i.exec(uri);
599
- if (!m) return null;
600
- var authority = m[1];
642
+ var r = _uriAuthorityRange(uri);
643
+ if (r === null) return null;
644
+ var authority = uri.slice(r[0], r[1]);
601
645
  // authority = [ userinfo "@" ] host [ ":" port ]. RFC 3986 userinfo does not
602
646
  // contain a raw "@", so a conformant authority has at most one "@". With more
603
647
  // than one the host cannot be determined reliably, so this fails closed
@@ -606,7 +650,7 @@ function uriHost(uri) {
606
650
  var firstAt = authority.indexOf("@");
607
651
  if (firstAt !== authority.lastIndexOf("@")) return null;
608
652
  if (firstAt !== -1) authority = authority.slice(firstAt + 1);
609
- var host = authority.replace(/:\d+$/, "");
653
+ var host = authority.slice(0, _portStripIndex(authority));
610
654
  return host === "" ? null : host; // an empty authority cannot be evaluated -> unsupported
611
655
  }
612
656
 
@@ -788,8 +832,7 @@ function treeIsEmpty(tree) { return tree.children.length === 0; }
788
832
  function isSubtreeBaseValid(tag, base) {
789
833
  switch (tag) {
790
834
  case 1: case 2: case 6: return typeof base === "string"; // rfc822Name / dNSName / URI
791
- case 7: return (Buffer.isBuffer(base) || base instanceof Uint8Array) &&
792
- (base.length === 8 || base.length === 32); // iPAddress: address + mask
835
+ case 7: return Buffer.isBuffer(base) && (base.length === 8 || base.length === 32); // iPAddress: address + mask (any BufferSource is normalized to a Buffer in checkedSubtreeSeeds)
793
836
  case 4: return base !== null && typeof base === "object" && !!base.rdns && intrinsic.isArray(base.rdns); // directoryName
794
837
  default: return base !== undefined;
795
838
  }
@@ -803,10 +846,19 @@ function checkedSubtreeSeeds(list, optName) {
803
846
  if (list === undefined || list === null) return [];
804
847
  if (!intrinsic.isArray(list)) throw E("path/bad-input", "validate: opts." + optName + " must be an array of { tag, base } subtree entries");
805
848
  return list.map(function (st) {
806
- if (!st || typeof st !== "object" || !Number.isInteger(st.tag) || st.tag < 0 || st.tag > 8 || !isSubtreeBaseValid(st.tag, st.base)) {
849
+ if (!st || typeof st !== "object" || !Number.isInteger(st.tag) || st.tag < 0 || st.tag > 8) {
850
+ throw E("path/bad-input", "validate: opts." + optName + " entries must be { tag: <GeneralName tag number 0..8>, base: <that form's constraint value> }");
851
+ }
852
+ // An iPAddress base (address+mask) may arrive as any BufferSource; normalize it to a
853
+ // private Buffer so the matcher can index it by byte and a caller cannot mutate it once a
854
+ // microtask has passed. The name forms (string / directoryName) are validated as they stand.
855
+ var base = (st.tag === 7 && guard.bytes.isByteSource(st.base))
856
+ ? guard.bytes.snapshotSource(st.base, PathError, "path/bad-input", "an iPAddress subtree base")
857
+ : st.base;
858
+ if (!isSubtreeBaseValid(st.tag, base)) {
807
859
  throw E("path/bad-input", "validate: opts." + optName + " entries must be { tag: <GeneralName tag number 0..8>, base: <that form's constraint value> }");
808
860
  }
809
- return { tag: st.tag, base: st.base };
861
+ return { tag: st.tag, base: base };
810
862
  });
811
863
  }
812
864
 
@@ -2522,8 +2574,8 @@ function verifyOcspResponse(response, cert, issuerCert, time, opts) {
2522
2574
  var _OCSP_CLAIM = ["responseStatus", "basicResponse", "tbsResponseDataBytes"];
2523
2575
  function _ocspFromBytes(response) {
2524
2576
  return guard.parsed.fromTrustedSource(response, "ocspResponse", _OCSP_CLAIM, function (bytes) {
2525
- if (!Buffer.isBuffer(bytes) && !(bytes instanceof Uint8Array) && typeof bytes !== "string") {
2526
- throw E("path/bad-input", "verifyOcspResponse: the response must be an OCSP response DER Buffer, a PEM string, or a pki.schema.ocsp.parseResponse result");
2577
+ if (!guard.bytes.isByteSource(bytes) && typeof bytes !== "string") {
2578
+ throw E("path/bad-input", "verifyOcspResponse: the response must be an OCSP response DER BufferSource, a PEM string, or a pki.schema.ocsp.parseResponse result");
2527
2579
  }
2528
2580
  return ocsp.parseResponse(bytes);
2529
2581
  }, E, "path/bad-input",
@@ -2674,7 +2726,7 @@ function resolvePurposeOpts(opts) {
2674
2726
  // A dotted-form attempt (leads with a digit) must be a canonical OID -- a
2675
2727
  // loose regex accepted a leading-zero / out-of-bounds key that would never
2676
2728
  // match the canonical EKU the target advertises; anything else is a name.
2677
- if (/^[0-9]/.test(p)) return guard.identifier.assertCanonicalOid(p, E, "path/bad-input", "validate: opts.requiredEku entry " + JSON.stringify(p));
2729
+ if (_startsWithDigit(p)) return guard.identifier.assertCanonicalOid(p, E, "path/bad-input", "validate: opts.requiredEku entry " + JSON.stringify(p));
2678
2730
  var dotted = oid.byName(p);
2679
2731
  if (typeof dotted !== "string") throw E("path/bad-input", "validate: opts.requiredEku entry " + JSON.stringify(p) + " is not a registered OID name");
2680
2732
  return dotted;
@@ -2685,7 +2737,7 @@ function resolvePurposeOpts(opts) {
2685
2737
  if (typeof opts.checkPurpose !== "string" || opts.checkPurpose.length === 0) {
2686
2738
  throw E("path/bad-input", "validate: opts.checkPurpose must be a key-purpose OID name or dotted OID string");
2687
2739
  }
2688
- if (/^[0-9]/.test(opts.checkPurpose)) {
2740
+ if (_startsWithDigit(opts.checkPurpose)) {
2689
2741
  var cpDotted = guard.identifier.assertCanonicalOid(opts.checkPurpose, E, "path/bad-input", "validate: opts.checkPurpose");
2690
2742
  checkPurpose = oid.name(cpDotted) || cpDotted; // normalize a dotted purpose OID to its name for the anchor map
2691
2743
  } else {
@@ -2840,7 +2892,7 @@ function toAnchor(entry, verb) {
2840
2892
  // entry refusal, not a raw exception leaked past the typed guards. `alg` and the raw publicKey are PINNED
2841
2893
  // to ONE read here and used throughout, so a stateful accessor that returns a value now and throws (or
2842
2894
  // returns a different value) on a later read cannot slip past this guard at the shape check or the snapshot.
2843
- var isTuple = false, alg = null, pubRaw = null, nameRaw = null, pk = null, purposesSnap, distrustSnap;
2895
+ var isTuple = false, alg = null, pubRaw, nameRaw = null, pk = null, purposesSnap, distrustSnap;
2844
2896
  var subjectDerRaw, labelRaw, mozRaw;
2845
2897
  // A Proxy anchor is refused outright. Every field read below -- whether by property access or from an own
2846
2898
  // data descriptor -- is answered by the anchor's own reflection, and a Proxy's traps let the same read
@@ -2915,7 +2967,10 @@ function toAnchor(entry, verb) {
2915
2967
  // it is captured. A non-tuple leaves pk null / isTuple false and falls through to coerceCert below.
2916
2968
  pubRaw = entry.publicKey;
2917
2969
  if (guard.bytes.isByteSource(pubRaw)) {
2918
- pk = guard.bytes.snapshot(pubRaw, PathError, "path/bad-input", who + ": trustAnchor publicKey");
2970
+ // snapshotSource, not snapshot: isByteSource accepts all four BufferSource forms (Buffer, any view,
2971
+ // DataView, ArrayBuffer), so the normalizer must too -- snapshot accepts only Buffer/Uint8Array and
2972
+ // would accept-then-throw on an ArrayBuffer / DataView anchor publicKey.
2973
+ pk = guard.bytes.snapshotSource(pubRaw, PathError, "path/bad-input", who + ": trustAnchor publicKey");
2919
2974
  }
2920
2975
  nameRaw = entry.name;
2921
2976
  alg = entry.algorithm;
@@ -3005,7 +3060,10 @@ function toAnchor(entry, verb) {
3005
3060
  // resolve a polluted Object.prototype.rdns on a name that supplied none (and could invoke an inherited
3006
3061
  // throwing getter) -- means a name with no own rdns is refused, never accepted on an inherited array.
3007
3062
  var nameHasRdns = intrinsic.hasOwn(nameOut, "rdns") && intrinsic.isArray(nameOut.rdns);
3008
- if (!nameHasRdns || !Buffer.isBuffer(pubRaw) || algStr === null) {
3063
+ // Gate on the NORMALIZED key `pk` (a private Buffer from snapshotSource iff publicKey was any of the four
3064
+ // BufferSource forms), not the raw `pubRaw` -- Buffer.isBuffer(pubRaw) would reject a Uint8Array / view /
3065
+ // ArrayBuffer publicKey that isByteSource + snapshotSource just accepted.
3066
+ if (!nameHasRdns || !Buffer.isBuffer(pk) || algStr === null) {
3009
3067
  throw E("path/bad-input", who + ": a trustAnchor tuple must be { name: { rdns: [...] }, publicKey: Buffer, algorithm: an OID string or a SubjectPublicKeyInfo algorithm carrying an oid }");
3010
3068
  }
3011
3069
  // Hold the stated algorithm to a canonical dotted-decimal OID: a shape-valid but non-canonical value
@@ -3256,7 +3314,7 @@ async function _fetchAiaIssuers(current, aia) {
3256
3314
  // A DNS-NAME host's private RESOLUTION can only be blocked by a transport that filters the resolved address;
3257
3315
  // with an UNGUARDED transport (an injected fn that does not vouch blocksPrivateAddresses) fail closed -- fetch
3258
3316
  // IP literals only (already validated above), never a hostname whose resolved address we cannot verify.
3259
- if (net.isIP(u.hostname.replace(/^\[(.*)\]$/, "$1")) === 0 && !aia.transportGuardsAddresses) continue;
3317
+ if (net.isIP(_stripBrackets(u.hostname)) === 0 && !aia.transportGuardsAddresses) continue;
3260
3318
  u.hash = ""; // the fragment is never sent on the wire -> not part of the request/dedupe identity
3261
3319
  if (intrinsic.setHas(aia.fetchedUrls, u.href) || intrinsic.setHas(seenThisCert, u.href)) continue; // dedupe (build-wide OR same-cert) on the normalized URL
3262
3320
  if (seenThisCert.size >= aia.maxPerCert) break; // per-cert DISTINCT-url cap, checked BEFORE appending so maxAiaPerCert:0 collects nothing (no fetch at all)
package/lib/pbes2.js CHANGED
@@ -157,7 +157,7 @@ function pbes2Encrypt(pwBytes, plaintext, opts, E, prefix) {
157
157
  opts = opts || {};
158
158
  var cipherName = opts.cipher || "aes256-CBC";
159
159
  var keyBits = CONTENT_KEYBITS[O(cipherName)];
160
- if (!keyBits || !/CBC/.test(cipherName)) throw E(prefix + "/bad-input", "unsupported PBES2 cipher " + cipherName + " (AES-CBC only)");
160
+ if (!keyBits || cipherName.indexOf("CBC") === -1) throw E(prefix + "/bad-input", "unsupported PBES2 cipher " + cipherName + " (AES-CBC only)");
161
161
  var prf = opts.prf || "hmacWithSHA256";
162
162
  var prfNode = prfNodeByName(prf, E, prefix);
163
163
  var iterations = assertIterations(opts.iterations == null ? 2048 : opts.iterations, E, prefix);
@@ -200,7 +200,7 @@ function pbes2Decrypt(pwBytes, params, ciphertext, opts, E, prefix, budget) {
200
200
  pb = parsePbkdf2Params(kdf[1].bytes, opts, E, prefix, true); // strictPrf: reject a non-canonical explicit default prf
201
201
  var encOid = asn1.read.oid(encScheme[0]);
202
202
  keyBits = CONTENT_KEYBITS[encOid];
203
- if (!keyBits || !/CBC/.test(oid.name(encOid) || "")) throw E(prefix + "/unsupported-algorithm", "unsupported PBES2 encryptionScheme " + (oid.name(encOid) || encOid) + " (AES-CBC only)");
203
+ if (!keyBits || (oid.name(encOid) || "").indexOf("CBC") === -1) throw E(prefix + "/unsupported-algorithm", "unsupported PBES2 encryptionScheme " + (oid.name(encOid) || encOid) + " (AES-CBC only)");
204
204
  iv = asn1.read.octetString(encScheme[1]);
205
205
  if (iv.length !== 16) throw E(prefix + "/bad-algorithm-parameters", "the AES-CBC IV must be 16 octets");
206
206
  } catch (e) {
@@ -216,7 +216,7 @@ function _p12Encode(pw) {
216
216
  // A view first, so a detached backing store is a reject and never an empty password; then a copy,
217
217
  // because everything this function returns is reported owned and gets zeroized after derivation,
218
218
  // and handing back a view of a caller's Uint8Array would wipe the caller's own credential.
219
- if (Buffer.isBuffer(pw) || pw instanceof Uint8Array) return guard.bytes.snapshot(pw, Pkcs12Error, "pkcs12/bad-input", "the password");
219
+ if (Buffer.isBuffer(pw) || pw instanceof Uint8Array) return guard.bytes.snapshot(pw, Pkcs12Error, "pkcs12/bad-input", "the password"); // allow:byte-source-narrow -- password on the ownership contract (string / Buffer / Uint8Array); widening passwords is a separate root decision
220
220
  if (typeof pw !== "string") throw _err("pkcs12/bad-input", _MISSING_PASSWORD);
221
221
  var out = Buffer.alloc(pw.length * 2 + 2); // + the 2-byte NULL terminator
222
222
  for (var i = 0; i < pw.length; i++) {
@@ -507,7 +507,7 @@ async function _buildAuthSafeElement(sc, opts) {
507
507
  var alg = sc.contentEncryptionAlgorithm || "aes-256-cbc";
508
508
  // RFC 7292 sec. 4.1 lists only id-envelopedData for a public-key privacy safe (never id-ct-authEnvelopedData);
509
509
  // cms.encrypt DEFAULTS to aes-256-gcm (AEAD -> AuthEnvelopedData), so force a CBC content cipher and reject GCM.
510
- if (!/^aes-(128|192|256)-cbc$/.test(alg)) throw _err("pkcs12/bad-input", "a public-key privacy safe requires an AES-CBC content cipher (aes-128|192|256-cbc), not " + JSON.stringify(alg) + " -- RFC 7292 sec. 4.1 admits only id-envelopedData, not GCM/AuthEnvelopedData");
510
+ if (alg !== "aes-128-cbc" && alg !== "aes-192-cbc" && alg !== "aes-256-cbc") throw _err("pkcs12/bad-input", "a public-key privacy safe requires an AES-CBC content cipher (aes-128|192|256-cbc), not " + JSON.stringify(alg) + " -- RFC 7292 sec. 4.1 admits only id-envelopedData, not GCM/AuthEnvelopedData");
511
511
  var envCi = await cms.encrypt(safeContentsDer, sc.recipients, { contentEncryptionAlgorithm: alg, contentType: "data" });
512
512
  return b.raw(envCi); // cms.encrypt returns the whole id-envelopedData ContentInfo DER -- splice it verbatim
513
513
  }