@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/trust.js CHANGED
@@ -44,7 +44,10 @@ var intrinsic = require("./guard-intrinsic");
44
44
  // Own-key membership through an operation taken at load. Written out, the question reads two
45
45
  // replaceable properties to answer one thing, and here it decides which trust bits an anchor carries.
46
46
  var _hasOwn = intrinsic.hasOwn;
47
+ var _charCodeAt = intrinsic.uncurry(String.prototype.charCodeAt);
48
+ var _strSlice = intrinsic.uncurry(String.prototype.slice);
47
49
  var x509 = require("./schema-x509");
50
+ var pkix = require("./schema-pkix");
48
51
 
49
52
  var TrustError = errors.TrustError;
50
53
  var LIMITS = constants.LIMITS;
@@ -75,7 +78,104 @@ var CKT_DELEGATOR = "CKT_NSS_TRUSTED_DELEGATOR";
75
78
  // An attribute line: `CKA_<NAME> <type> [<value>]`. The value grammar is
76
79
  // per-type (a quoted UTF8 string, a single token, or nothing for
77
80
  // MULTILINE_OCTAL whose payload follows on its own lines).
78
- var ATTR_RE = /^(CKA_[A-Z0-9_]+)(?:\s+(\S+)(?:\s+(.*))?)?$/;
81
+ // ---- certdata scanners by character (no regex) ----------------------------------------------------
82
+ function _isNameChar(c) { return (c >= 65 && c <= 90) || (c >= 48 && c <= 57) || c === 95; } // A-Z 0-9 _
83
+ function _isDigitCode(c) { return c >= 48 && c <= 57; }
84
+ function _isDateSepCode(c) { return c === 46 || c === 47 || c === 45; } // . / -
85
+ function _isOctalDigit(c) { return c >= 48 && c <= 55; } // 0-7
86
+ function _isWsCode(c) { return pkix.isJsWhitespace(c); }
87
+ function _containsWs(s) { for (var i = 0; i < s.length; i++) { if (_isWsCode(_charCodeAt(s, i))) return true; } return false; } // /\s/.test(s)
88
+
89
+ // An attribute line `CKA_<NAME> [<type> [<value>]]`, matching
90
+ // /^(CKA_[A-Z0-9_]+)(?:\s+(\S+)(?:\s+(.*))?)?$/. Returns [name, type|undefined, value|undefined] or null.
91
+ function _parseAttrLine(s) {
92
+ var n = s.length;
93
+ if (_strSlice(s, 0, 4) !== "CKA_") return null;
94
+ var p = 4;
95
+ while (p < n && _isNameChar(_charCodeAt(s, p))) p += 1;
96
+ if (p === 4) return null;
97
+ var name = _strSlice(s, 0, p);
98
+ if (p === n) return [name, undefined, undefined];
99
+ if (!_isWsCode(_charCodeAt(s, p))) return null;
100
+ while (p < n && _isWsCode(_charCodeAt(s, p))) p += 1;
101
+ var typeStart = p;
102
+ while (p < n && !_isWsCode(_charCodeAt(s, p))) p += 1;
103
+ if (p === typeStart) return null;
104
+ var type = _strSlice(s, typeStart, p);
105
+ if (p === n) return [name, type, undefined];
106
+ if (!_isWsCode(_charCodeAt(s, p))) return null;
107
+ while (p < n && _isWsCode(_charCodeAt(s, p))) p += 1;
108
+ return [name, type, _strSlice(s, p)];
109
+ }
110
+
111
+ // A single double-quoted value with no interior quote (matching /^"([^"]*)"$/): the inner content, or
112
+ // null. Empty content ("\"\"") returns "" (a valid value), distinct from the null no-match.
113
+ function _unquote(s) {
114
+ var n = s.length;
115
+ if (n < 2 || _charCodeAt(s, 0) !== 34 || _charCodeAt(s, n - 1) !== 34) return null; // surrounding "
116
+ for (var i = 1; i < n - 1; i++) if (_charCodeAt(s, i) === 34) return null; // no interior "
117
+ return _strSlice(s, 1, n - 1);
118
+ }
119
+
120
+ // String.prototype.split(/\s+/): the pieces between whitespace runs (a leading/trailing run yields a
121
+ // "" element; "" -> [""]). Index assignment (not .push) so the scanner adds no live intrinsic read.
122
+ function _splitWhitespace(s) {
123
+ var out = [], i = 0, n = s.length, pieceStart = 0;
124
+ while (i < n) {
125
+ if (_isWsCode(_charCodeAt(s, i))) {
126
+ out[out.length] = _strSlice(s, pieceStart, i);
127
+ while (i < n && _isWsCode(_charCodeAt(s, i))) i += 1;
128
+ pieceStart = i;
129
+ } else i += 1;
130
+ }
131
+ out[out.length] = _strSlice(s, pieceStart);
132
+ return out;
133
+ }
134
+
135
+ // Exactly three octal digits (matching /^[0-7]{3}$/).
136
+ function _isOctal3(d) {
137
+ if (d.length !== 3) return false;
138
+ for (var i = 0; i < 3; i++) { if (!_isOctalDigit(_charCodeAt(d, i))) return false; }
139
+ return true;
140
+ }
141
+
142
+ // String.prototype.split(/\r\n|\n|\r/): split on CRLF, LF, or CR line terminators.
143
+ function _splitLines(text) {
144
+ var out = [], start = 0, i = 0, n = text.length;
145
+ while (i < n) {
146
+ var c = _charCodeAt(text, i);
147
+ if (c === 13) { // CR (consume a following LF as one)
148
+ out[out.length] = _strSlice(text, start, i);
149
+ if (i + 1 < n && _charCodeAt(text, i + 1) === 10) i += 1;
150
+ i += 1; start = i;
151
+ } else if (c === 10) { // LF
152
+ out[out.length] = _strSlice(text, start, i);
153
+ i += 1; start = i;
154
+ } else i += 1;
155
+ }
156
+ out[out.length] = _strSlice(text, start);
157
+ return out;
158
+ }
159
+
160
+ // A Y-M-D date with dotted/dashed/slashed separators, matching /^(\d{4})[./-](\d{1,2})[./-](\d{1,2})$/.
161
+ // Returns [year, month, day] (raw digit strings) or null.
162
+ function _parseYmd(s) {
163
+ var n = s.length;
164
+ for (var a = 0; a < 4; a++) if (!_isDigitCode(_charCodeAt(s, a))) return null;
165
+ var year = _strSlice(s, 0, 4), p = 4;
166
+ if (!_isDateSepCode(_charCodeAt(s, p))) return null; p += 1;
167
+ var ms = p;
168
+ while (p < n && p - ms < 2 && _isDigitCode(_charCodeAt(s, p))) p += 1;
169
+ if (p === ms) return null;
170
+ var month = _strSlice(s, ms, p);
171
+ if (!_isDateSepCode(_charCodeAt(s, p))) return null; p += 1;
172
+ var ds = p;
173
+ while (p < n && p - ds < 2 && _isDigitCode(_charCodeAt(s, p))) p += 1;
174
+ if (p === ds) return null;
175
+ var day = _strSlice(s, ds, p);
176
+ if (p !== n) return null;
177
+ return [year, month, day];
178
+ }
79
179
 
