@blamejs/pki 0.8.61 → 0.9.1

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 (51) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/MIGRATING.md +46 -0
  3. package/README.md +8 -1
  4. package/index.js +55 -26
  5. package/lib/acme.js +5 -5
  6. package/lib/asn1-der.js +1 -1
  7. package/lib/attrcert-sign.js +2 -2
  8. package/lib/cbor-det.js +4 -1
  9. package/lib/cmc-build.js +1 -1
  10. package/lib/cmp-build.js +5 -5
  11. package/lib/cmp-session.js +6 -3
  12. package/lib/cmp-verify.js +1 -1
  13. package/lib/cms-compress.js +5 -2
  14. package/lib/cms-decrypt.js +2 -2
  15. package/lib/cms-encrypt.js +2 -2
  16. package/lib/cms-sign.js +2 -2
  17. package/lib/constants.js +3 -0
  18. package/lib/crl-sign.js +3 -3
  19. package/lib/crmf-sign.js +1 -1
  20. package/lib/csr-sign.js +1 -1
  21. package/lib/ct.js +14 -13
  22. package/lib/est.js +101 -29
  23. package/lib/framework-error.js +1 -1
  24. package/lib/guard-bytes.js +1 -1
  25. package/lib/guard-limits.js +3 -2
  26. package/lib/hpke.js +2 -2
  27. package/lib/http-retry-after.js +4 -1
  28. package/lib/http-transport.js +6 -6
  29. package/lib/inspect.js +9 -4
  30. package/lib/jose.js +3 -3
  31. package/lib/merkle.js +4 -4
  32. package/lib/mime.js +124 -17
  33. package/lib/ocsp.js +2 -2
  34. package/lib/path-validate.js +3 -3
  35. package/lib/pbes2.js +8 -4
  36. package/lib/pkcs12-build.js +5 -5
  37. package/lib/scep.js +1 -1
  38. package/lib/schema-engine.js +12 -12
  39. package/lib/schema-pkix.js +12 -12
  40. package/lib/sign-scheme.js +2 -2
  41. package/lib/sigstore.js +1 -1
  42. package/lib/smime.js +11 -12
  43. package/lib/tlog.js +2 -2
  44. package/lib/tls-cert-compress.js +3 -3
  45. package/lib/tsp-sign.js +6 -6
  46. package/lib/tuf.js +3 -3
  47. package/lib/webauthn-mds.js +1 -1
  48. package/lib/webauthn.js +1 -1
  49. package/lib/x509-sign.js +3 -3
  50. package/package.json +1 -1
  51. package/sbom.cdx.json +6 -6
