@blamejs/pki 0.8.30 → 0.8.48
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +411 -0
- package/README.md +67 -16
- package/bin/pki.js +716 -30
- package/examples/issue-a-certificate.js +90 -0
- package/examples/post-quantum.js +66 -0
- package/examples/read-a-certificate.js +56 -0
- package/examples/revoke-and-check.js +86 -0
- package/examples/sign-and-verify-cms.js +63 -0
- package/index.js +18 -5
- package/lib/acme.js +223 -74
- package/lib/alt-sig.js +441 -0
- package/lib/asn1-der.js +48 -25
- package/lib/attrcert-sign.js +27 -19
- package/lib/cbor-det.js +24 -14
- package/lib/cmc-build.js +9 -2
- package/lib/cmc-verify.js +14 -3
- package/lib/cmp-build.js +17 -5
- package/lib/cmp-session.js +24 -11
- package/lib/cmp-verify.js +28 -19
- package/lib/cms-compress.js +3 -1
- package/lib/cms-decrypt.js +102 -7
- package/lib/cms-encrypt.js +59 -7
- package/lib/cms-sign.js +31 -9
- package/lib/cms-verify.js +22 -18
- package/lib/composite-kem.js +30 -18
- package/lib/composite-sig.js +29 -14
- package/lib/constants.js +28 -0
- package/lib/crl-sign.js +111 -19
- package/lib/crmf-sign.js +167 -15
- package/lib/csr-sign.js +117 -6
- package/lib/ct.js +939 -48
- package/lib/est.js +29 -16
- package/lib/framework-error.js +12 -0
- package/lib/guard-async.js +1 -1
- package/lib/guard-bytes.js +103 -20
- package/lib/guard-crypto.js +205 -3
- package/lib/guard-encoding.js +6 -1
- package/lib/guard-identifier.js +55 -8
- package/lib/guard-intrinsic.js +43 -0
- package/lib/guard-json.js +122 -9
- package/lib/guard-limits.js +12 -1
- package/lib/guard-list.js +63 -8
- package/lib/guard-name.js +167 -20
- package/lib/guard-range.js +43 -5
- package/lib/guard-text.js +41 -0
- package/lib/guard-verdict.js +39 -8
- package/lib/hpke.js +688 -42
- package/lib/http-transport.js +156 -4
- package/lib/identity-match.js +4 -4
- package/lib/inspect.js +448 -2
- package/lib/jose.js +93 -25
- package/lib/key.js +18 -13
- package/lib/lint.js +165 -1
- package/lib/merkle.js +233 -27
- package/lib/ocsp.js +27 -14
- package/lib/oid.js +14 -4
- package/lib/path-validate.js +143 -61
- package/lib/pkcs12-build.js +7 -2
- package/lib/pki-build.js +31 -7
- package/lib/possession.js +575 -0
- package/lib/related-cert.js +708 -0
- package/lib/revocation-fetch.js +6 -6
- package/lib/scep.js +15 -5
- package/lib/schema-c509.js +5 -2
- package/lib/schema-cmc.js +1 -1
- package/lib/schema-cmp.js +8 -8
- package/lib/schema-cms.js +2 -15
- package/lib/schema-csr.js +69 -0
- package/lib/schema-engine.js +10 -3
- package/lib/schema-pkix.js +267 -4
- package/lib/schema-tsp.js +7 -6
- package/lib/sign-scheme.js +22 -9
- package/lib/sigstore.js +684 -168
- package/lib/sleep.js +8 -1
- package/lib/smime.js +22 -1
- package/lib/tlog.js +1235 -0
- package/lib/tls-cert-compress.js +25 -8
- package/lib/tsp-sign.js +120 -35
- package/lib/tuf.js +1395 -0
- package/lib/validator-tpm.js +4 -1
- package/lib/verify-seam.js +7 -1
- package/lib/webauthn-mds.js +41 -4
- package/lib/webauthn.js +43 -25
- package/lib/webcrypto.js +48 -46
- package/lib/x509-sign.js +225 -19
- package/package.json +2 -1
- 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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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); });
|
package/lib/framework-error.js
CHANGED
|
@@ -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,
|
package/lib/guard-async.js
CHANGED
|
@@ -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;
|
package/lib/guard-bytes.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
});
|