@blamejs/pki 0.8.31 → 0.8.49

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 (87) hide show
  1. package/CHANGELOG.md +401 -0
  2. package/README.md +66 -15
  3. package/bin/pki.js +716 -30
  4. package/examples/issue-a-certificate.js +90 -0
  5. package/examples/post-quantum.js +66 -0
  6. package/examples/read-a-certificate.js +56 -0
  7. package/examples/revoke-and-check.js +86 -0
  8. package/examples/sign-and-verify-cms.js +63 -0
  9. package/index.js +18 -5
  10. package/lib/acme.js +223 -74
  11. package/lib/alt-sig.js +441 -0
  12. package/lib/asn1-der.js +48 -25
  13. package/lib/attrcert-sign.js +27 -19
  14. package/lib/cbor-det.js +24 -14
  15. package/lib/cmc-build.js +9 -2
  16. package/lib/cmc-verify.js +14 -3
  17. package/lib/cmp-build.js +17 -5
  18. package/lib/cmp-session.js +24 -11
  19. package/lib/cmp-verify.js +28 -19
  20. package/lib/cms-compress.js +3 -1
  21. package/lib/cms-decrypt.js +102 -7
  22. package/lib/cms-encrypt.js +59 -7
  23. package/lib/cms-sign.js +31 -9
  24. package/lib/cms-verify.js +22 -18
  25. package/lib/composite-kem.js +30 -18
  26. package/lib/composite-sig.js +29 -14
  27. package/lib/constants.js +26 -0
  28. package/lib/crl-sign.js +111 -19
  29. package/lib/crmf-sign.js +167 -15
  30. package/lib/csr-sign.js +117 -6
  31. package/lib/ct.js +1008 -58
  32. package/lib/est.js +29 -16
  33. package/lib/framework-error.js +12 -0
  34. package/lib/guard-async.js +1 -1
  35. package/lib/guard-bytes.js +103 -20
  36. package/lib/guard-crypto.js +205 -3
  37. package/lib/guard-encoding.js +6 -1
  38. package/lib/guard-identifier.js +55 -8
  39. package/lib/guard-intrinsic.js +43 -0
  40. package/lib/guard-json.js +122 -9
  41. package/lib/guard-limits.js +12 -1
  42. package/lib/guard-list.js +63 -8
  43. package/lib/guard-name.js +167 -20
  44. package/lib/guard-range.js +43 -5
  45. package/lib/guard-text.js +41 -0
  46. package/lib/guard-verdict.js +39 -8
  47. package/lib/hpke.js +688 -42
  48. package/lib/http-transport.js +156 -4
  49. package/lib/identity-match.js +4 -4
  50. package/lib/inspect.js +33 -6
  51. package/lib/jose.js +93 -25
  52. package/lib/key.js +18 -13
  53. package/lib/lint.js +165 -1
  54. package/lib/merkle.js +233 -27
  55. package/lib/ocsp.js +27 -14
  56. package/lib/oid.js +14 -4
  57. package/lib/path-validate.js +143 -61
  58. package/lib/pkcs12-build.js +7 -2
  59. package/lib/pki-build.js +31 -7
  60. package/lib/possession.js +575 -0
  61. package/lib/related-cert.js +708 -0
  62. package/lib/revocation-fetch.js +6 -6
  63. package/lib/scep.js +15 -5
  64. package/lib/schema-c509.js +5 -2
  65. package/lib/schema-cmc.js +1 -1
  66. package/lib/schema-cmp.js +8 -8
  67. package/lib/schema-cms.js +2 -15
  68. package/lib/schema-csr.js +69 -0
  69. package/lib/schema-engine.js +10 -3
  70. package/lib/schema-pkix.js +228 -2
  71. package/lib/schema-tsp.js +7 -6
  72. package/lib/sign-scheme.js +22 -9
  73. package/lib/sigstore.js +684 -168
  74. package/lib/sleep.js +8 -1
  75. package/lib/smime.js +22 -1
  76. package/lib/tlog.js +1235 -0
  77. package/lib/tls-cert-compress.js +25 -8
  78. package/lib/tsp-sign.js +120 -35
  79. package/lib/tuf.js +1395 -0
  80. package/lib/validator-tpm.js +4 -1
  81. package/lib/verify-seam.js +7 -1
  82. package/lib/webauthn-mds.js +41 -4
  83. package/lib/webauthn.js +43 -25
  84. package/lib/webcrypto.js +48 -46
  85. package/lib/x509-sign.js +225 -19
  86. package/package.json +2 -1
  87. package/sbom.cdx.json +6 -6