package/lib/constants.js CHANGED
@@ -189,6 +189,9 @@ var LIMITS = Object.assign(Object.create(null), {
189
189
  PBKDF2_MAX_SALT: 1024,
190
190
  MIME_MAX_BYTES: BYTES.mib(16),
191
191
  HEADER_LINE_MAX_OCTETS: 998,
192
+ MIME_MAX_PARTS: 1024,
193
+ MIME_MAX_HEADERS: 1024,
194
+ MIME_MAX_PARAMS: 256,
192
195
  COMPRESS_MAX_BYTES: BYTES.mib(16),
193
196
  TLS_CERT_MSG_MAX_BYTES: BYTES.mib(16) - 1,
194
197
  TLS_CERT_MAX_ENTRIES: 100,
package/lib/crl-sign.js CHANGED
@@ -240,7 +240,7 @@ function _buildCrlExtensions(spec, ctx) {
240
240
  var crlNumberVal = null;
241
241
  if (spec.crlNumber != null) { var cn = _boundedInteger(spec.crlNumber, "cRLNumber"); crlNumberVal = cn.value; push("cRLNumber", false, cn.tlv); }
242
242
  var ext = spec.extensions;
243
- if (ext == null) ext = {};
243
+ if (ext == null) ext = intrinsic.create(null);
244
244
  if (Array.isArray(ext)) {
245
245
  var arrBase = null, arrCrlNum = null;
246
246
  /** @internal The walk goes through `guard.list.copyMap`, never the caller's own `forEach`. Reached
@@ -391,9 +391,9 @@ function _assertIssuerCanSignCrl(issuerCert) {
391
391
  }
392
392
 
393
393
  function _sign(spec, issuer, opts) {
394
- opts = opts || {};
394
+ opts = opts || intrinsic.create(null);
395
395
  if (!spec || typeof spec !== "object" || Buffer.isBuffer(spec)) throw _err("crl/bad-input", "the CRL spec must be an object");
396
- issuer = issuer || {};
396
+ issuer = issuer || intrinsic.create(null);
397
397
  var byCert = issuer.cert != null, byName = issuer.name != null;
398
398
  guard.identifier.assertKnownKeys(spec, byCert || byName ? KNOWN_SPEC_KEYS_ISSUER_NAMED : KNOWN_SPEC_KEYS,
399
399
  _err, "crl/bad-input", function (k) {
package/lib/crmf-sign.js CHANGED
@@ -1215,7 +1215,7 @@ function _buildCertReqMsg(spec, key, opts) {
1215
1215
  }
1216
1216
 
1217
1217
  function _build(spec, key, opts) {
1218
- opts = opts || {};
1218
+ opts = opts || intrinsic.create(null);
1219
1219
  if (!spec || typeof spec !== "object" || _isBuffer(spec)) throw _err("crmf/bad-input", "the certificate-request-message spec must be an object");
1220
1220
  var specs;
1221
1221
  if (spec.messages != null) {
package/lib/csr-sign.js CHANGED
@@ -209,7 +209,7 @@ function sign(spec, key, opts) {
209
209
  }
210
210
 
211
211
  function _sign(spec, key, opts) {
212
- opts = opts || {};
212
+ opts = opts || intrinsic.create(null);
213
213
  if (!spec || typeof spec !== "object" || Buffer.isBuffer(spec)) throw _err("csr/bad-input", "the certification-request spec must be an object");
214
214
  guard.identifier.assertKnownKeys(spec, KNOWN_SPEC_KEYS, _err, "csr/bad-input", "unknown spec field ");
215
215
  guard.identifier.assertKnownKeys(opts, KNOWN_SIGN_OPTS, _err, "csr/bad-input", "pki.csr.sign has an unknown option ");
package/lib/ct.js CHANGED
@@ -40,6 +40,7 @@
40
40
  */
41
41
 
42
42
  var asn1 = require("./asn1-der.js");
43
+ var _create = require("./guard-intrinsic").create;
43
44
  var constants = require("./constants.js");
44
45
  var frameworkError = require("./framework-error.js");
45
46
  var guard = require("./guard-all.js");
@@ -357,7 +358,7 @@ function _u24Bytes(n) {
357
358
  var _X509_ENTRY_KEYS = intrinsic.assign(intrinsic.create(null), { entryType: 1, leafCert: 1 });
358
359
  var _PRECERT_ENTRY_KEYS = intrinsic.assign(intrinsic.create(null), { entryType: 1, tbsCertificate: 1, issuerKeyHash: 1 });
359
360
  function reconstructSignedData(entry, sct) {
360
- entry = entry || {};
361
+ entry = entry || _create(null);
361
362
  if (typeof entry !== "object") throw new CtError("ct/bad-input", "the log entry must be an object { entryType, ... } (RFC 6962 sec. 3.1)");
362
363
  var entryType = entry.entryType;
363
364
  if (entryType !== 0 && entryType !== 1) {
@@ -493,7 +494,7 @@ async function verifySct(entry, sct, logPublicKey) {
493
494
  throw new CtError("ct/log-id-mismatch", "the SCT logId does not match SHA-256 of the provided log key (RFC 6962 sec. 3.2)");
494
495
  }
495
496
  }
496
- var sigInfo = rec.signatureAlgorithm || {};
497
+ var sigInfo = rec.signatureAlgorithm || _create(null);
497
498
  var hashName = CT_HASH[sigInfo.hashName];
498
499
  if (!hashName) throw new CtError("ct/unsupported-algorithm", "unsupported SCT hash algorithm " + JSON.stringify(sigInfo.hashName) + " (RFC 6962 sec. 2.1.4 mandates sha256)");
499
500
  var alg = _spkiAlg(spki);
@@ -899,7 +900,7 @@ function _classifyLog(sct, logList, entry, opts) {
899
900
  }
900
901
 
901
902
  async function verifySctWithLogList(entry, sct, logList, opts) {
902
- opts = opts || {};
903
+ opts = opts || _create(null);
903
904
  var c = _classifyLog(sct, logList, entry, opts);
904
905
  if (c.status !== "ok") throw _ctErr(c.code, c.message);
905
906
  /** @internal Read before the verify, so the timestamp the verdict states is the one the signature covered.
@@ -1249,7 +1250,7 @@ function _assertTransport(opts) {
1249
1250
  * `Symbol.split` is part of that operation. The token decides which parser a log's response body is
1250
1251
  * handed to. */
1251
1252
  function _fetchCtypeToken(headers) {
1252
- var raw = intrinsic.String((headers || {})["content-type"] || "");
1253
+ var raw = intrinsic.String((headers || _create(null))["content-type"] || "");
1253
1254
  var parts = intrinsic.splitChar(raw, 59);
1254
1255
  return _toLowerCase(_ctTrim(parts.length ? parts[0] : ""));
1255
1256
  }
@@ -1279,7 +1280,7 @@ function _fetchBody(transport, url, req, label) {
1279
1280
  if (req.body !== undefined) request.body = req.body;
1280
1281
  return transport(request);
1281
1282
  }).then(function (res) {
1282
- res = res || {};
1283
+ res = res || _create(null);
1283
1284
  var h = intrinsic.create(null);
1284
1285
  guard.identifier.optionNames(res.headers).forEach(function (k) { h[k.toLowerCase()] = res.headers[k]; });
1285
1286
  var body = guard.bytes.isByteSource(res.body)
@@ -1581,7 +1582,7 @@ function _uintField(obj, name, code, label) {
1581
1582
  var _STH_KEYS = intrinsic.assign(intrinsic.create(null),
1582
1583
  { timestamp: 1, treeSize: 1, rootHash: 1, signature: 1, raw: 1, then: 1 });
1583
1584
  function sthSignedData(sth) {
1584
- sth = sth || {};
1585
+ sth = sth || _create(null);
1585
1586
  guard.identifier.assertKnownKeys(sth, _STH_KEYS, _ctErr, "ct/bad-input", "unknown sth field ");
1586
1587
  var ts = guard.range.uint64(sth.timestamp, _ctErr, "ct/bad-input", "sth.timestamp");
1587
1588
  var size = guard.range.uint64(sth.treeSize, _ctErr, "ct/bad-input", "sth.treeSize");
@@ -1627,7 +1628,7 @@ function sthSignedData(sth) {
1627
1628
  * // -> true
1628
1629
  */
1629
1630
  async function verifySth(sth, logKey) {
1630
- sth = sth || {};
1631
+ sth = sth || _create(null);
1631
1632
  var signature = _toBuffer(sth.signature, "sth.signature");
1632
1633
  var signedData = sthSignedData({ timestamp: sth.timestamp, treeSize: sth.treeSize, rootHash: sth.rootHash });
1633
1634
  return await _verifyDigitallySigned(signedData, signature, logKey, "the signed tree head");
@@ -1668,7 +1669,7 @@ async function verifySth(sth, logKey) {
1668
1669
  * sth.treeSize; // -> 3n
1669
1670
  */
1670
1671
  async function getSth(opts) {
1671
- var door = _clientDoor(opts || {}, "logKey");
1672
+ var door = _clientDoor(opts || _create(null), "logKey");
1672
1673
  opts = door.o;
1673
1674
  /** @internal ONE read of the option, and the presence check and the copy both decide from it. Reading
1674
1675
  * it twice let an accessor answer the two separately: a non-null value for the check and an
@@ -1727,7 +1728,7 @@ async function getSth(opts) {
1727
1728
  * p.verified; // -> true
1728
1729
  */
1729
1730
  async function getProofByHash(opts) {
1730
- var door = _clientDoor(opts || {}, null);
1731
+ var door = _clientDoor(opts || _create(null), null);
1731
1732
  opts = door.o;
1732
1733
  var sth = _assertSth(opts.sth, "opts.sth");
1733
1734
  /** @internal Copied for the reason the tree head is: it is sent on the wire and then folded against the
@@ -1783,7 +1784,7 @@ async function getProofByHash(opts) {
1783
1784
  * c.verified; // -> true
1784
1785
  */
1785
1786
  async function getSthConsistency(opts) {
1786
- var door = _clientDoor(opts || {}, null);
1787
+ var door = _clientDoor(opts || _create(null), null);
1787
1788
  opts = door.o;
1788
1789
  var oldSth = _assertSth(opts.oldSth, "opts.oldSth");
1789
1790
  var newSth = _assertSth(opts.newSth, "opts.newSth");
@@ -1841,7 +1842,7 @@ async function getSthConsistency(opts) {
1841
1842
  * e.entries[0].leafInput; // -> <Buffer ...>
1842
1843
  */
1843
1844
  async function getEntries(opts) {
1844
- var door = _clientDoor(opts || {}, null);
1845
+ var door = _clientDoor(opts || _create(null), null);
1845
1846
  opts = door.o;
1846
1847
  var start = guard.range.uint64(opts.start, _ctErr, "ct/bad-input", "opts.start");
1847
1848
  var end = guard.range.uint64(opts.end, _ctErr, "ct/bad-input", "opts.end");
@@ -1899,7 +1900,7 @@ async function getEntries(opts) {
1899
1900
  */
1900
1901
  async function getRoots(opts) {
1901
1902
  /** @internal This message carries no options of its own, so the door's record is all that is used. */
1902
- var door = _clientDoor(opts || {}, null);
1903
+ var door = _clientDoor(opts || _create(null), null);
1903
1904
  var doc = await _clientJson(door, "get-roots", "roots");
1904
1905
  return guard.verdict.of({ certificates: _b64Array(doc, "certificates", "ct/bad-roots", "roots", undefined, C.LIMITS.CT_MAX_ROOTS) });
1905
1906
  }
@@ -1982,7 +1983,7 @@ async function addPreChain(opts) {
1982
1983
  * The chain encoding, the response shape, and the refusal when the receipt does not verify are one
1983
1984
  * rule stated once. */
1984
1985
  async function _addChain(opts, message, entryType) {
1985
- var door = _clientDoor(opts || {}, "logKey");
1986
+ var door = _clientDoor(opts || _create(null), "logKey");
1986
1987
  opts = door.o;
1987
1988
  /** @internal ONE read of the option, used by the presence check here and by the copy below. Reading it
1988
1989
  * twice let an accessor answer the two separately, presenting a non-null value to the check and an
package/lib/est.js CHANGED
@@ -281,8 +281,14 @@ function _matchDelimAt(text, ls, boundary) {
281
281
  if (_charCodeAt(text, q) === 0x0d && q + 1 < n && _charCodeAt(text, q + 1) === 0x0a) return { end: q + 2, close: close };
282
282
  return null;
283
283
  }
284
+ /** @internal The delimiter count is bounded before the list is built, since one mark is appended per
285
+ * delimiter found anywhere in the body and the scan does not stop at the terminal one. The bound is
286
+ * the part cap plus that terminal delimiter, and it is the same `C.LIMITS` row the MIME reader
287
+ * counts parts against, so the two doors hold a multipart body to one number. */
284
288
  function _findMultipartMarks(text, boundary) {
285
289
  var marks = [], n = text.length, searchStart = 0, p;
290
+ var counted = guard.limits.counter(constants.LIMITS.MIME_MAX_PARTS, E, "est/bad-multipart",
291
+ "the multipart body part");
286
292
  while (searchStart <= n) {
287
293
  var found = null, foundAt = -1;
288
294
  for (p = searchStart; p <= n; p++) {
@@ -293,7 +299,15 @@ function _findMultipartMarks(text, boundary) {
293
299
  if (lsN !== -1) { var r1 = _matchDelimAt(text, lsN, boundary); if (r1) { found = r1; foundAt = p; break; } }
294
300
  }
295
301
  if (!found) break;
302
+ /** @internal The closing delimiter opens no part, so it is not charged and the cap counts the
303
+ * parts an operator is told it counts. */
304
+ if (!found.close) counted.tick();
296
305
  guard.list.append(marks, { at: foundAt, bodyStart: found.end, close: found.close });
306
+ /** @internal RFC 2046 sec. 5.1.1 discards everything after the closing delimiter, and the caller
307
+ * reads no mark past the first one that closes, so the scan stops there. Running on charged the
308
+ * epilogue against the part budget: a body at the cap was refused for text it never reads, while
309
+ * the same body without that epilogue was accepted. */
310
+ if (found.close) break;
297
311
  searchStart = found.end === foundAt ? found.end + 1 : found.end;
298
312
  }
299
313
  return marks;
@@ -314,18 +328,29 @@ function _stripOneTrailingEol(seg, end) {
314
328
  }
315
329
  return end;
316
330
  }
317
- function _unfoldHeaders(raw) {
318
- var out = "", last = 0, i = 0, n = raw.length;
331
+ /** @internal Every line break in the part's header area is charged as the scan passes it, before the
332
+ * unfolded text is split, so the refusal costs the cap rather than the input. Counting the split
333
+ * lines afterwards instead read 2,000,000 one-line headers in 425 ms and 172 MiB of resident memory
334
+ * before refusing, and never reached two on 1,500,000 continuation lines, which fold into one line. */
335
+ function _unfoldHeaders(raw, counted) {
336
+ var out = "", last = 0, i = 0, n = raw.length, afterLastBreak = 0;
319
337
  while (i < n) {
320
338
  var nlLen = 0;
321
339
  if (_charCodeAt(raw, i) === 0x0d && i + 1 < n && _charCodeAt(raw, i + 1) === 0x0a) nlLen = 2;
322
340
  else if (_charCodeAt(raw, i) === 0x0a) nlLen = 1;
323
- if (nlLen > 0) {
324
- var after = i + nlLen;
325
- if (after < n && (_charCodeAt(raw, after) === 0x20 || _charCodeAt(raw, after) === 0x09)) { out += _strSlice(raw, last, i); last = after; i = after; continue; }
326
- }
327
- i += 1;
341
+ if (nlLen === 0) { i += 1; continue; }
342
+ /** @internal The scan steps PAST the whole break rather than one byte into it, so a CRLF is one
343
+ * line and not two: stepping one byte left the LF to be read as a second break on the next pass,
344
+ * which charged every CRLF twice and halved the cap a part was actually held to. */
345
+ counted.tick();
346
+ var after = i + nlLen;
347
+ afterLastBreak = after;
348
+ if (after < n && (_charCodeAt(raw, after) === 0x20 || _charCodeAt(raw, after) === 0x09)) { out += _strSlice(raw, last, i); last = after; i = after; continue; }
349
+ i = after;
328
350
  }
351
+ /** @internal The run after the last break is a line as well, and a header area that ends at a break
352
+ * has no run after it, so the charge is the LINE count the cap is stated in and not one less. */
353
+ if (afterLastBreak < n) counted.tick();
329
354
  return out + _strSlice(raw, last, n);
330
355
  }
331
356
  function _splitCrlf(s) {
@@ -359,7 +384,9 @@ function splitMultipartMixed(body, contentType) {
359
384
  var rawHeaders = _strSlice(seg, 0, sep);
360
385
  var partBody = _strSlice(seg, _stripTwoLeadingEol(seg, sep), _stripOneTrailingEol(seg, seg.length));
361
386
  var headers = intrinsic.create(null);
362
- var lines = _splitCrlf(_unfoldHeaders(rawHeaders));
387
+ var countedHeaders = guard.limits.counter(constants.LIMITS.MIME_MAX_HEADERS, E, "est/bad-multipart",
388
+ "the multipart part header line");
389
+ var lines = _splitCrlf(_unfoldHeaders(rawHeaders, countedHeaders));
363
390
  for (var li = 0; li < lines.length; li++) {
364
391
  var line = lines[li];
365
392
  /** @internal A part's header name and value are cut, trimmed and folded through the captured
@@ -367,7 +394,20 @@ function splitMultipartMixed(body, contentType) {
367
394
  * its body goes to: measured, a part stating `application/pkcs8` was read as
368
395
  * `application/pkcs7-mime` with a `smime-type` of `certs-only`. */
369
396
  var col = _estIndexOf(line, ":");
370
- if (col > 0) headers[_toLowerCase(_estTrim(_strSlice(line, 0, col)))] = _estTrim(_strSlice(line, col + 1));
397
+ if (col <= 0) continue;
398
+ var fname = _toLowerCase(_estTrim(_strSlice(line, 0, col)));
399
+ /** @internal A repeat is refused rather than overwritten. Storing each value in turn left the
400
+ * LAST line deciding the field, and the field this decides is the part's own content type,
401
+ * which selects the reader its body goes to: measured, the same two Content-Type lines swapped
402
+ * reported `application/pkcs7-mime; smime-type=certs-only` one way and `application/pkcs8` the
403
+ * other, which is which part is the private key and which is the certificate. RFC 5322 sec. 3.6
404
+ * gives a singleton field one occurrence, and taking the first is as conforming as taking the
405
+ * last, so there is no value here to resolve to. */
406
+ if (intrinsic.hasOwn(headers, fname)) {
407
+ throw E("est/bad-multipart", "a multipart part declares the " + guard.text.showValue(fname) +
408
+ " header field more than once, so the value it resolves to is ambiguous (RFC 5322 sec. 3.6)");
409
+ }
410
+ headers[fname] = _estTrim(_strSlice(line, col + 1));
371
411
  }
372
412
  var partCt = headers["content-type"] || "";
373
413
  if (_ciStartsWith(partCt, "MULTIPART/")) throw E("est/bad-multipart", "a nested multipart part is not permitted");
@@ -471,36 +511,58 @@ function _recipientMatches(recipientInfos, opts) {
471
511
  * replacement decides which segments a parameter is then looked for in, and one of those parameters
472
512
  * is the multipart boundary a response body is divided on: measured, a `boundary=wire` header
473
513
  * yielded `forged`. */
514
+ /** @internal The first segment is the media type rather than a parameter, so it is not charged, and
515
+ * the charge comes BEFORE the segment is stored, so a refusal costs the cap and not the input: with
516
+ * no cap here a 3.8 MiB Content-Type carrying 1,000,000 parameters was accepted in 194 ms and 100 MiB
517
+ * of resident memory, and a part's media type splits the same value a second time. The bound is the
518
+ * `C.LIMITS` row the MIME reader counts parameters against, so both doors hold one media type to one
519
+ * number. */
520
+ function _pushCtSegment(segs, seg, counted) {
521
+ if (segs.length > 0) counted.tick();
522
+ intrinsic.append(segs, seg);
523
+ }
524
+
474
525
  function _splitContentTypeParams(s) {
475
526
  var segs = [], cur = "", inQuote = false;
527
+ var counted = guard.limits.counter(constants.LIMITS.MIME_MAX_PARAMS, E, "est/bad-multipart",
528
+ "the Content-Type parameter");
476
529
  for (var i = 0; i < s.length; i++) {
477
530
  var ch = _estCharAt(s, i);
478
531
  if (ch === '"') { inQuote = !inQuote; cur += ch; }
479
- else if (ch === ";" && !inQuote) { intrinsic.append(segs, cur); cur = ""; }
532
+ else if (ch === ";" && !inQuote) { _pushCtSegment(segs, cur, counted); cur = ""; }
480
533
  else cur += ch;
481
534
  }
482
- intrinsic.append(segs, cur);
535
+ _pushCtSegment(segs, cur, counted);
483
536
  return segs;
484
537
  }
485
538
 
486
539
  function _ctParam(contentType, name) {
487
540
  var segs = _splitContentTypeParams(_stringOf(contentType || ""));
488
- var value = null, count = 0;
541
+ var value = null, plain = 0, starred = 0;
489
542
  /** @internal Each parameter is read through the captured operations. The name decides which
490
543
  * parameter this is, and the value it returns is the one a caller acts on: for the RFC 7030
491
544
  * multipart boundary, it is where a response body is split. */
492
545
  for (var i = 1; i < segs.length; i++) {
493
546
  var seg = segs[i];
494
547
  var eq = _estIndexOf(seg, "=");
495
- if (eq === -1) continue;
496
- if (_toLowerCase(_estTrim(_strSlice(seg, 0, eq))) !== name) continue;
497
- count++;
498
- if (count > 1) continue;
548
+ /** @internal An occurrence counts whether or not it carries a value, which is why a bare
549
+ * attribute is counted: skipping it let `boundary; boundary="b"` through while
550
+ * `boundary="a"; boundary="b"` was refused. The plain and the starred RFC 2231 spellings are
551
+ * counted apart, because what has to be refused is two readings carrying DIFFERENT values.
552
+ * `boundary*=us-ascii''DECOY; boundary=REAL` is read here as REAL and by an RFC 2231 reader as
553
+ * DECOY, while continuation segments alone leave this reader no boundary at all, which the
554
+ * caller already refuses (RFC 2231 sec. 3, sec. 4, sec. 7). */
555
+ var attr = _toLowerCase(_estTrim(eq === -1 ? seg : _strSlice(seg, 0, eq)));
556
+ var star = _estIndexOf(attr, "*");
557
+ if ((star > 0 ? _strSlice(attr, 0, star) : attr) !== name) continue;
558
+ if (star > 0) { starred++; continue; }
559
+ plain++;
560
+ if (eq === -1 || plain > 1) continue;
499
561
  var val = _estTrim(_strSlice(seg, eq + 1));
500
562
  if (val.length >= 2 && _estCharAt(val, 0) === '"' && _estCharAt(val, val.length - 1) === '"') val = _strSlice(val, 1, -1);
501
563
  value = val;
502
564
  }
503
- return { value: value, duplicated: count > 1 };
565
+ return { value: value, duplicated: plain > 1 || (plain > 0 && starred > 0) };
504
566
  }
505
567
 
506
568
  var _MT_FIRST = pkix.charTable("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789");
@@ -533,7 +595,7 @@ function _partMediaType(contentType) {
533
595
  }
534
596
 
535
597
  function parseServerKeygenResponse(body, contentType, opts) {
536
- opts = opts || {};
598
+ opts = opts || intrinsic.create(null);
537
599
  opts = _knownOpts(opts, PARSE_SERVERKEYGEN_OPTS, "parseServerKeygenResponse");
538
600
  var parts = splitMultipartMixed(body, contentType);
539
601
  if (parts.length !== 2) throw E("est/bad-multipart", "a serverkeygen response must have exactly two parts (RFC 7030 sec. 4.4.2)");
@@ -617,7 +679,7 @@ function classifyResponse(status, headers, body, opts) {
617
679
  }
618
680
 
619
681
  function _classifyResponse(status, headers, body, opts) {
620
- opts = opts || {};
682
+ opts = opts || intrinsic.create(null);
621
683
  opts = _knownOpts(opts, CLASSIFY_OPTS, "classifyResponse");
622
684
  var op = opts.op;
623
685
  if (op !== undefined && op !== null && CLASSIFIABLE_OPS.indexOf(op) === -1) throw E("est/unsupported-operation", "unrecognized EST operation " + guard.text.showValue(op));
@@ -689,7 +751,7 @@ function _classifyResponse(status, headers, body, opts) {
689
751
  * // -> "https://ca.example/.well-known/est/cacerts"
690
752
  */
691
753
  function paths(baseUrl, opts) {
692
- opts = opts || {};
754
+ opts = opts || intrinsic.create(null);
693
755
  opts = _knownOpts(opts, PATHS_OPTS, "paths");
694
756
  var base = String(baseUrl);
695
757
  var prefix = base.slice(0, _stripTrailingSlashEnd(base)) + "/.well-known/est";
@@ -957,7 +1019,7 @@ function _drive(method, url, body, headers, opts, transport, budgets) {
957
1019
  function step() {
958
1020
  var hopBody = (typeof body === "function" && budgets.onAttempt) ? budgets.onAttempt() : body;
959
1021
  return guard.async.invoked(transport, [{ method: method, url: _urlHref(url), headers: _headersFor(url), body: hopBody, tls: _tlsFor(url), proxy: budgets.proxy, timeout: budgets.timeout, maxResponseBytes: budgets.maxResponseBytes }], E, "est/bad-input", "opts.transport").then(function (res) {
960
- res = res || {};
1022
+ res = res || intrinsic.create(null);
961
1023
  var blen = _bodyLen(res);
962
1024
  if (blen > budgets.maxResponseBytes) throw E("est/response-too-large", "the response body (" + blen + " bytes) exceeds the " + budgets.maxResponseBytes + "-byte cap (RFC 7030 sec. 6)");
963
1025
  var h = intrinsic.assign(intrinsic.create(null), {
@@ -1159,7 +1221,7 @@ function _certsResult(op, res, opts, csrSpki) {
1159
1221
  * r.retry && r.retryAfterSeconds; // 60
1160
1222
  */
1161
1223
  function fullcmc(baseUrl, request, opts) {
1162
- opts = opts || {};
1224
+ opts = opts || intrinsic.create(null);
1163
1225
  var der, wanted, sent;
1164
1226
  try {
1165
1227
  if (typeof opts !== "object" || Buffer.isBuffer(opts)) throw E("est/bad-input", "pki.est.fullcmc options must be an object");
@@ -1588,7 +1650,7 @@ function _enroll(op, baseUrl, csrInput, opts, proxySnap) {
1588
1650
  * r.certificates; // -> [Buffer, ...] raw, unordered
1589
1651
  */
1590
1652
  function cacerts(baseUrl, opts) {
1591
- opts = opts || {};
1653
+ opts = opts || intrinsic.create(null);
1592
1654
  var proxySnap; try { proxySnap = _snapProxy(opts); } catch (_pe) { return _rej(E("est/bad-input", "the options object could not be read", _pe)); }
1593
1655
  return _promiseResolve(_Promise, undefined).then(function () {
1594
1656
  opts = _knownOpts(opts, CACERTS_OPTS, "cacerts");
@@ -1654,7 +1716,7 @@ function cacerts(baseUrl, opts) {
1654
1716
  * bound.retry; // true
1655
1717
  */
1656
1718
  function simpleenroll(baseUrl, csrInput, opts) {
1657
- opts = opts || {};
1719
+ opts = opts || intrinsic.create(null);
1658
1720
  var proxySnap; try { proxySnap = _snapProxy(opts); } catch (_pe) { return _rej(E("est/bad-input", "the options object could not be read", _pe)); }
1659
1721
  return _promiseResolve(_Promise, undefined).then(function () {
1660
1722
  opts = _knownOpts(opts, SIMPLEENROLL_OPTS, "simpleenroll");
@@ -1694,7 +1756,7 @@ function simpleenroll(baseUrl, csrInput, opts) {
1694
1756
  * r.retry; // true
1695
1757
  */
1696
1758
  function simplereenroll(baseUrl, csrInput, opts) {
1697
- opts = opts || {};
1759
+ opts = opts || intrinsic.create(null);
1698
1760
  var proxySnap; try { proxySnap = _snapProxy(opts); } catch (_pe) { return _rej(E("est/bad-input", "the options object could not be read", _pe)); }
1699
1761
  return _promiseResolve(_Promise, undefined).then(function () {
1700
1762
  opts = _knownOpts(opts, SIMPLEREENROLL_OPTS, "simplereenroll");
@@ -1751,7 +1813,7 @@ function _assertConfidentialCipher(res) {
1751
1813
  }
1752
1814
 
1753
1815
  function _ciHeaderList(headers, name) {
1754
- headers = headers || {};
1816
+ headers = headers || intrinsic.create(null);
1755
1817
  /** @internal The header NAME a value is selected under is folded through the captured conversion on
1756
1818
  * both sides of the comparison: the match decides which header this response is read from. */
1757
1819
  var lname = _toLowerCase(name), keys = guard.identifier.optionNames(headers), parts = [];
@@ -1765,7 +1827,7 @@ function _ciHeaderList(headers, name) {
1765
1827
  }
1766
1828
 
1767
1829
  function _ciHeader(headers, name) {
1768
- headers = headers || {};
1830
+ headers = headers || intrinsic.create(null);
1769
1831
  var lname = _toLowerCase(name), keys = guard.identifier.optionNames(headers), found = null, n = 0;
1770
1832
  for (var i = 0; i < keys.length; i++) {
1771
1833
  if (_toLowerCase(keys[i]) !== lname) continue;
@@ -1777,6 +1839,16 @@ function _ciHeader(headers, name) {
1777
1839
  "the response carries more than one " + lname + " header field, so what the body is cannot be told " +
1778
1840
  "from it (RFC 9110 sec. 5.1: field names are case-insensitive)");
1779
1841
  }
1842
+ /** @internal One key can still hold SEVERAL values, which is how `node:http` reports a field that
1843
+ * arrived more than once, and these three fields each carry one value. Counting keys alone left a
1844
+ * two-element array to be read as one string: measured, the two orders of the same pair gave
1845
+ * `est/bad-content-type` one way and an accepted classification the other, because the joined text
1846
+ * parsed as a media type in one order and not in the other. */
1847
+ if (intrinsic.isArray(found) && found.length > 1) {
1848
+ throw E("est/bad-content-type",
1849
+ "the response carries " + found.length + " values for " + lname + ", so what the body is cannot be " +
1850
+ "told from it (RFC 9110 sec. 5.1)");
1851
+ }
1780
1852
  return n === 0 ? null : found;
1781
1853
  }
1782
1854
 
@@ -1854,7 +1926,7 @@ async function _serverkeygenResult(res, opts, derived) {
1854
1926
  * r.retry; // true -- a 202 is surfaced, never slept
1855
1927
  */
1856
1928
  function serverkeygen(baseUrl, csrInput, opts) {
1857
- opts = opts || {};
1929
+ opts = opts || intrinsic.create(null);
1858
1930
  var proxySnap; try { proxySnap = _snapProxy(opts); } catch (_pe) { return _rej(E("est/bad-input", "the options object could not be read", _pe)); }
1859
1931
  return _promiseResolve(_Promise, undefined).then(function () {
1860
1932
  opts = _knownOpts(opts, SERVERKEYGEN_OPTS, "serverkeygen");
@@ -1916,7 +1988,7 @@ function _csrattrsResult(res, opts) {
1916
1988
  * r.available; // false -- a 404 is "no CSR-attributes policy available"
1917
1989
  */
1918
1990
  function csrattrs(baseUrl, opts) {
1919
- opts = opts || {};
1991
+ opts = opts || intrinsic.create(null);
1920
1992
  var proxySnap; try { proxySnap = _snapProxy(opts); } catch (_pe) { return _rej(E("est/bad-input", "the options object could not be read", _pe)); }
1921
1993
  return _promiseResolve(_Promise, undefined).then(function () {
1922
1994
  opts = _knownOpts(opts, CSRATTRS_OPTS, "csrattrs");
@@ -122,7 +122,7 @@ function defineClass(name, opts) {
122
122
  if (typeof name !== "string" || name.length === 0) {
123
123
  throw new TypeError("defineClass: name must be a non-empty string");
124
124
  }
125
- opts = opts || {};
125
+ opts = opts || intrinsic.create(null);
126
126
  var withCause = !!opts.withCause;
127
127
  var flagKey = "is" + name;
128
128
 
@@ -281,7 +281,7 @@ function snapshotSource(input, ErrorClass, code, label) {
281
281
  * the budget rather than the shape of the input. The figure is far above any real caller value: an
282
282
  * options bag or a JWK is tens of nodes, and a certificate chain passed as an anchor list is thousands. */
283
283
  function snapshotDeep(value, ErrorClass, code, label, opts) {
284
- var o = opts || {};
284
+ var o = opts || _create(null);
285
285
  var cap = o.maxDepth == null ? 64 : o.maxDepth;
286
286
  var maxNodes = o.maxNodes == null ? constants.LIMITS.DEEP_COPY_MAX_NODES : o.maxNodes;
287
287
  return _deep(value, ErrorClass, code, label, cap, 0, o.collect || null, { n: 0, max: maxNodes });
@@ -4,6 +4,7 @@
4
4
  // @internal
5
5
 
6
6
  var constants = require("./constants");
7
+ var _create = require("./guard-intrinsic").create;
7
8
  var _isInteger = require("./guard-intrinsic").isInteger;
8
9
  var _freeze = require("./guard-intrinsic").freeze;
9
10
  var _sizeOf = require("./guard-intrinsic").sizeOf;
@@ -15,7 +16,7 @@ function _monotonicNow() { return _Number(_hrtimeBigint() / 1000000n); }
15
16
  // @enforced-by behavioral -- a config-time cap validator has no rename-proof code
16
17
  function cap(value, key, dflt, opts) {
17
18
  if (value === undefined) return dflt;
18
- opts = opts || {};
19
+ opts = opts || _create(null);
19
20
  /** @internal Each bound is read ONCE, into the value the integer check and the comparison share.
20
21
  * Read twice, the check saw one bound and the comparison another, so an `opts` answering a valid
21
22
  * small integer to the check and a large one to the comparison configured a cap that admitted a
@@ -141,7 +142,7 @@ function budget(limits) {
141
142
  */
142
143
  // @enforced-by behavioral -- an elapsed-time comparison has no rename-proof shape that separates a deadline from any other subtraction; the vectors driving a checker to its bound are the guard
143
144
  function deadline(totalMs, opts) {
144
- opts = opts || {};
145
+ opts = opts || _create(null);
145
146
  var rawNow = opts.now;
146
147
  var now = typeof rawNow === "function" ? rawNow : _monotonicNow;
147
148
  if (!_isInteger(totalMs) || totalMs < 0) {
package/lib/hpke.js CHANGED
@@ -923,7 +923,7 @@ function _setupS(ids, pkR, opts) {
923
923
  * the subject being the key rather than the ciphertext. `_suite(ids)` stays above both, by necessity:
924
924
  * which KEM this is decides the width the key is held to. */
925
925
  var r = _recipPublic(suite, pkR);
926
- opts = _options(opts || {}, _SETUP_S_KEYS, "sender setup");
926
+ opts = _options(opts || intrinsic.create(null), _SETUP_S_KEYS, "sender setup");
927
927
  var mode = opts.mode;
928
928
  var kem = suite.kem, k;
929
929
  if (mode === MODE_AUTH || mode === MODE_AUTH_PSK) {
@@ -978,7 +978,7 @@ function _setupR(ids, enc, skR, opts) {
978
978
  * size of whatever was supplied: a 64 MiB value handed where a 32-byte encapsulation belongs was
979
979
  * copied in full and only then refused. */
980
980
  var sealed = _rawKey(enc, _encWidth(suite.kem));
981
- opts = _options(opts || {}, _SETUP_R_KEYS, "recipient setup");
981
+ opts = _options(opts || intrinsic.create(null), _SETUP_R_KEYS, "recipient setup");
982
982
  var mode = opts.mode;
983
983
  var r = _recipPrivate(suite, skR);
984
984
  var kem = suite.kem, ss;
@@ -3,6 +3,9 @@
3
3
  "use strict";
4
4
  // @internal
5
5
 
6
+ /** @internal The record constructor, captured at load, for the reason the same capture in
7
+ * `lib/inspect.js` gives: arming this module counts 48 live runtime reads. */
8
+ var _create = Object.create;
6
9
  var constants = require("./constants");
7
10
  var guard = require("./guard-all");
8
11
 
@@ -100,7 +103,7 @@ function httpDateMs(s, refMs) {
100
103
  }
101
104
 
102
105
  function parse(value, opts) {
103
- opts = opts || {};
106
+ opts = opts || _create(null);
104
107
  var raStr = String(value).trim();
105
108
  var out = { retryAfterSeconds: null, retryAfterDate: null };
106
109
  if (_isAllDigits(raStr)) {
@@ -339,7 +339,7 @@ function _blockedAddrErr(hostname, address) {
339
339
  }
340
340
  function _makeGuardedLookup(lookupFn) {
341
341
  return function guardedLookup(hostname, options, callback) {
342
- lookupFn(hostname, options || {}, function (err, address, family) {
342
+ lookupFn(hostname, options || intrinsic.create(null), function (err, address, family) {
343
343
  if (err) return callback(err);
344
344
  /** @internal Each address is read ONCE, the read is what gets classified, and the SNAPSHOT is
345
345
  * what the socket layer is handed. Forwarding the list the resolver returned let the entry be
@@ -633,7 +633,7 @@ function _proxyErrorClass(e, C) {
633
633
  * res.status; // 200
634
634
  */
635
635
  function httpsTransport(defaults) {
636
- defaults = defaults || {};
636
+ defaults = defaults || intrinsic.create(null);
637
637
  /** @internal Each of these is read ONCE, into the value both the test and the use see, and the
638
638
  * error factory a refusal is built from is one of them. */
639
639
  var rawE = defaults.E;
@@ -789,7 +789,7 @@ function httpsTransport(defaults) {
789
789
  }
790
790
 
791
791
  var _transportFn = function transport(request) {
792
- request = request || {};
792
+ request = request || intrinsic.create(null);
793
793
  var prep;
794
794
  try { prep = _prepare(request); }
795
795
  catch (e) { return _promiseReject(_Promise, e); }
@@ -848,7 +848,7 @@ function httpsTransport(defaults) {
848
848
  /** @internal Captured for consistency with the rest of the file, not because this gates
849
849
  * anything: an understated `content-length` only skips this EARLY refusal, and the cap is
850
850
  * enforced below on the bytes actually counted as they arrive. */
851
- var declared = intrinsic.parseInt((res.headers || {})["content-length"], 10);
851
+ var declared = intrinsic.parseInt((res.headers || intrinsic.create(null))["content-length"], 10);
852
852
  if (intrinsic.numberIsFinite(declared) && declared > prep.maxBytes) { fail(C("response-too-large"), "the declared content-length " + declared + " exceeds the " + prep.maxBytes + "-byte cap (RFC 7030 sec. 6)"); return; }
853
853
  var buf = _bufferAllocUnsafe(0);
854
854
  var len = 0;
@@ -1039,7 +1039,7 @@ function httpsTransport(defaults) {
1039
1039
  * connecting directly while a proxy is configured would reach an origin the caller asked not to
1040
1040
  * reach directly. */
1041
1041
  _transportFn.peerChain = function peerChain(request) {
1042
- request = request || {};
1042
+ request = request || intrinsic.create(null);
1043
1043
  var prep;
1044
1044
  try { prep = _prepare({
1045
1045
  url: request.url,
@@ -1149,6 +1149,6 @@ function httpsTransport(defaults) {
1149
1149
  * pki.schema.x509.parse(channel.peerChain[0]).subject.dn; // the leaf's subject
1150
1150
  * channel.peerCertificate; // the one certificate the endpoint sent
1151
1151
  */
1152
- function peerChain(request, defaults) { return httpsTransport(defaults || {}).peerChain(request); }
1152
+ function peerChain(request, defaults) { return httpsTransport(defaults || intrinsic.create(null)).peerChain(request); }
1153
1153
 
1154
1154
  module.exports = { https: httpsTransport, peerChain: peerChain, isBlockedIp: _isBlockedIp, _makeGuardedLookup: _makeGuardedLookup, MAX_TIMEOUT: MAX_TIMEOUT, snapshotProxy: snapshotProxy };
package/lib/inspect.js CHANGED
@@ -23,6 +23,11 @@
23
23
  * @card Read a certificate like `openssl x509 -text`, in pure JS.
24
24
  */
25
25
 
26
+ /** @internal The record constructor, captured at load. A default record written as `{}` has
27
+ * `Object.prototype`, so every field the caller omitted is read off the prototype chain. This module
28
+ * does not take the guard-intrinsic captures, and requiring them arms it whole: measured, that counts
29
+ * 662 live runtime reads here, which is a migration of its own rather than a line in this change. */
30
+ var _create = Object.create;
26
31
  var frameworkError = require("./framework-error");
27
32
  var constants = require("./constants");
28
33
  var asn1 = require("./asn1-der");
@@ -56,7 +61,7 @@ var OID_UNOTICE = oid.byName("unotice");
56
61
 
57
62
  var HEX = "0123456789abcdef";
58
63
  function _hexColon(buf, opts) {
59
- opts = opts || {};
64
+ opts = opts || _create(null);
60
65
  var hex = [];
61
66
  for (var i = 0; i < buf.length; i++) {
62
67
  var b = buf[i], s = HEX[(b >> 4) & 0xf] + HEX[b & 0xf];
@@ -346,7 +351,7 @@ var EXT_RENDERERS = Object.assign(Object.create(null), {
346
351
  return (decoded || []).map(function (ad) {
347
352
  var m = null;
348
353
  try { m = oid.name(ad.accessMethod); } catch (_e) { /* allow:swallow-unverified display best-effort: an unregistered accessMethod OID falls back to the raw dotted OID below (inspection is best-effort, never a verdict) */ }
349
- var loc = ad.accessLocation || {}, lv;
354
+ var loc = ad.accessLocation || _create(null), lv;
350
355
  if (loc.tag === 6) lv = "URI:" + loc.value;
351
356
  else if (loc.tag === 2) lv = "DNS:" + loc.value;
352
357
  else if (loc.tag === 1) lv = "email:" + loc.value;
@@ -1368,7 +1373,7 @@ function pkcs12Report(input) {
1368
1373
  * structure, and the outer MacData fields then describe nothing: read from there, a store with 2048
1369
1374
  * PBKDF2 iterations reported one iteration, an empty salt, and "pbmac1" as its digest. */
1370
1375
  if (p.mac.pbmac1) {
1371
- var kdf = p.mac.pbmac1.kdf || {};
1376
+ var kdf = p.mac.pbmac1.kdf || _create(null);
1372
1377
  _optLine(L, " ", "Digest", p.mac.pbmac1.schemeName || p.mac.pbmac1.schemeOid);
1373
1378
  _optLine(L, " ", "Key Derivation", kdf.prfName || kdf.prfOid);
1374
1379
  _optLine(L, " ", "Iterations", kdf.iterationCount);
@@ -2363,7 +2368,7 @@ var KNOWN_DUMP_OPTS = Object.assign(Object.create(null),
2363
2368
  * // Incomplete: the walk could not read the value at offset 5: content length 3 overruns the 0 bytes that remain.
2364
2369
  */
2365
2370
  function asn1Dump(input, opts) {
2366
- if (opts === undefined || opts === null) opts = {};
2371
+ if (opts === undefined || opts === null) opts = _create(null);
2367
2372
  if (typeof opts !== "object" || Array.isArray(opts)) {
2368
2373
  throw _err("inspect/bad-input", "pki.inspect.asn1 options must be an object");
2369
2374
  }