80
180
  // ---------------------------------------------------------------------------
81
181
  // certdata.txt lexer -- line-oriented, C.LIMITS-bounded, fail-closed
@@ -93,7 +193,7 @@ function _readOctalBody(lines, idx) {
93
193
  var t = lines[i].trim();
94
194
  if (t === "END") return { bytes: Buffer.from(out), next: i + 1 };
95
195
  if (t === "") throw E("trust/bad-octal", "MULTILINE_OCTAL body interrupted by a blank line before END");
96
- var chunks = t.split(/\s+/);
196
+ var chunks = _splitWhitespace(t);
97
197
  for (var c = 0; c < chunks.length; c++) {
98
198
  var chunk = chunks[c];
99
199
  for (var k = 0; k < chunk.length; k += 4) {
@@ -101,7 +201,7 @@ function _readOctalBody(lines, idx) {
101
201
  throw E("trust/bad-octal", "MULTILINE_OCTAL expects \\ooo escapes, got " + JSON.stringify(chunk.slice(k, k + 4)));
102
202
  }
103
203
  var d = chunk.slice(k + 1, k + 4);
104
- if (!/^[0-7]{3}$/.test(d)) {
204
+ if (!_isOctal3(d)) {
105
205
  throw E("trust/bad-octal", "a MULTILINE_OCTAL escape is a backslash + exactly three octal digits, got " + JSON.stringify("\\" + d));
106
206
  }
107
207
  if (d.charCodeAt(0) > 51 /* '3' */) {
@@ -120,7 +220,7 @@ function _readOctalBody(lines, idx) {
120
220
  // `CKA_* <type> <value>` attribute lines. Returns [{ attrs }] where attrs maps
121
221
  // the attribute name to { type, value | bytes }.
122
222
  function _lexObjects(text) {
123
- var lines = text.split(/\r\n|\n|\r/);
223
+ var lines = _splitLines(text);
124
224
  var n = lines.length;
125
225
  var i = 0, sawBegin = false;
126
226
  for (; i < n; i++) {
@@ -137,10 +237,10 @@ function _lexObjects(text) {
137
237
  i++; continue;
138
238
  }
139
239
  if (t.charAt(0) === "#") { i++; continue; }
140
- var m = ATTR_RE.exec(t);
240
+ var m = _parseAttrLine(t);
141
241
  if (!m) throw E("trust/bad-block", "unexpected line in the certdata object stream: " + JSON.stringify(t.slice(0, 64)));
142
- var name = m[1], type = m[2];
143
- var rawVal = m[3] !== undefined ? m[3].trim() : undefined;
242
+ var name = m[0], type = m[1];
243
+ var rawVal = m[2] !== undefined ? m[2].trim() : undefined;
144
244
  if (!type) throw E("trust/bad-block", "attribute line is missing its type token: " + name);
145
245
  if (!cur) {
146
246
  // Refuse the block bomb before lexing another block's payloads.
@@ -161,15 +261,15 @@ function _lexObjects(text) {
161
261
  if (rawVal !== "CK_TRUE" && rawVal !== "CK_FALSE") throw E("trust/bad-block", name + " CK_BBOOL must be CK_TRUE or CK_FALSE");
162
262
  entry = { type: type, value: rawVal === "CK_TRUE" };
163
263
  } else if (type === "UTF8") {
164
- var q = rawVal !== undefined ? /^"([^"]*)"$/.exec(rawVal) : null;
165
- if (!q) throw E("trust/bad-block", name + " UTF8 must carry one double-quoted value");
166
- entry = { type: type, value: q[1] };
264
+ var q = rawVal !== undefined ? _unquote(rawVal) : null;
265
+ if (q === null) throw E("trust/bad-block", name + " UTF8 must carry one double-quoted value");
266
+ entry = { type: type, value: q };
167
267
  } else if (type === "CK_TRUST") {
168
- if (rawVal === undefined || /\s/.test(rawVal)) throw E("trust/bad-block", name + " CK_TRUST must carry a single token value");
268
+ if (rawVal === undefined || _containsWs(rawVal)) throw E("trust/bad-block", name + " CK_TRUST must carry a single token value");
169
269
  if (!CKT_RECOGNIZED[rawVal]) throw E("trust/bad-trust-value", "unrecognized CK_TRUST value " + JSON.stringify(rawVal) + " on " + name);
170
270
  entry = { type: type, value: rawVal };
171
271
  } else if (type === "CK_OBJECT_CLASS" || type === "CK_CERTIFICATE_TYPE") {
172
- if (rawVal === undefined || /\s/.test(rawVal)) throw E("trust/bad-block", name + " " + type + " must carry a single token value");
272
+ if (rawVal === undefined || _containsWs(rawVal)) throw E("trust/bad-block", name + " " + type + " must carry a single token value");
173
273
  entry = { type: type, value: rawVal };
174
274
  } else {
175
275
  // An unknown value type cannot be lexed safely (its value grammar is
@@ -625,10 +725,10 @@ function _trustBits(cell) {
625
725
  function _csvDate(cell, label) {
626
726
  var s = String(cell).trim();
627
727
  if (s === "") return null;
628
- var m = /^(\d{4})[./-](\d{1,2})[./-](\d{1,2})$/.exec(s);
728
+ var m = _parseYmd(s);
629
729
  if (!m) throw E("trust/bad-csv", label + " must be a Y-M-D date, got " + JSON.stringify(s));
630
730
  var pad = function (x) { return x.length === 1 ? "0" + x : x; };
631
- var payload = Buffer.from(m[1] + pad(m[2]) + pad(m[3]) + "235959Z", "latin1");
731
+ var payload = Buffer.from(m[0] + pad(m[1]) + pad(m[2]) + "235959Z", "latin1");
632
732
  // _strictTime tags every rejection (a rolled-over month/day among them) with the code passed here,
633
733
  // so the re-throw carries it unchanged; the wrap normalizes any other failure to the same typed code
634
734
  // so a bad row is always trust/bad-csv, never an untyped exception escaping parseCcadbCsv.
package/lib/tsp-sign.js CHANGED
@@ -26,6 +26,7 @@ var smime = require("./schema-smime");
26
26
  var schemaTsp = require("./schema-tsp");
27
27
  var guard = require("./guard-all");
28
28
  var intrinsic = require("./guard-intrinsic");
29
+ var _charCodeAt = intrinsic.uncurry(String.prototype.charCodeAt);
29
30
  var frameworkError = require("./framework-error");
30
31
 
31
32
  var pkix = require("./schema-pkix");
@@ -34,6 +35,12 @@ var TspError = frameworkError.TspError;
34
35
  var _NS = pkix.makeNS("tsp", TspError, oid);
35
36
  var b = asn1.build;
36
37
  function _err(code, message, cause) { return new TspError(code, message, cause); }
38
+
39
+ // Any ASCII digit 1-9 present (a non-zero fractional digit), by character scan -- no regex.
40
+ function _hasNonZeroDigit(s) {
41
+ for (var i = 0; i < s.length; i++) { var c = _charCodeAt(s, i); if (c >= 49 && c <= 57) return true; }
42
+ return false;
43
+ }
37
44
  function O(name) { return oid.byName(name); }
38
45
 
39
46
  // The shared authoring-side builder, for the same pre-encoded-Extension validation every other
@@ -72,17 +79,20 @@ function _hashAlgId(name) {
72
79
  // A policy identifier: an OID name or a dotted-decimal string.
73
80
  function _policy(p) {
74
81
  if (typeof p !== "string") throw _err("tsp/bad-input", "the timestamp policy must be an OID name or dotted string");
75
- return /^\d+(\.\d+)+$/.test(p) ? b.oid(p) : b.oid(O(p));
82
+ return oid.isDottedDecimal(p) ? b.oid(p) : b.oid(O(p));
76
83
  }
77
- // The signer certificate DER (DER Buffer / PEM string / Uint8Array) -- for the ESSCertIDv2 hash.
84
+ // The signer certificate DER (a DER BufferSource / PEM string) -- for the ESSCertIDv2 hash.
78
85
  function _certDer(c) {
79
86
  if (c == null) throw _err("tsp/bad-input", "the TSA signer requires a certificate (cert)");
80
- if (c instanceof Uint8Array && !Buffer.isBuffer(c)) c = Buffer.from(c); // a Uint8Array -> Buffer
81
- if (Buffer.isBuffer(c)) { if (c[0] === 0x30) return c; c = c.toString("latin1"); } // DER as-is, else decode as PEM
82
- if (typeof c !== "string") throw _err("tsp/bad-input", "the TSA certificate must be a DER Buffer or a PEM string");
83
- var m = c.match(/-----BEGIN CERTIFICATE-----([\s\S]*?)-----END CERTIFICATE-----/);
84
- if (!m) throw _err("tsp/bad-input", "the TSA certificate PEM is not a CERTIFICATE block");
85
- return Buffer.from(m[1].replace(/[^A-Za-z0-9+/=]/g, ""), "base64");
87
+ if (guard.bytes.isByteSource(c)) { // any BufferSource -> a byte view
88
+ var buf = guard.bytes.source(c, TspError, "tsp/bad-input", "the TSA certificate");
89
+ if (buf[0] === 0x30) return buf; // DER as-is
90
+ c = buf.toString("latin1"); // else decode as PEM
91
+ }
92
+ if (typeof c !== "string") throw _err("tsp/bad-input", "the TSA certificate must be a DER BufferSource or a PEM string");
93
+ var der = pkix.pemDecodeLenient(c, "CERTIFICATE");
94
+ if (der === null) throw _err("tsp/bad-input", "the TSA certificate PEM is not a CERTIFICATE block");
95
+ return der;
86
96
  }
87
97
 
88
98
  // The RFC 5035 SigningCertificateV2 signed-attribute value binding the token to the TSA cert:
@@ -177,8 +187,9 @@ function _sign(messageImprint, tsa, opts) {
177
187
  }
178
188
  var mi = messageImprint || {};
179
189
  if (!mi.hashAlgorithm || !NODE_DIGEST[mi.hashAlgorithm]) throw _err("tsp/unsupported-algorithm", "messageImprint.hashAlgorithm must be a supported hash name");
180
- if (!Buffer.isBuffer(mi.hashedMessage) && !(mi.hashedMessage instanceof Uint8Array)) throw _err("tsp/bad-input", "messageImprint.hashedMessage must be a Buffer");
181
- if (mi.hashedMessage.length !== HASH_LEN[mi.hashAlgorithm]) throw _err("tsp/bad-input", "messageImprint.hashedMessage length (" + mi.hashedMessage.length + ") does not match the " + mi.hashAlgorithm + " digest length (" + HASH_LEN[mi.hashAlgorithm] + ")");
190
+ if (!guard.bytes.isByteSource(mi.hashedMessage)) throw _err("tsp/bad-input", "messageImprint.hashedMessage must be a BufferSource");
191
+ var _signHmLen = guard.bytes.source(mi.hashedMessage, TspError, "tsp/bad-input", "messageImprint.hashedMessage").length;
192
+ if (_signHmLen !== HASH_LEN[mi.hashAlgorithm]) throw _err("tsp/bad-input", "messageImprint.hashedMessage length (" + _signHmLen + ") does not match the " + mi.hashAlgorithm + " digest length (" + HASH_LEN[mi.hashAlgorithm] + ")");
182
193
  if (!opts.policy) throw _err("tsp/bad-input", "a timestamp token requires a policy identifier (opts.policy)");
183
194
  if (opts.serialNumber == null) throw _err("tsp/bad-input", "a timestamp token requires a serialNumber (opts.serialNumber)");
184
195
 
@@ -188,7 +199,7 @@ function _sign(messageImprint, tsa, opts) {
188
199
 
189
200
  // TSTInfo (RFC 3161 sec. 2.4.2): version(1), policy, messageImprint, serialNumber, genTime,
190
201
  // then the OPTIONAL accuracy / ordering / nonce in ascending order.
191
- var imprint = b.sequence([_hashAlgId(mi.hashAlgorithm), b.octetString(Buffer.from(mi.hashedMessage))]);
202
+ var imprint = b.sequence([_hashAlgId(mi.hashAlgorithm), b.octetString(guard.bytes.source(mi.hashedMessage, TspError, "tsp/bad-input", "messageImprint.hashedMessage"))]);
192
203
  // genTime defaults to now; a supplied value MUST be a valid Date (never a silently-ignored
193
204
  // non-Date or an Invalid Date that would encode a garbage GeneralizedTime).
194
205
  if (opts.genTime != null) guard.time.assertValid(opts.genTime, _err, "tsp/bad-input", "genTime");
@@ -270,16 +281,16 @@ function _failInfoBits(names) {
270
281
  // Coerce a timeStampToken (a CMS ContentInfo the token producer emits) to DER for embedding in a
271
282
  // TimeStampResp -- a DER Buffer as-is, a Uint8Array copied, or a PEM string de-armored.
272
283
  function _tokenDer(t) {
273
- if (Buffer.isBuffer(t) && t[0] === 0x30) return t;
274
- if (t instanceof Uint8Array && !Buffer.isBuffer(t)) { var u = Buffer.from(t); if (u[0] === 0x30) return u; }
284
+ if (guard.bytes.isByteSource(t)) { var u = guard.bytes.source(t, TspError, "tsp/bad-input", "the timeStampToken"); if (u[0] === 0x30) return u; }
275
285
  if (typeof t === "string") return schemaTsp.pemDecode(t);
276
- throw _err("tsp/bad-input", "the timeStampToken must be a DER Buffer or a PEM string");
286
+ throw _err("tsp/bad-input", "the timeStampToken must be a DER BufferSource or a PEM string");
277
287
  }
278
288
 
279
289
  function _assertImprint(mi) {
280
290
  if (!mi.hashAlgorithm || !NODE_DIGEST[mi.hashAlgorithm]) throw _err("tsp/unsupported-algorithm", "messageImprint.hashAlgorithm must be a supported hash name");
281
- if (!Buffer.isBuffer(mi.hashedMessage) && !(mi.hashedMessage instanceof Uint8Array)) throw _err("tsp/bad-input", "messageImprint.hashedMessage must be a Buffer");
282
- if (mi.hashedMessage.length !== HASH_LEN[mi.hashAlgorithm]) throw _err("tsp/bad-input", "messageImprint.hashedMessage length (" + mi.hashedMessage.length + ") does not match the " + mi.hashAlgorithm + " digest length (" + HASH_LEN[mi.hashAlgorithm] + ")");
291
+ if (!guard.bytes.isByteSource(mi.hashedMessage)) throw _err("tsp/bad-input", "messageImprint.hashedMessage must be a BufferSource");
292
+ var hmLen = guard.bytes.source(mi.hashedMessage, TspError, "tsp/bad-input", "messageImprint.hashedMessage").length;
293
+ if (hmLen !== HASH_LEN[mi.hashAlgorithm]) throw _err("tsp/bad-input", "messageImprint.hashedMessage length (" + hmLen + ") does not match the " + mi.hashAlgorithm + " digest length (" + HASH_LEN[mi.hashAlgorithm] + ")");
283
294
  }
284
295
 
285
296
  /**
@@ -328,7 +339,7 @@ function request(messageImprint, opts) {
328
339
  });
329
340
  }
330
341
  _assertImprint(mi);
331
- var imprint = b.sequence([_hashAlgId(mi.hashAlgorithm), b.octetString(Buffer.from(mi.hashedMessage))]);
342
+ var imprint = b.sequence([_hashAlgId(mi.hashAlgorithm), b.octetString(guard.bytes.source(mi.hashedMessage, TspError, "tsp/bad-input", "messageImprint.hashedMessage"))]);
332
343
  // TimeStampReq: version(1), messageImprint, then OPTIONAL reqPolicy / nonce / certReq /
333
344
  // extensions in schema order. certReq DEFAULT FALSE -> only an explicit TRUE is encoded (DER).
334
345
  var fields = [b.integer(1n), imprint];
@@ -337,7 +348,7 @@ function request(messageImprint, opts) {
337
348
  if (opts.certReq != null && typeof opts.certReq !== "boolean") throw _err("tsp/bad-input", "certReq must be a boolean");
338
349
  if (opts.certReq === true) fields.push(b.boolean(true));
339
350
  if (opts.extensions != null) {
340
- if (!Array.isArray(opts.extensions) || !guard.list.allMatch(opts.extensions, function (e) { return Buffer.isBuffer(e) || e instanceof Uint8Array; })) throw _err("tsp/bad-input", "extensions must be an array of encoded Extension DER buffers");
351
+ if (!Array.isArray(opts.extensions) || !guard.list.allMatch(opts.extensions, function (e) { return guard.bytes.isByteSource(e); })) throw _err("tsp/bad-input", "extensions must be an array of encoded Extension DER buffers");
341
352
  // Each pre-encoded Extension is validated as one, and its extnID must not repeat -- the same
342
353
  // gate every other request builder applies. Spliced in unchecked, a caller relaying a blob it
343
354
  // did not author put fully chosen bytes inside [0] and this encoder emitted DER its own
@@ -345,7 +356,7 @@ function request(messageImprint, opts) {
345
356
  // the DER DEFAULT rule forbids.
346
357
  var seenExt = {};
347
358
  var encoded = opts.extensions.map(function (e, i) {
348
- var der = Buffer.from(e);
359
+ var der = guard.bytes.source(e, TspError, "tsp/bad-input", "an extension");
349
360
  _b.assertValidExtension(der, i);
350
361
  var extnId = asn1.read.oid(asn1.decode(der).children[0]);
351
362
  if (seenExt[extnId]) throw _err("tsp/bad-input", "duplicate request extension " + extnId + " (RFC 5280 sec. 4.2)");
@@ -516,14 +527,15 @@ function _imprintMatches(mi, data) {
516
527
  // matching short precomputed imprint must never make it verify (RFC 3161 sec. 2.4.1).
517
528
  if (mi.hashedMessage.length !== wantLen) return "tsp/imprint-mismatch";
518
529
  var actual;
519
- if (Buffer.isBuffer(data) || data instanceof Uint8Array) {
520
- actual = nodeCrypto.createHash(nodeAlg).update(Buffer.from(data)).digest();
521
- } else if (data && typeof data === "object" && (Buffer.isBuffer(data.hashedMessage) || data.hashedMessage instanceof Uint8Array)) {
530
+ if (guard.bytes.isByteSource(data)) {
531
+ actual = nodeCrypto.createHash(nodeAlg).update(guard.bytes.source(data, TspError, "tsp/bad-input", "data")).digest();
532
+ } else if (data && typeof data === "object" && guard.bytes.isByteSource(data.hashedMessage)) {
522
533
  if (data.hashAlgorithm !== name) return "tsp/imprint-mismatch"; // a precomputed imprint under a different algorithm
523
534
  // a caller-supplied precomputed imprint of the wrong length is a config error (the sign path
524
535
  // rejects it identically) -- throw rather than silently mismatch (three-tier: caller input).
525
- if (data.hashedMessage.length !== wantLen) throw _err("tsp/bad-input", "precomputed imprint hashedMessage length (" + data.hashedMessage.length + ") does not match the " + name + " digest length (" + wantLen + ")");
526
- actual = Buffer.from(data.hashedMessage);
536
+ var dhmLen = guard.bytes.source(data.hashedMessage, TspError, "tsp/bad-input", "precomputed imprint hashedMessage").length;
537
+ if (dhmLen !== wantLen) throw _err("tsp/bad-input", "precomputed imprint hashedMessage length (" + dhmLen + ") does not match the " + name + " digest length (" + wantLen + ")");
538
+ actual = guard.bytes.source(data.hashedMessage, TspError, "tsp/bad-input", "precomputed imprint hashedMessage");
527
539
  } else {
528
540
  throw _err("tsp/bad-input", "pki.tsp.verify data must be a Buffer or a { hashAlgorithm, hashedMessage } imprint");
529
541
  }
@@ -749,7 +761,7 @@ async function verify(token, data, opts) {
749
761
  // nothing to notice it by. Naming the difference at the boundary is the only place it is cheap.
750
762
  guard.identifier.assertKnownKeys(opts, _VERIFY_OPTS, _err, "tsp/bad-input",
751
763
  "pki.tsp.verify has an unknown option (note the anchor option here is `trustAnchor`, singular, an anchor tuple -- not the `trustAnchors` certificate list pki.cms.verify takes) ");
752
- if (opts.certs != null && (!Array.isArray(opts.certs) || !guard.list.allMatch(opts.certs, function (c) { return Buffer.isBuffer(c) || c instanceof Uint8Array; }))) {
764
+ if (opts.certs != null && (!Array.isArray(opts.certs) || !guard.list.allMatch(opts.certs, function (c) { return guard.bytes.isByteSource(c); }))) {
753
765
  throw _err("tsp/bad-input", "pki.tsp.verify opts.certs must be an array of DER certificate Buffers"); // a bad element is a caller error, never silently dropped
754
766
  }
755
767
  var tokenDer = _tokenDer(token); // DER Buffer / PEM -> DER; a non-token input throws tsp/bad-input
@@ -790,7 +802,7 @@ async function verify(token, data, opts) {
790
802
  }
791
803
  // M15 -- if the requested policy is supplied, the token's policy MUST equal it.
792
804
  if (opts.reqPolicy != null) {
793
- var wantPolicy = /^\d+(\.\d+)+$/.test(opts.reqPolicy) ? opts.reqPolicy : O(opts.reqPolicy);
805
+ var wantPolicy = oid.isDottedDecimal(opts.reqPolicy) ? opts.reqPolicy : O(opts.reqPolicy);
794
806
  if (tst.policy !== wantPolicy) return fail("tsp/policy-mismatch");
795
807
  }
796
808
  // A TSTInfo carrying a CRITICAL extension the verifier cannot process is unusable: RFC 3161 sec.
@@ -835,7 +847,7 @@ async function verify(token, data, opts) {
835
847
  // notBefore is rejected at the floor (ceil >= notBefore but floor < notBefore). Certificate
836
848
  // validity is seconds-precise (RFC 5280 sec. 4.1.2.5), so this never over-rejects a valid token.
837
849
  var floorT = tst.genTime;
838
- var ceilT = (tst.genTimeFraction != null && /[1-9]/.test(tst.genTimeFraction.slice(3))) ? new Date(guard.time.instantOf(floorT) + 1) : floorT;
850
+ var ceilT = (tst.genTimeFraction != null && _hasNonZeroDigit(tst.genTimeFraction.slice(3))) ? new Date(guard.time.instantOf(floorT) + 1) : floorT;
839
851
  try {
840
852
  // parseToken surfaces each embedded cert as { bytes, tagClass, ... }; take the X.509 certs
841
853
  // (universal SEQUENCE), skipping any attribute-certificate choices, and parse their raw bytes.
@@ -226,10 +226,10 @@ function normalizeObjectAttributePolicy(policy, E, code) {
226
226
  // and now live in one place. It also decodes, so the hex path cannot validate one string
227
227
  // and decode another.
228
228
  allow = ap.allow.map(function (entry, i) {
229
- if (Buffer.isBuffer(entry)) return guard.bytes.snapshot(entry, E, code, "opts.tpmPolicy.authPolicy.allow[" + i + "]");
229
+ if (guard.bytes.isByteSource(entry)) return guard.bytes.snapshotSource(entry, E, code, "opts.tpmPolicy.authPolicy.allow[" + i + "]");
230
230
  var label = "opts.tpmPolicy.authPolicy.allow[" + i + "]";
231
231
  if (typeof entry !== "string" || entry.length === 0) {
232
- throw new E(code, label + " must be a Buffer or an even-length hex string");
232
+ throw new E(code, label + " must be a BufferSource or an even-length hex string");
233
233
  }
234
234
  return guard.encoding.hex(entry, null, function (c, m) { return new E(c, m); }, code,
235
235
  label + " must be a Buffer or an even-length hex string --");
@@ -26,6 +26,7 @@ var intrinsic = require("./guard-intrinsic");
26
26
  // Own-key membership through an operation taken at load. Written out, the question reads two
27
27
  // replaceable properties to answer one thing, and here it decides which status rows an entry carries.
28
28
  var _hasOwn = intrinsic.hasOwn;
29
+ var _charAt = intrinsic.uncurry(String.prototype.charAt);
29
30
  var jose = require("./jose");
30
31
  var rfc3339 = require("./rfc3339");
31
32
  var constants = require("./constants");
@@ -40,6 +41,10 @@ var pkix = require("./schema-pkix");
40
41
  var WebauthnError = frameworkError.WebauthnError;
41
42
  var _KU_NS = pkix.makeNS("webauthn", WebauthnError, oid);
42
43
  function _err(code, message, cause) { return new WebauthnError(code, message, cause); }
44
+ // A 40-character certificate-key-identifier: SHA-1 hex. One accepts either case (/^[0-9a-fA-F]{40}$/), the
45
+ // lookup form is lower-case only (/^[0-9a-f]{40}$/).
46
+ var _KEYID_HEX_CI = pkix.charTable("0123456789abcdefABCDEF");
47
+ var _KEYID_HEX_LOWER = pkix.charTable("0123456789abcdef");
43
48
  var C = constants.LIMITS;
44
49
 
45
50
  // Which signature schemes each certificate key type may perform, keyed by the SPKI algorithm the
@@ -176,7 +181,20 @@ function _deepFreeze(v, depth) {
176
181
  Object.keys(v).forEach(function (k) { _deepFreeze(v[k], depth + 1); });
177
182
  return v;
178
183
  }
179
- var AAGUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
184
+ // The AAGUID canonical form (lowercase hex 8-4-4-4-12 with dashes), matching
185
+ // /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/, decided by character (no regex).
186
+ function _isAaguid(s) {
187
+ if (s.length !== 36) return false;
188
+ for (var i = 0; i < 36; i++) {
189
+ if (i === 8 || i === 13 || i === 18 || i === 23) {
190
+ if (_charAt(s, i) !== "-") return false;
191
+ } else {
192
+ var c = _charAt(s, i);
193
+ if (!((c >= "0" && c <= "9") || (c >= "a" && c <= "f"))) return false;
194
+ }
195
+ }
196
+ return true;
197
+ }
180
198
  // "This authenticator declares no model identity": the AAGUID a U2F authenticator carries, since
181
199
  // U2F has no such concept. It is a sentinel, never a lookup key, and lives in one place so the
182
200
  // lookup and the dispatch that decides which key space applies cannot disagree about it.
@@ -281,7 +299,7 @@ function _verifyMetadataBlob(blob, opts) {
281
299
  // network, and the ordinary way to hold a fetched body is an ArrayBuffer, so the form an
282
300
  // operator most naturally arrives with was the one form this refused. `byteLength` is declared by
283
301
  // an ArrayBuffer and by every view over one, so the ceiling below still bites before any copy.
284
- var raw = (ArrayBuffer.isView(blob) || blob instanceof ArrayBuffer)
302
+ var raw = guard.bytes.isByteSource(blob)
285
303
  ? guard.bytes.source(blob, WebauthnError, "webauthn/bad-input", "the metadata BLOB") : null;
286
304
  // A string's `.length` counts UTF-16 code units, not the UTF-8 bytes the conversion produces, so
287
305
  // measuring it would let a string of multi-byte characters sit under the ceiling and then expand
@@ -600,7 +618,7 @@ function _parsePayload(seg, at, opts) {
600
618
  });
601
619
  var aaguid = null;
602
620
  if (e.aaguid !== undefined) {
603
- if (typeof e.aaguid !== "string" || !AAGUID_RE.test(e.aaguid.toLowerCase())) {
621
+ if (typeof e.aaguid !== "string" || !_isAaguid(e.aaguid.toLowerCase())) {
604
622
  throw _err("webauthn/bad-metadata-blob", "metadata entry " + i + " has a malformed aaguid");
605
623
  }
606
624
  aaguid = e.aaguid.toLowerCase();
@@ -634,7 +652,7 @@ function _parsePayload(seg, at, opts) {
634
652
  // matches -- an entry that silently cannot be found. sec. 3.1.1 requires lower case; the
635
653
  // value is canonicalized, not refused, so a catalogue that differs only in letter
636
654
  // case still resolves instead of denying service for every authenticator it lists.
637
- if (typeof k !== "string" || !/^[0-9a-fA-F]{40}$/.test(k)) {
655
+ if (typeof k !== "string" || k.length !== 40 || !pkix.allCharsIn(k, _KEYID_HEX_CI)) {
638
656
  throw _err("webauthn/bad-metadata-blob", "metadata entry " + i + " has a malformed attestation certificate key identifier");
639
657
  }
640
658
  var lower = k.toLowerCase();
@@ -692,11 +710,11 @@ function metadataFor(metadata, identifier) {
692
710
  if (!isVerifiedResult(metadata)) throw _err("webauthn/bad-input", "metadataFor expects a verifyMetadataBlob result -- an object that merely resembles one, such as a catalogue restored from a cache, has not been through the signature and chain checks");
693
711
  if (typeof identifier !== "string") return null;
694
712
  var key = identifier.toLowerCase();
695
- if (AAGUID_RE.test(key)) {
713
+ if (_isAaguid(key)) {
696
714
  if (key === ZERO_AAGUID) return null;
697
715
  return metadata.byAaguid[key] || null;
698
716
  }
699
- if (/^[0-9a-f]{40}$/.test(key)) return metadataForKeyIdentifier(metadata, key);
717
+ if (key.length === 40 && pkix.allCharsIn(key, _KEYID_HEX_LOWER)) return metadataForKeyIdentifier(metadata, key);
700
718
  return null;
701
719
  }
702
720
 
package/lib/webauthn.js CHANGED
@@ -536,7 +536,7 @@ var _FORMAT_SCOPED_BOOLEAN_OPTS = ["verifySafetyNetJws", "requireCtsProfileMatch
536
536
  // object cannot recurse without end.
537
537
  function _cloneParsed(v, depth) {
538
538
  if (depth > 64) throw _err("webauthn/bad-input", "opts.rootCertificates[] is nested too deeply to be a parsed certificate");
539
- if (Buffer.isBuffer(v) || v instanceof Uint8Array) return guard.bytes.snapshot(v, _err, "webauthn/bad-input", "a byte field of opts.rootCertificates[]");
539
+ if (guard.bytes.isByteSource(v)) return guard.bytes.snapshotSource(v, _err, "webauthn/bad-input", "a byte field of opts.rootCertificates[]");
540
540
  if (Array.isArray(v)) return v.map(function (x) { return _cloneParsed(x, depth + 1); });
541
541
  if (guard.time.isDate(v)) return new Date(guard.time.instantOf(v));
542
542
  if (v && typeof v === "object") {
@@ -559,7 +559,7 @@ function _cloneParsed(v, depth) {
559
559
  //
560
560
  // Anything that is not a BufferSource is refused BY NAME, rather than passed down to be described
561
561
  // by whichever parser reaches it first.
562
- function _isBufferSource(v) { return ArrayBuffer.isView(v) || v instanceof ArrayBuffer; }
562
+ function _isBufferSource(v) { return guard.bytes.isByteSource(v); }
563
563
  function _bytesArg(v, label) {
564
564
  if (_isBufferSource(v)) {
565
565
  return guard.bytes.snapshotSource(v, WebauthnError, "webauthn/bad-input", label);
@@ -2313,7 +2313,7 @@ function verifyAssertion(input) {
2313
2313
  // store already holds the COSE bytes. Accepting only the object made a caller fabricate an
2314
2314
  // authenticatorData that never existed just to reach their own key.
2315
2315
  var coseKey = input.credentialPublicKey;
2316
- if (Buffer.isBuffer(coseKey) || ArrayBuffer.isView(coseKey) || coseKey instanceof ArrayBuffer) {
2316
+ if (guard.bytes.isByteSource(coseKey)) {
2317
2317
  coseKey = parseCoseKey(coseKey);
2318
2318
  } else if (_isPlainObject(coseKey)) {
2319
2319
  // The OBJECT form is held to the same rules about the KEY as the bytes form. The bytes went
package/lib/webcrypto.js CHANGED
@@ -760,7 +760,7 @@ async function _deriveBitsRaw(alg, key, length) {
760
760
  // caveat covers, so it is wiped once the derivation has consumed it.
761
761
  return _withSecretBytes(key, function (ikm) {
762
762
  var derived = nodeCrypto.hkdfSync(_hashNode(alg.hash, "HKDF"), ikm, _toBuf(alg.salt, "HKDF salt"), _toBuf(alg.info || _bufferAlloc(0), "HKDF info"), length / 8);
763
- return derived instanceof ArrayBuffer ? derived : _toArrayBuffer(_bufferFrom(derived));
763
+ return _types.isArrayBuffer(derived) ? derived : _toArrayBuffer(_bufferFrom(derived));
764
764
  });
765
765
  }
766
766
  if (name === "PBKDF2") {
package/lib/x509-sign.js CHANGED
@@ -105,7 +105,7 @@ function _skiValueOf(caCert) {
105
105
  return null;
106
106
  }
107
107
  function _akiKeyId(val, ctx) {
108
- if (Buffer.isBuffer(val)) return guard.bytes.snapshot(val, CertificateError, "x509/bad-input", "the authorityKeyIdentifier keyIdentifier");
108
+ if (guard.bytes.isByteSource(val)) return guard.bytes.snapshotSource(val, CertificateError, "x509/bad-input", "the authorityKeyIdentifier keyIdentifier");
109
109
  if (val === true) {
110
110
  if (ctx.issuerCert) { var ski = _skiValueOf(ctx.issuerCert); if (ski) return ski; }
111
111
  return _spkiKeyId(ctx.issuerSpki);
@@ -264,7 +264,12 @@ function _hasCriticalSan(extSpec) {
264
264
  * key being certified), `notBefore` / `notAfter` (`Date`s), an optional `serialNumber`, and an optional
265
265
  * `extensions` object. `issuer` is the signing side: `{ key }` alone issues a self-signed certificate
266
266
  * (issuer = subject, signed with the subject's own key); `{ name, publicKey, key }` or `{ cert, key }`
267
- * issues a CA-signed one. The signature algorithm is resolved from the signing key: RSA (PKCS#1 v1.5
267
+ * issues a CA-signed one. The signing key `key` is a WebCrypto `CryptoKey` -- a `pki.key.generate`
268
+ * private key passed directly, without exporting -- or a PKCS#8 private key as DER (`Buffer`) or PEM
269
+ * (`string`); a public or secret CryptoKey, or a `node:crypto` KeyObject, is refused. A `subjectAltName`
270
+ * (or any `GeneralName`) entry may be a form object (`{ dNSName: "..." }`) or a bare string classified
271
+ * fail-closed into its form (`"host.example"`, `"a@b.example"`, `"10.0.0.1"`, `"https://host.example/"`).
272
+ * The signature algorithm is resolved from the signing key: RSA (PKCS#1 v1.5
268
273
  * or PSS via `opts.pss`), ECDSA, EdDSA, ML-DSA, SLH-DSA, or a composite arm, so every algorithm the
269
274
  * toolkit signs with is available here without a per-algorithm branch.
270
275
  *
@@ -283,12 +288,11 @@ function _hasCriticalSan(extSpec) {
283
288
  * @example
284
289
  * var pair = await pki.key.generate("Ed25519");
285
290
  * var signerSpki = await pki.key.export(pair.publicKey);
286
- * var signerKeyPkcs8 = await pki.key.export(pair.privateKey);
287
291
  * var root = await pki.x509.sign(
288
292
  * { subject: "Example Root CA", subjectPublicKey: signerSpki,
289
293
  * notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z"),
290
294
  * extensions: { basicConstraints: { cA: true }, keyUsage: ["keyCertSign", "cRLSign"], subjectKeyIdentifier: true } },
291
- * { key: signerKeyPkcs8 });
295
+ * { key: pair.privateKey }); // a WebCrypto CryptoKey signs directly (or pass a PKCS#8 DER Buffer / PEM string)
292
296
  * pki.schema.x509.parse(root).subject.dn; // "CN=Example Root CA"
293
297
  */
294
298
  function sign(spec, issuer, opts) {
@@ -324,7 +328,7 @@ function _sign(spec, issuer, opts) {
324
328
  : "explicit form; the issuer takes { key, name, publicKey }, or { key, cert } to take both from a certificate");
325
329
  });
326
330
  guard.identifier.assertKnownKeys(opts, KNOWN_SIGN_OPTS, _err, "x509/bad-input", "pki.x509.sign has an unknown option ");
327
- if (issuer.key == null) throw _err("x509/bad-input", "a signing key (issuer.key, a PKCS#8 private key) is required");
331
+ if (issuer.key == null) throw _err("x509/bad-input", "a signing key (issuer.key, a WebCrypto CryptoKey or a PKCS#8 private key DER/PEM) is required");
328
332
 
329
333
  var spki = _reqDer(spec.subjectPublicKey, "spec.subjectPublicKey (the SPKI DER of the certified key)");
330
334
  _assertValidSpki(spki, "spec.subjectPublicKey");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blamejs/pki",
3
- "version": "0.5.30",
3
+ "version": "0.5.32",
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:a82d39df-dc02-419e-86bb-787363fa4d07",
5
+ "serialNumber": "urn:uuid:e0315ce7-f8af-453b-be94-cae61e3f47ea",
6
6
  "version": 1,
7
7
  "metadata": {
8
- "timestamp": "2026-08-24T19:49:42.857Z",
8
+ "timestamp": "2026-08-28T18:08:22.906Z",
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.5.30",
22
+ "bom-ref": "@blamejs/pki@0.5.32",
23
23
  "type": "application",
24
24
  "name": "pki",
25
- "version": "0.5.30",
25
+ "version": "0.5.32",
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.5.30",
29
+ "purl": "pkg:npm/%40blamejs/pki@0.5.32",
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.5.30",
57
+ "ref": "@blamejs/pki@0.5.32",
58
58
  "dependsOn": []
59
59
  }
60
60
  ]