package/lib/est.js CHANGED
@@ -76,6 +76,12 @@ var intrinsic = require("./guard-intrinsic");
76
76
  var _charCodeAt = intrinsic.uncurry(String.prototype.charCodeAt);
77
77
  var _strSlice = intrinsic.uncurry(String.prototype.slice);
78
78
  var _toLowerCase = intrinsic.uncurry(String.prototype.toLowerCase);
79
+ /** @internal Each verb gates its body on a resolved promise and returns its refusals as rejected ones,
80
+ * and both build from the RECEIVER, so reading the global binding at the call decides whether the body
81
+ * runs and whether a refusal stays a refusal. Taken at load. */
82
+ var _Promise = intrinsic.Promise;
83
+ var _promiseResolve = intrinsic.uncurry(intrinsic.promiseResolve);
84
+ var _promiseReject = intrinsic.uncurry(intrinsic.promiseReject);
79
85
  var pkix = require("./schema-pkix");
80
86
  var _hasOwn = intrinsic.hasOwn;
81
87
  var httpTransport = require("./http-transport");
@@ -151,7 +157,7 @@ function _knownOpts(opts, known, verb) {
151
157
  }
152
158
 
153
159
  function _snapProxy(opts) { return httpTransport.snapshotProxy(opts.proxy); }
154
- function _rej(e) { return Promise.reject(e); }
160
+ function _rej(e) { return _promiseReject(_Promise, e); }
155
161
 
156
162
 
157
163
  /**
@@ -568,7 +574,14 @@ var NOT_IMPLEMENTED_OPS = intrinsic.assign(intrinsic.create(null), { fullcmc: 1
568
574
  * var v = pki.est.classifyResponse(202, { "retry-after": "120" }, "", { op: "simpleenroll" });
569
575
  * v.retryAfterSeconds; // -> 120
570
576
  */
577
+ /** @internal Every branch below answers with a fresh classification object, and the sentinel is added
578
+ * at the one exit rather than at each `return`, so a branch added later carries it without being
579
+ * told to. */
571
580
  function classifyResponse(status, headers, body, opts) {
581
+ return guard.verdict.shield(_classifyResponse(status, headers, body, opts));
582
+ }
583
+
584
+ function _classifyResponse(status, headers, body, opts) {
572
585
  opts = opts || {};
573
586
  opts = _knownOpts(opts, CLASSIFY_OPTS, "classifyResponse");
574
587
  var op = opts.op;
@@ -589,22 +602,22 @@ function classifyResponse(status, headers, body, opts) {
589
602
  throw E("est/bad-content-type", "a 200 " + op + " response must carry content-type " + spec.media + (spec.smimeTypes ? "; smime-type=" + spec.smimeTypes.join(" or ") : "") + ", got " + JSON.stringify(ct));
590
603
  }
591
604
  }
592
- return { status: "ok", contentType: ct };
605
+ return guard.verdict.of({ status: "ok", contentType: ct });
593
606
  }
594
607
  if (status === 202) {
595
608
  var ra = h["retry-after"];
596
609
  if (ra === undefined || ra === null || String(ra).trim() === "") throw E("est/missing-retry-after", "an HTTP 202 EST response must include Retry-After (RFC 7030 sec. 4.2.3)");
597
610
  var raStr = String(ra).trim();
598
611
  var parsed = retryAfter.parse(raStr, { now: opts.now, E: E, code: "est/bad-retry-after" });
599
- return { status: "retry", retryAfter: raStr, retryAfterSeconds: parsed.retryAfterSeconds, retryAfterDate: parsed.retryAfterDate };
612
+ return guard.verdict.of({ status: "retry", retryAfter: raStr, retryAfterSeconds: parsed.retryAfterSeconds, retryAfterDate: parsed.retryAfterDate });
600
613
  }
601
- if (status === 501 && NOT_IMPLEMENTED_OPS[op]) return { status: "not-implemented", httpStatus: status };
614
+ if (status === 501 && NOT_IMPLEMENTED_OPS[op]) return guard.verdict.of({ status: "not-implemented", httpStatus: status });
602
615
  if (status === 204 || status === 404) {
603
- if (op === "csrattrs") return { status: "none-available" };
604
- if (status === 404 && NOT_IMPLEMENTED_OPS[op]) return { status: "not-implemented", httpStatus: status };
616
+ if (op === "csrattrs") return guard.verdict.of({ status: "none-available" });
617
+ if (status === 404 && NOT_IMPLEMENTED_OPS[op]) return guard.verdict.of({ status: "not-implemented", httpStatus: status });
605
618
  throw E("est/http-error", "HTTP " + status + " is not a valid " + op + " response");
606
619
  }
607
- if (status >= 300 && status < 400) return { status: "redirect", location: h["location"] || null };
620
+ if (status >= 300 && status < 400) return guard.verdict.of({ status: "redirect", location: h["location"] || null });
608
621
  if (status >= 400) {
609
622
  var text;
610
623
  if (Buffer.isBuffer(body)) {
@@ -617,7 +630,7 @@ function classifyResponse(status, headers, body, opts) {
617
630
  }
618
631
  throw E("est/http-error", "EST server returned HTTP " + status + (text ? ": " + text : ""));
619
632
  }
620
- return { status: "unexpected", httpStatus: status };
633
+ return guard.verdict.of({ status: "unexpected", httpStatus: status });
621
634
  }
622
635
 
623
636
 
@@ -1387,12 +1400,12 @@ function _fullcmcResult(res, opts, wanted, sent) {
1387
1400
  var certs = parseCertsOnly(der);
1388
1401
  var issuedCerts = _correlateIssued(certs.certificates, wanted, "certs-only");
1389
1402
  var issued = issuedCerts[0];
1390
- return { outcome: "issued", certificate: issued, issuedCertificates: issuedCerts,
1403
+ return guard.verdict.of({ outcome: "issued", certificate: issued, issuedCertificates: issuedCerts,
1391
1404
  certificates: certs.certificates, crls: certs.crls,
1392
1405
  controls: [], statuses: [], publishTrustAnchors: null, trusted: false,
1393
1406
  signatureVerified: false,
1394
1407
  bound: { transactionId: false, senderNonce: false, dataReturn: false, bodyPartIDs: false },
1395
- boundToRequest: false };
1408
+ boundToRequest: false });
1396
1409
  }
1397
1410
  return cmcVerify.verify(der, sent).then(function (verdict) {
1398
1411
  if (verdict.outcome !== "issued") return verdict;
@@ -1405,7 +1418,7 @@ function _fullcmcResult(res, opts, wanted, sent) {
1405
1418
  }
1406
1419
 
1407
1420
  function _tryDecodeCmcFault(res, sent) {
1408
- return Promise.resolve().then(function () {
1421
+ return _promiseResolve(_Promise, undefined).then(function () {
1409
1422
  var pt = _partMediaType(_ciHeader(res.headers, "content-type"));
1410
1423
  if (pt.media !== "application/pkcs7-mime") return null;
1411
1424
  if (pt.ambiguous) return null;
@@ -1533,7 +1546,7 @@ function _enroll(op, baseUrl, csrInput, opts, proxySnap) {
1533
1546
  function cacerts(baseUrl, opts) {
1534
1547
  opts = opts || {};
1535
1548
  var proxySnap; try { proxySnap = _snapProxy(opts); } catch (_pe) { return _rej(E("est/bad-input", "the options object could not be read", _pe)); }
1536
- return Promise.resolve().then(function () {
1549
+ return _promiseResolve(_Promise, undefined).then(function () {
1537
1550
  opts = _knownOpts(opts, CACERTS_OPTS, "cacerts");
1538
1551
  return _client("cacerts", "GET", baseUrl, null, { accept: "application/pkcs7-mime" }, opts, proxySnap, undefined, opts.transport);
1539
1552
  }).then(function (res) { return _certsResult("cacerts", res, opts, null); });
@@ -1599,7 +1612,7 @@ function cacerts(baseUrl, opts) {
1599
1612
  function simpleenroll(baseUrl, csrInput, opts) {
1600
1613
  opts = opts || {};
1601
1614
  var proxySnap; try { proxySnap = _snapProxy(opts); } catch (_pe) { return _rej(E("est/bad-input", "the options object could not be read", _pe)); }
1602
- return Promise.resolve().then(function () {
1615
+ return _promiseResolve(_Promise, undefined).then(function () {
1603
1616
  opts = _knownOpts(opts, SIMPLEENROLL_OPTS, "simpleenroll");
1604
1617
  return _enroll("simpleenroll", baseUrl, csrInput, opts, proxySnap);
1605
1618
  });
@@ -1639,7 +1652,7 @@ function simpleenroll(baseUrl, csrInput, opts) {
1639
1652
  function simplereenroll(baseUrl, csrInput, opts) {
1640
1653
  opts = opts || {};
1641
1654
  var proxySnap; try { proxySnap = _snapProxy(opts); } catch (_pe) { return _rej(E("est/bad-input", "the options object could not be read", _pe)); }
1642
- return Promise.resolve().then(function () {
1655
+ return _promiseResolve(_Promise, undefined).then(function () {
1643
1656
  opts = _knownOpts(opts, SIMPLEREENROLL_OPTS, "simplereenroll");
1644
1657
  if (!opts.oldCert) throw E("est/bad-input", "simplereenroll requires opts.oldCert (the certificate being renewed, RFC 7030 sec. 4.2.2)");
1645
1658
  if (typeof csrInput !== "function") reenrollGuard(opts.oldCert, _csrDer(csrInput));
@@ -1797,7 +1810,7 @@ async function _serverkeygenResult(res, opts, derived) {
1797
1810
  function serverkeygen(baseUrl, csrInput, opts) {
1798
1811
  opts = opts || {};
1799
1812
  var proxySnap; try { proxySnap = _snapProxy(opts); } catch (_pe) { return _rej(E("est/bad-input", "the options object could not be read", _pe)); }
1800
- return Promise.resolve().then(function () {
1813
+ return _promiseResolve(_Promise, undefined).then(function () {
1801
1814
  opts = _knownOpts(opts, SERVERKEYGEN_OPTS, "serverkeygen");
1802
1815
  var checkedTransport = opts.transport;
1803
1816
  var csrDer = _csrDer(csrInput);
@@ -1859,7 +1872,7 @@ function _csrattrsResult(res, opts) {
1859
1872
  function csrattrs(baseUrl, opts) {
1860
1873
  opts = opts || {};
1861
1874
  var proxySnap; try { proxySnap = _snapProxy(opts); } catch (_pe) { return _rej(E("est/bad-input", "the options object could not be read", _pe)); }
1862
- return Promise.resolve().then(function () {
1875
+ return _promiseResolve(_Promise, undefined).then(function () {
1863
1876
  opts = _knownOpts(opts, CSRATTRS_OPTS, "csrattrs");
1864
1877
  return _client("csrattrs", "GET", baseUrl, null, { accept: "application/csrattrs" }, opts, proxySnap, undefined, opts.transport);
1865
1878
  }).then(function (res) { return _csrattrsResult(res, opts); });
@@ -188,6 +188,11 @@ var SmimeError = defineClass("SmimeError", { withCause: true });
188
188
 
189
189
  var CtError = defineClass("CtError", { withCause: true });
190
190
 
191
+ var TufError = defineClass("TufError", { withCause: true });
192
+ var RelatedCertError = defineClass("RelatedCertError", { withCause: true });
193
+ var AltSigError = defineClass("AltSigError", { withCause: true });
194
+ var PossessionError = defineClass("PossessionError", { withCause: true });
195
+
191
196
  var ScepError = defineClass("ScepError", { withCause: true });
192
197
 
193
198
  var TlsError = defineClass("TlsError", { withCause: true });
@@ -196,6 +201,8 @@ var C509Error = defineClass("C509Error", { withCause: true });
196
201
 
197
202
  var MerkleError = defineClass("MerkleError", { withCause: true });
198
203
 
204
+ var TlogError = defineClass("TlogError", { withCause: true });
205
+
199
206
  var CsrattrsError = defineClass("CsrattrsError", { withCause: true });
200
207
 
201
208
  var EstError = defineClass("EstError", { withCause: true });
@@ -245,6 +252,10 @@ module.exports = {
245
252
  CmpError: CmpError,
246
253
  PathError: PathError,
247
254
  CtError: CtError,
255
+ TufError: TufError,
256
+ RelatedCertError: RelatedCertError,
257
+ AltSigError: AltSigError,
258
+ PossessionError: PossessionError,
248
259
  ScepError: ScepError,
249
260
  TlsError: TlsError,
250
261
  C509Error: C509Error,
@@ -256,6 +267,7 @@ module.exports = {
256
267
  WebauthnError: WebauthnError,
257
268
  LintError: LintError,
258
269
  MerkleError: MerkleError,
270
+ TlogError: TlogError,
259
271
  SmimeError: SmimeError,
260
272
  CsrattrsError: CsrattrsError,
261
273
  EstError: EstError,
@@ -7,7 +7,7 @@ var _resolve = _intrinsic.uncurry(_intrinsic.promiseResolve);
7
7
  var _reject = _intrinsic.uncurry(_intrinsic.promiseReject);
8
8
  var _apply = _intrinsic.apply;
9
9
  var _String = _intrinsic.String;
10
- var _Promise = Promise;
10
+ var _Promise = _intrinsic.Promise;
11
11
  /** @internal Bound at load, like every other operation this module decides with: a replacement
12
12
  * installed later would otherwise choose when a bound expires, or whether it ever does. */
13
13
  var _setTimeout = setTimeout;
@@ -6,12 +6,15 @@ var async = require("./guard-async");
6
6
  var identifier = require("./guard-identifier");
7
7
  var time = require("./guard-time");
8
8
  var intrinsic = require("./guard-intrinsic");
9
- var _NAMED_SCAN_MAX_ELEMENTS = require("./constants").LIMITS.NAMED_SCAN_MAX_ELEMENTS;
9
+ var constants = require("./constants");
10
+ var _NAMED_SCAN_MAX_ELEMENTS = constants.LIMITS.NAMED_SCAN_MAX_ELEMENTS;
10
11
  var util = intrinsic.assign(intrinsic.create(null), { types: intrinsic.types });
11
12
  var _isArray = intrinsic.isArray;
12
13
  var _bufferFrom = intrinsic.bufferFrom;
14
+ var _abSlice = intrinsic.arrayBufferSlice;
13
15
  var _isView = intrinsic.isView;
14
16
  var _isBuffer = intrinsic.isBuffer;
17
+ var _bufferCompare = intrinsic.compare;
15
18
  var _asyncIterator = intrinsic.asyncIterator;
16
19
  var _NO_ARGS = intrinsic.freeze([]);
17
20
  var _create = intrinsic.create;
@@ -42,7 +45,8 @@ var _mapSet = intrinsic.mapSet;
42
45
  var _taSet = intrinsic.typedArraySet;
43
46
  var _Set = intrinsic.Set;
44
47
  var _Map = intrinsic.Map;
45
- var _append = require("./guard-list").append;
48
+ var guardList = require("./guard-list");
49
+ var _append = guardList.append;
46
50
 
47
51
  function _ownArrayIndices(v, n) {
48
52
  var keys = _ownKeys(v);
@@ -71,6 +75,12 @@ function asyncStreamOf(x) {
71
75
  return stream;
72
76
  }
73
77
 
78
+ /** @internal A streaming door's own refusal for a fault the WebCrypto layer raised while reading a
79
+ * chunk. Anything else is rethrown as it stands.
80
+ *
81
+ * @enforced-by behavioral -- the rule is which faults are translated and which are rethrown, which no
82
+ * code shape distinguishes from any other error-code test; the vectors driving a chunk that is not a byte
83
+ * source and an unrelated throw through a streaming door are the guard */
74
84
  function translateStreamError(e, E, code) {
75
85
  if (e && e.code === "webcrypto/data") throw E(code, "a streamed content chunk is not a byte source", e);
76
86
  if (e && e.code === "webcrypto/syntax") throw E(code, "the streamed content is not a valid async iterable of byte chunks", e);
@@ -167,6 +177,21 @@ function lengthOf(view) {
167
177
  return _lengthOf(view);
168
178
  }
169
179
 
180
+ /** @internal A view's bytes as an ArrayBuffer of their own, read through the captured accessors.
181
+ * `v.buffer`, `v.byteOffset` and `v.byteLength` are getters on the TypedArray and DataView prototypes,
182
+ * so a replacement of any of them decides which bytes the result holds: measured, replacing them after
183
+ * the slicing helper had been captured turned a valid 64-byte Ed25519 signature into 64 zero bytes on
184
+ * its way out of `pki.webcrypto.subtle.sign`. This is the form for a value crossing into a WebCrypto
185
+ * return, where the API's type is an ArrayBuffer rather than a view.
186
+ *
187
+ * @enforced-by guard-shape-reinlined
188
+ * @guard-shape \(\s*([A-Za-z_$][\w$]*)\.buffer\s*,\s*\1\.byteOffset
189
+ */
190
+ function toArrayBuffer(input) {
191
+ var offset = _offsetOf(input);
192
+ return _abSlice(_storeOf(input), offset, offset + _lengthOf(input));
193
+ }
194
+
170
195
  function _refuseShared(v, ErrorClass, code, label) {
171
196
  if (util.types.isSharedArrayBuffer(v) ||
172
197
  (_isView(v) && util.types.isSharedArrayBuffer(_storeOf(v)))) {
@@ -233,10 +258,19 @@ function snapshotSource(input, ErrorClass, code, label) {
233
258
  }
234
259
 
235
260
  // @enforced-by behavioral -- a copy has no rename-proof code shape to detect (any `Buffer.from(x)`
261
+ /** @internal `maxNodes` bounds the WORK, which the depth cap does not. A value that shares a subobject
262
+ * with itself is a graph rather than a tree, and this copy visits every reference independently: with
263
+ * `v = [v, v]` repeated n times, the depth is n and the work is 2 to the n. MEASURED through two shipped
264
+ * doors, a DAG placed in a member beside the ones a verb validates, `pki.jose.thumbprint` took 748 ms at
265
+ * 16 levels, 2.9 seconds at 18 and 5.9 at 19, doubling every level up to a depth cap of 64, and
266
+ * `pki.tuf.keyId` the same (CWE-770). The counter is shared across the whole walk, so the cost follows
267
+ * the budget rather than the shape of the input. The figure is far above any real caller value: an
268
+ * options bag or a JWK is tens of nodes, and a certificate chain passed as an anchor list is thousands. */
236
269
  function snapshotDeep(value, ErrorClass, code, label, opts) {
237
270
  var o = opts || {};
238
271
  var cap = o.maxDepth == null ? 64 : o.maxDepth;
239
- return _deep(value, ErrorClass, code, label, cap, 0, o.collect || null);
272
+ var maxNodes = o.maxNodes == null ? constants.LIMITS.DEEP_COPY_MAX_NODES : o.maxNodes;
273
+ return _deep(value, ErrorClass, code, label, cap, 0, o.collect || null, { n: 0, max: maxNodes });
240
274
  }
241
275
 
242
276
  function _surface(names) {
@@ -345,8 +379,19 @@ function _protoChainHasProxy(v) {
345
379
 
346
380
  function _plainCopy(copy) { return copy; }
347
381
 
348
- function _deep(v, ErrorClass, code, label, cap, depth, collect) {
382
+ function _deep(v, ErrorClass, code, label, cap, depth, collect, work) {
349
383
  if (depth > cap) throw _raise(ErrorClass, code, label + " is nested too deeply to copy");
384
+ /** @internal One unit per value visited, charged against the shared counter before the value is read.
385
+ * The depth cap above bounds how far down the walk goes and says nothing about how WIDE the walk is
386
+ * when references are shared, which is what the counter answers. */
387
+ if (work !== undefined) {
388
+ work.n += 1;
389
+ if (work.n > work.max) {
390
+ throw _raise(ErrorClass, code, label + " expands to more than " + work.max +
391
+ " values when copied: a value that holds the same subobject in more than one place is copied " +
392
+ "once per place, so a few dozen fields can expand without bound");
393
+ }
394
+ }
350
395
  if (v == null || typeof v !== "object") return v;
351
396
  _refuseShared(v, ErrorClass, code, label);
352
397
  if (_protoChainHasProxy(v)) {
@@ -360,20 +405,25 @@ function _deep(v, ErrorClass, code, label, cap, depth, collect) {
360
405
  if (_isBuffer(v) || _isView(v) || _isArrayBuffer(v)) {
361
406
  var bytesCopy = _copyBytesSameKind(v, ErrorClass, code, label, collect);
362
407
  if (bytesCopy.byteLength <= _NAMED_SCAN_MAX_ELEMENTS) {
363
- _copyNamed(v, bytesCopy, ErrorClass, code, label, cap, depth, collect);
408
+ _copyNamed(v, bytesCopy, ErrorClass, code, label, cap, depth, collect, work);
364
409
  }
365
410
  return bytesCopy;
366
411
  }
367
412
  if (require("./guard-parsed").isRecordedAsProduced(v)) return v; // allow:inline-require -- circular load: guard-parsed requires this module
368
413
  if (util.types.isDate(v)) {
369
414
  var dateCopy = new _Date(time.instantOf(v));
370
- _copyNamed(v, dateCopy, ErrorClass, code, label, cap, depth, collect, _DATE_BEHAVIOR);
415
+ _copyNamed(v, dateCopy, ErrorClass, code, label, cap, depth, collect, work, _DATE_BEHAVIOR);
371
416
  return _plainCopy(dateCopy);
372
417
  }
373
418
  if (_isArray(v)) {
374
419
  var arr = [];
375
420
  var indexE = function (c, m) { return _raise(ErrorClass, c, m); };
376
- var indices = _ownArrayIndices(v, v.length);
421
+ /** @internal The count is narrowed where it is taken and the copy's own length is set from it, not
422
+ * from a second read of the source. Read again at the end, a nested getter that shrank the source
423
+ * while the walk ran TRUNCATED the finished copy: both elements had been collected and the copy
424
+ * then reported one. The snapshot is of the array as it was counted. */
425
+ var vCount = guardList.count(v, "snapshotDeep");
426
+ var indices = _ownArrayIndices(v, vCount);
377
427
  if (identifier.readableIndices(v, indexE, code, label).length !== indices.length) {
378
428
  throw _raise(ErrorClass, code, label + " has an in-range index supplied by its prototype rather than the array itself; a list resolved through the prototype chain is refused");
379
429
  }
@@ -382,10 +432,10 @@ function _deep(v, ErrorClass, code, label, cap, depth, collect) {
382
432
  var element;
383
433
  try { element = v[k]; }
384
434
  catch (e) { throw _raise(ErrorClass, code, label + ": reading element " + k + " threw", e); }
385
- arr[k] = _deep(element, ErrorClass, code, label, cap, depth + 1, collect);
435
+ arr[k] = _deep(element, ErrorClass, code, label, cap, depth + 1, collect, work);
386
436
  });
387
- arr.length = v.length;
388
- _copyNamed(v, arr, ErrorClass, code, label, cap, depth, collect);
437
+ arr.length = vCount;
438
+ _copyNamed(v, arr, ErrorClass, code, label, cap, depth, collect, work);
389
439
  return _plainCopy(arr);
390
440
  }
391
441
  var surface = _opaqueSurface(v, ErrorClass, code, label);
@@ -398,13 +448,13 @@ function _deep(v, ErrorClass, code, label, cap, depth, collect) {
398
448
  "fields as a plain object");
399
449
  }
400
450
  if (util.types.isMap(v)) {
401
- return _plainCopy(_copyEntries(v, new _Map(), ErrorClass, code, label, cap, depth, collect));
451
+ return _plainCopy(_copyEntries(v, new _Map(), ErrorClass, code, label, cap, depth, collect, work));
402
452
  }
403
453
  if (util.types.isSet(v)) {
404
- return _plainCopy(_copyEntries(v, new _Set(), ErrorClass, code, label, cap, depth, collect));
454
+ return _plainCopy(_copyEntries(v, new _Set(), ErrorClass, code, label, cap, depth, collect, work));
405
455
  }
406
456
  var out = _create(_getPrototypeOf(v) === null ? null : _ObjectProto);
407
- _copyNamed(v, out, ErrorClass, code, label, cap, depth, collect);
457
+ _copyNamed(v, out, ErrorClass, code, label, cap, depth, collect, work);
408
458
  return out;
409
459
  }
410
460
 
@@ -416,14 +466,14 @@ function _namesToCopy(src, ErrorClass, code, label, atArgument) {
416
466
  return names;
417
467
  }
418
468
 
419
- function _copyEntries(src, dst, ErrorClass, code, label, cap, depth, collect) {
469
+ function _copyEntries(src, dst, ErrorClass, code, label, cap, depth, collect, work) {
420
470
  var walk = util.types.isSet(src) ? _setForEach : _mapForEach;
421
471
  walk(src, function (value, key) {
422
- var copiedValue = _deep(value, ErrorClass, code, label, cap, depth + 1, collect);
472
+ var copiedValue = _deep(value, ErrorClass, code, label, cap, depth + 1, collect, work);
423
473
  if (util.types.isSet(dst)) _setAdd(dst, copiedValue);
424
- else _mapSet(dst, _deep(key, ErrorClass, code, label, cap, depth + 1, collect), copiedValue);
474
+ else _mapSet(dst, _deep(key, ErrorClass, code, label, cap, depth + 1, collect, work), copiedValue);
425
475
  });
426
- _copyNamed(src, dst, ErrorClass, code, label, cap, depth, collect);
476
+ _copyNamed(src, dst, ErrorClass, code, label, cap, depth, collect, work);
427
477
  return dst;
428
478
  }
429
479
 
@@ -433,8 +483,22 @@ var _DATE_BEHAVIOR = (function () {
433
483
  return t;
434
484
  })();
435
485
 
436
- function _copyNamed(src, dst, ErrorClass, code, label, cap, depth, collect, behavior) {
486
+ function _copyNamed(src, dst, ErrorClass, code, label, cap, depth, collect, work, behavior) {
437
487
  var keys = _namesToCopy(src, ErrorClass, code, label, depth === 0);
488
+ /** @internal Every PROPERTY is charged, not only every value `_deep` recurses into. A function is
489
+ * carried across by reference rather than copied, so it took no charge, and the counter then
490
+ * under-counted the real work by the property count: MEASURED through `pki.jose.thumbprint`, a graph
491
+ * whose shared leaf carried 1000 function properties cost 17.7 seconds against 384 ms for the same
492
+ * graph with a plain leaf, because the visits were capped and the per-visit work was not. Charging the
493
+ * key run up front is one charge for work proportional to it. */
494
+ if (work !== undefined && keys.length > 0) {
495
+ work.n += keys.length;
496
+ if (work.n > work.max) {
497
+ throw _raise(ErrorClass, code, label + " expands to more than " + work.max +
498
+ " values when copied: a value that holds the same subobject in more than one place is copied " +
499
+ "once per place, so a few dozen fields can expand without bound");
500
+ }
501
+ }
438
502
  for (var k = 0; k < keys.length; k++) {
439
503
  var value;
440
504
  try { value = src[keys[k]]; }
@@ -442,7 +506,7 @@ function _copyNamed(src, dst, ErrorClass, code, label, cap, depth, collect, beha
442
506
  if (behavior && behavior[keys[k]] && typeof value === "function") continue;
443
507
  _defineProperty(dst, keys[k], {
444
508
  value: typeof value === "function" ? value
445
- : _deep(value, ErrorClass, code, label, cap, depth + 1, collect),
509
+ : _deep(value, ErrorClass, code, label, cap, depth + 1, collect, work),
446
510
  writable: true, enumerable: _wasEnumerable(src, keys[k]), configurable: true,
447
511
  });
448
512
  }
@@ -465,7 +529,9 @@ function fixArguments(ErrorClass, code, args) {
465
529
  "a copy of a caller-supplied argument");
466
530
  }
467
531
  try {
468
- for (var i = 0; i < args.length; i++) {
532
+ /** @internal The count is narrowed where it is taken, as every walk in `guard.list` is. */
533
+ var argCount = guardList.count(args, "fixArguments");
534
+ for (var i = 0; i < argCount; i++) {
469
535
  _append(values, snapshotDeep(args[i][0], ErrorClass, code, args[i][1], { collect: copies }));
470
536
  }
471
537
  } catch (e) {
@@ -484,8 +550,25 @@ function fixedCall(ErrorClass, code, args, body) {
484
550
  }), function () { if (handle) handle.release(); });
485
551
  }
486
552
 
553
+ /**
554
+ * @internal The ORDER of two byte strings, through the captured operation. A DER SET OF is ordered by
555
+ * its members' encoded bytes and a byte-for-byte claim is this comparison, so whatever answers
556
+ * "before" and "equal" decides emitted bytes at one end and a verdict at the other: a replacement
557
+ * answering a fixed order sets the order of every SET this toolkit builds, and one answering zero turns
558
+ * a mismatch into a match. Offered here so a module can reach the captured operation through the guard
559
+ * surface it already takes. This is an ORDERING, not a secret comparison: a secret goes through
560
+ * `guard.crypto.constantTimeEqual`.
561
+ *
562
+ * @enforced-by guard-shape-reinlined
563
+ * @guard-shape Buffer\.compare\(
564
+ * @guard-via (?:guard\.bytes|intrinsic)\.compare\(
565
+ */
566
+ function compare(a, b) { return _bufferCompare(a, b); }
567
+
487
568
  module.exports = intrinsic.freeze({
569
+ compare: compare,
488
570
  view: view, source: source, snapshot: snapshot, snapshotSource: snapshotSource,
489
571
  isByteSource: isByteSource, asyncStreamOf: asyncStreamOf, translateStreamError: translateStreamError, lengthOf: lengthOf, outputView: outputView,
572
+ toArrayBuffer: toArrayBuffer,
490
573
  snapshotDeep: snapshotDeep, fixArguments: fixArguments, fixedCall: fixedCall,
491
574
  });