@blamejs/pki 0.8.49 → 0.8.51
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 +27 -0
- package/README.md +3 -3
- package/lib/acme.js +53 -27
- package/lib/cmp-build.js +19 -8
- package/lib/constants.js +3 -2
- package/lib/ct.js +190 -43
- package/lib/est.js +42 -19
- package/lib/guard-bytes.js +17 -2
- package/lib/guard-identifier.js +22 -0
- package/lib/guard-intrinsic.js +55 -0
- package/lib/http-digest.js +8 -1
- package/lib/http-transport.js +48 -20
- package/lib/inspect.js +3 -2
- package/lib/lint.js +105 -2
- package/lib/ocsp.js +3 -1
- package/lib/path-validate.js +93 -9
- package/lib/pki-build.js +40 -0
- package/lib/revocation-fetch.js +14 -5
- package/lib/scep.js +36 -15
- package/lib/x509-sign.js +22 -2
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/lib/ct.js
CHANGED
|
@@ -67,7 +67,17 @@ var _bufCopy = intrinsic.uncurry(Buffer.prototype.copy);
|
|
|
67
67
|
var _writeU64BE = intrinsic.uncurry(Buffer.prototype.writeBigUInt64BE);
|
|
68
68
|
var _searchSet = intrinsic.uncurry(URLSearchParams.prototype.set);
|
|
69
69
|
var _numberOf = intrinsic.Number;
|
|
70
|
-
var _URL = URL;
|
|
70
|
+
var _URL = intrinsic.URL;
|
|
71
|
+
/** @internal Every part of a parsed destination is read through the accessor captured at load, not off
|
|
72
|
+
* the value's own prototype: a header getter that redefined `URL.prototype.href` had the request go to
|
|
73
|
+
* the host its getter named while the url the caller passed was still the one reported. */
|
|
74
|
+
var _urlHref = intrinsic.urlHref;
|
|
75
|
+
var _urlOrigin = intrinsic.urlOrigin;
|
|
76
|
+
var _urlProtocol = intrinsic.urlProtocol;
|
|
77
|
+
var _urlPathname = intrinsic.urlPathname;
|
|
78
|
+
var _urlSearchParams = intrinsic.urlSearchParams;
|
|
79
|
+
var _urlSetPathname = intrinsic.urlSetPathname;
|
|
80
|
+
var _urlSetSearch = intrinsic.urlSetSearch;
|
|
71
81
|
var _JSON_SUFFIX = ".json";
|
|
72
82
|
function _endsWithJson(s) { return s.length >= _JSON_SUFFIX.length && _strSlice(s, s.length - _JSON_SUFFIX.length) === _JSON_SUFFIX; }
|
|
73
83
|
var ByteReader = require("./byte-reader.js");
|
|
@@ -1177,14 +1187,42 @@ var KNOWN_FETCH_OPTS = intrinsic.assign(intrinsic.create(null), { url: 1, signer
|
|
|
1177
1187
|
* and rewrite the servername after the check had passed: the anchors checked were then not the anchors
|
|
1178
1188
|
* the connection used. Copying the list is what closes that, since emptying an array the caller still
|
|
1179
1189
|
* holds is the lever. Flatten once, check that record, and connect under the same one. */
|
|
1190
|
+
/** @internal One piece of trust material, copied rather than referenced. An anchor, a client
|
|
1191
|
+
* certificate and a client key are each a Buffer the caller still holds, and copying the list that
|
|
1192
|
+
* holds them copies the references in it: a header getter that wrote an attacker's PEM OVER an
|
|
1193
|
+
* anchor's bytes left every reference pointing where it did and still opened the connection under
|
|
1194
|
+
* trust material the unpinned-log check had already approved, MEASURED on this module. A PEM string
|
|
1195
|
+
* cannot be changed after the fact and is taken as it is; anything that is neither is handed on
|
|
1196
|
+
* unchanged, so the transport keeps giving the refusal it gives today for a shape it does not
|
|
1197
|
+
* accept. */
|
|
1198
|
+
function _trustValue(v, field) {
|
|
1199
|
+
if (typeof v === "string") return v;
|
|
1200
|
+
if (guard.bytes.isByteSource(v)) return _toBuffer(v, field, _ctErr, "ct/bad-input");
|
|
1201
|
+
return v;
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1180
1204
|
function _tlsForFetch(opts) {
|
|
1181
|
-
|
|
1205
|
+
/** @internal A record with NO prototype where the caller supplied no TLS at all, because the empty
|
|
1206
|
+
* object a literal gives inherits from `Object.prototype` and every field below is then answerable
|
|
1207
|
+
* from there. MEASURED: a getter inside `opts.sth` set `Object.prototype.useSystemStore = true`, the
|
|
1208
|
+
* record built here inherited it, and the refusal that turns away an unpinned log was skipped, so a
|
|
1209
|
+
* call that had named neither a transport nor an anchor opened a connection trusting the system
|
|
1210
|
+
* store. Where the caller DID supply a TLS record this is settled at the door and the record read
|
|
1211
|
+
* here is the flat one taken then, whose seven fields are its own. */
|
|
1212
|
+
var t = opts.tls != null ? opts.tls : intrinsic.create(null);
|
|
1182
1213
|
var anchors = t.anchors;
|
|
1183
|
-
|
|
1184
|
-
|
|
1214
|
+
/** @internal Copied by index rather than with `slice`, which consults the array's species and so
|
|
1215
|
+
* would run a caller's constructor. `guard.list.copyMap` walks it with a narrowed count and copies
|
|
1216
|
+
* each element's bytes as it goes. */
|
|
1217
|
+
anchors = intrinsic.isArray(anchors)
|
|
1218
|
+
? guard.list.copyMap(anchors, function (a, i) { return _trustValue(a, "opts.tls.anchors[" + i + "]"); })
|
|
1219
|
+
: _trustValue(anchors, "opts.tls.anchors");
|
|
1220
|
+
return { anchors: anchors, useSystemStore: t.useSystemStore, cert: _trustValue(t.cert, "opts.tls.cert"),
|
|
1221
|
+
key: _trustValue(t.key, "opts.tls.key"), minVersion: t.minVersion, servername: t.servername,
|
|
1222
|
+
checkServerIdentity: t.checkServerIdentity };
|
|
1185
1223
|
}
|
|
1186
1224
|
|
|
1187
|
-
function _sameOrigin(a, b) { return new
|
|
1225
|
+
function _sameOrigin(a, b) { return _urlOrigin(new _URL(a)) === _urlOrigin(new _URL(b)); }
|
|
1188
1226
|
|
|
1189
1227
|
/** @internal Refused at the door and again where the value is used. */
|
|
1190
1228
|
function _assertTransport(opts) {
|
|
@@ -1196,17 +1234,20 @@ function _fetchCtypeToken(headers) { return String((headers || {})["content-type
|
|
|
1196
1234
|
|
|
1197
1235
|
function _parseHttpsUrl(u, label) {
|
|
1198
1236
|
var parsed;
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1237
|
+
var text = _stringOf(u);
|
|
1238
|
+
try { parsed = new _URL(text); }
|
|
1239
|
+
catch (e) { throw _ctErr("ct/bad-url", "the CT log-list " + label + " did not parse: " + text, e); }
|
|
1240
|
+
var scheme = _urlProtocol(parsed);
|
|
1241
|
+
if (scheme !== "https:") throw _ctErr("ct/insecure-url", "the CT log-list " + label + " must be https, got " + scheme + " for " + text);
|
|
1202
1242
|
return parsed;
|
|
1203
1243
|
}
|
|
1204
1244
|
|
|
1205
1245
|
function _sigUrlFor(parsed) {
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
sig
|
|
1209
|
-
|
|
1246
|
+
var path = _urlPathname(parsed);
|
|
1247
|
+
if (!_endsWithJson(path)) throw _ctErr("ct/bad-input", "cannot derive the detached-signature URL from a non-.json path (" + JSON.stringify(path) + "); pass opts.sigUrl explicitly");
|
|
1248
|
+
var sig = new _URL(_urlHref(parsed));
|
|
1249
|
+
_urlSetPathname(sig, _strSlice(path, 0, path.length - _JSON_SUFFIX.length) + ".sig");
|
|
1250
|
+
return _urlHref(sig);
|
|
1210
1251
|
}
|
|
1211
1252
|
|
|
1212
1253
|
function _fetchBody(transport, url, req, label) {
|
|
@@ -1239,6 +1280,10 @@ var STH_PREIMAGE_BYTES = 50;
|
|
|
1239
1280
|
var CT_VERSION_V1 = 0;
|
|
1240
1281
|
var SIG_TYPE_TREE_HASH = 1;
|
|
1241
1282
|
var CT_V1_PREFIX = "ct/v1/";
|
|
1283
|
+
/** @internal The union across the sec. 4 messages rather than one set per message, because a caller
|
|
1284
|
+
* drives several of them from a single options bag: the same record carries the pinned key for the
|
|
1285
|
+
* tree head and the range for the entries. An option a message does not read is ignored by it, so the
|
|
1286
|
+
* refusal this list makes is of a name no message knows. */
|
|
1242
1287
|
var KNOWN_CLIENT_OPTS = intrinsic.assign(intrinsic.create(null), {
|
|
1243
1288
|
url: 1, logKey: 1, transport: 1, tls: 1, headers: 1, timeout: 1, maxResponseBytes: 1,
|
|
1244
1289
|
sth: 1, oldSth: 1, newSth: 1, leafHash: 1, start: 1, end: 1, chain: 1,
|
|
@@ -1249,14 +1294,14 @@ var KNOWN_CLIENT_OPTS = intrinsic.assign(intrinsic.create(null), {
|
|
|
1249
1294
|
* with or without the `ct/v1/` prefix the specification fixes, and neither spelling doubles it. */
|
|
1250
1295
|
function _endpoint(url, message) {
|
|
1251
1296
|
var parsed = _parseHttpsUrl(url, "log URL");
|
|
1252
|
-
var path = parsed
|
|
1297
|
+
var path = _urlPathname(parsed);
|
|
1253
1298
|
if (path.length === 0 || path[path.length - 1] !== "/") path += "/";
|
|
1254
1299
|
var tail = CT_V1_PREFIX;
|
|
1255
1300
|
if (path.length >= CT_V1_PREFIX.length + 1 &&
|
|
1256
1301
|
_strSlice(path, path.length - CT_V1_PREFIX.length) === CT_V1_PREFIX) tail = "";
|
|
1257
|
-
var out = new _URL(parsed
|
|
1258
|
-
out
|
|
1259
|
-
out
|
|
1302
|
+
var out = new _URL(_urlHref(parsed));
|
|
1303
|
+
_urlSetPathname(out, path + tail + message);
|
|
1304
|
+
_urlSetSearch(out, "");
|
|
1260
1305
|
return out;
|
|
1261
1306
|
}
|
|
1262
1307
|
|
|
@@ -1268,7 +1313,13 @@ function _endpoint(url, message) {
|
|
|
1268
1313
|
* would be a pin approved on one object and a socket opened under another. Reading from the record
|
|
1269
1314
|
* afterwards means the value that was checked is the value that is used. Every verb calls this at its
|
|
1270
1315
|
* own entry and passes the record down, rather than relying on a check further in. */
|
|
1271
|
-
|
|
1316
|
+
/** @internal `keyName` is the option this message VERIFIES against, or null for one that verifies
|
|
1317
|
+
* nothing. The option list is the union across the messages so that one bag drives several of them,
|
|
1318
|
+
* and an option a message does not read is ignored by it; settling the pinned key at every door
|
|
1319
|
+
* instead would make a message that never checks a signature refuse a bag over a field it does not
|
|
1320
|
+
* consume. `getSth` and the two chain submissions name it; the proof, consistency, entries and roots
|
|
1321
|
+
* messages pass null. */
|
|
1322
|
+
function _clientDoor(opts, keyName) {
|
|
1272
1323
|
/** @internal `optionsObject` is the door `signSct` in this module already enters by, and it REFUSES
|
|
1273
1324
|
* an options bag carrying an accessor rather than tolerating one: a bag whose properties are not
|
|
1274
1325
|
* plain values is a shape no set of checks can answer for, and it also refuses one that changes
|
|
@@ -1286,15 +1337,93 @@ function _clientDoor(opts) {
|
|
|
1286
1337
|
* therefore dropped an inherited `transport`, and dropping a transport is not a smaller result: it
|
|
1287
1338
|
* falls through to opening a real connection where the caller passed a fake one. What makes the
|
|
1288
1339
|
* repeated reads safe instead is that this door refuses a bag whose properties are not plain values,
|
|
1289
|
-
* and that each option below is taken into a local ONCE, before anything that could run caller code.
|
|
1290
|
-
|
|
1340
|
+
* and that each option below is taken into a local ONCE, before anything that could run caller code.
|
|
1341
|
+
* EVERY DECLARED OPTION IS TAKEN HERE, and the ones a verdict rests on are made immune, but the
|
|
1342
|
+
* request is NOT shaped here. Shaping it here was tried and is wrong: copying the request headers
|
|
1343
|
+
* runs whatever getters the caller's header object carries, and doing that before a verb reads its
|
|
1344
|
+
* own options put every verification input at the mercy of one. A header getter swapped `logKey` for
|
|
1345
|
+
* an attacker's SPKI, then overwrote the same buffer in place once the reference was held, then
|
|
1346
|
+
* overwrote `sth.rootHash` so an empty audit proof verified. Each was worse than the wrong-host case
|
|
1347
|
+
* the move was meant to close. The verb's inputs are the more valuable thing, so the headers are
|
|
1348
|
+
* copied at send time as they always were, and the destination is protected by making it immune
|
|
1349
|
+
* instead: a string url cannot be rewritten, and a flattened tls with its anchor list copied cannot
|
|
1350
|
+
* be emptied. Taking the top level is safe because the door has already refused a bag whose
|
|
1351
|
+
* properties are not plain values, so no read here runs caller code. */
|
|
1352
|
+
return { o: _settleDecisive(_takeTopLevel(bag, KNOWN_CLIENT_OPTS), keyName) };
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
/** @internal Every DECLARED option the bag can answer, read once into a record of plain values, before
|
|
1356
|
+
* anything derives from one. `in` is used rather than an enumeration because the door accepts an option
|
|
1357
|
+
* supplied through a caller's defaults object, and an enumeration omits an inherited FUNCTION member,
|
|
1358
|
+
* which silently dropped a `transport` and fell through to opening a real connection. It consults the
|
|
1359
|
+
* prototype chain exactly as the direct reads it replaces did, so it carries no new exposure, and
|
|
1360
|
+
* absence survives: a name the bag cannot answer is left out rather than materialized as `undefined`,
|
|
1361
|
+
* which a guard that tells absent from present-and-undefined depends on. */
|
|
1362
|
+
function _takeTopLevel(bag, known) {
|
|
1363
|
+
var o = intrinsic.create(null);
|
|
1364
|
+
intrinsic.forEach(intrinsic.keys(known), function (k) {
|
|
1365
|
+
if (k in bag) o[k] = bag[k];
|
|
1366
|
+
});
|
|
1367
|
+
return o;
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
/** @internal The two values a verdict rests on, made IMMUNE rather than merely taken. Holding a
|
|
1371
|
+
* reference is not holding a value: a header getter copied an attacker's SPKI OVER the caller's
|
|
1372
|
+
* `logKey` buffer in place and an attacker-signed tree head was accepted, and a getter inside `tls`
|
|
1373
|
+
* rewrote an object-valued url's `hostname` and the request went to another host. Both happened after
|
|
1374
|
+
* the option had been taken, because what was taken could still be changed from outside. A string and
|
|
1375
|
+
* a copied buffer cannot be, so the destination and the pin stop depending on what the caller does
|
|
1376
|
+
* next. Done here, before anything nested is read, for the same reason the taking is. */
|
|
1377
|
+
function _settleDecisive(o, keyName) {
|
|
1378
|
+
/** @internal There is no order that fixes this by itself, which is what two rounds of reordering
|
|
1379
|
+
* showed: converting the url asks an object for its text and that runs the caller's code, which
|
|
1380
|
+
* emptied the anchor list not yet flattened; flattening the anchors first reads `tls.anchors` and
|
|
1381
|
+
* that runs the caller's code too, which rewrote the url not yet converted. So the caller's code is
|
|
1382
|
+
* refused HERE instead of ordered around. A url has to be a string, which needs no conversion, and
|
|
1383
|
+
* a tls carries no accessor, which makes reading its members inert. After that nothing in settling
|
|
1384
|
+
* runs caller code and the order stops mattering. */
|
|
1385
|
+
if (o.url != null && typeof o.url !== "string") {
|
|
1386
|
+
throw _ctErr("ct/bad-input", "opts.url must be a string -- an object is asked for its text form, " +
|
|
1387
|
+
"and asking runs the caller's code, which is how a request reached a host its URL had not named");
|
|
1388
|
+
}
|
|
1389
|
+
/** @internal The detached-signature URL is a destination too, and only the log-list fetch declares
|
|
1390
|
+
* it, so the rule is the same one: a value that has to be converted is a value whose conversion
|
|
1391
|
+
* runs here, after everything else has been settled, where it could still rewrite what was. */
|
|
1392
|
+
if (o.sigUrl != null && typeof o.sigUrl !== "string") {
|
|
1393
|
+
throw _ctErr("ct/bad-input", "opts.sigUrl must be a string -- an object is asked for its text " +
|
|
1394
|
+
"form, and asking runs the caller's code after the other options have been settled");
|
|
1395
|
+
}
|
|
1396
|
+
if (o.tls != null) {
|
|
1397
|
+
guard.identifier.refuseAccessorFields(o.tls,
|
|
1398
|
+
guard.identifier.readableNames(o.tls, _ctErr, "ct/bad-input", "opts.tls"),
|
|
1399
|
+
_ctErr, "ct/bad-input", "opts.tls");
|
|
1400
|
+
/** @internal And the ELEMENTS of the anchor list, which refusing accessors on `tls` itself does not
|
|
1401
|
+
* reach. An accessor on `anchors[0]` runs when the list is copied, and one that overwrote
|
|
1402
|
+
* `sth.rootHash` with the leaf being proven had an empty audit path verify. The refusal has to go
|
|
1403
|
+
* as deep as the reading does. */
|
|
1404
|
+
var suppliedAnchors = o.tls.anchors;
|
|
1405
|
+
if (suppliedAnchors != null) {
|
|
1406
|
+
guard.identifier.refuseAccessorFields(suppliedAnchors,
|
|
1407
|
+
guard.identifier.readableIndices(suppliedAnchors, _ctErr, "ct/bad-input", "opts.tls.anchors"),
|
|
1408
|
+
_ctErr, "ct/bad-input", "opts.tls.anchors");
|
|
1409
|
+
}
|
|
1410
|
+
/** @internal Flattened HERE, with the anchor list copied, because the caller still holds the object
|
|
1411
|
+
* and a getter anywhere else in the bag emptied the list after the trust-anchor check approved it.
|
|
1412
|
+
* Reading its members and its elements is inert now that both refuse an accessor, so this runs no
|
|
1413
|
+
* caller code and the order it runs in stops mattering. */
|
|
1414
|
+
o.tls = _tlsForFetch(o);
|
|
1415
|
+
}
|
|
1416
|
+
if (keyName !== null && o[keyName] != null) {
|
|
1417
|
+
o[keyName] = _toBuffer(o[keyName], "opts." + keyName, _ctErr, "ct/bad-input");
|
|
1418
|
+
}
|
|
1419
|
+
return o;
|
|
1291
1420
|
}
|
|
1292
1421
|
|
|
1293
1422
|
/** @internal The shared request shape every message uses: the caller's transport or a pinned-anchor
|
|
1294
1423
|
* one, the caps, and the header filter. Factored so a verb adds a path and a body and nothing else,
|
|
1295
1424
|
* and so a rule about reaching a log is stated once. `o` has already been through `_clientDoor`, so
|
|
1296
1425
|
* every read here is of a plain value. */
|
|
1297
|
-
function _clientRequest(o
|
|
1426
|
+
function _clientRequest(o) {
|
|
1298
1427
|
var transport = _assertTransport(o);
|
|
1299
1428
|
/** @internal ONE read, and the presence check and the endpoint both decide from it. Read again after
|
|
1300
1429
|
* the header copy below, which runs whatever getters the caller's header object carries, it was
|
|
@@ -1326,16 +1455,18 @@ function _clientRequest(o, label) {
|
|
|
1326
1455
|
if (lk !== "content-length" && lk !== "transfer-encoding") headers[k] = supplied[k];
|
|
1327
1456
|
});
|
|
1328
1457
|
return { transport: transport, url: url, req: { headers: headers, tls: tls, timeout: timeout,
|
|
1329
|
-
maxResponseBytes: maxResponseBytes }
|
|
1458
|
+
maxResponseBytes: maxResponseBytes } };
|
|
1330
1459
|
}
|
|
1331
1460
|
|
|
1332
1461
|
/** @internal A sec. 4 message, fetched and read as JSON. The specification returns every error as a
|
|
1333
1462
|
* 4xx or 5xx with a human-readable message, which `_fetchBody` already turns into a typed refusal,
|
|
1334
1463
|
* so a non-200 never reaches the reader. */
|
|
1335
|
-
async function _clientJson(
|
|
1336
|
-
|
|
1464
|
+
async function _clientJson(door, message, label, query, body) {
|
|
1465
|
+
/** @internal Shaped at send time, from the record the door settled. Shaping it at the door instead
|
|
1466
|
+
* ran the caller's header getters before any verb had captured its verification inputs. */
|
|
1467
|
+
var ctx = _clientRequest(door.o);
|
|
1337
1468
|
var url = _endpoint(ctx.url, message);
|
|
1338
|
-
if (query) intrinsic.keys(query).forEach(function (k) { _searchSet(url
|
|
1469
|
+
if (query) intrinsic.keys(query).forEach(function (k) { _searchSet(_urlSearchParams(url), k, query[k]); });
|
|
1339
1470
|
var req = ctx.req;
|
|
1340
1471
|
if (body !== undefined) {
|
|
1341
1472
|
req = intrinsic.assign(intrinsic.create(null), req);
|
|
@@ -1346,7 +1477,7 @@ async function _clientJson(o, message, label, query, body) {
|
|
|
1346
1477
|
h["content-type"] = "application/json";
|
|
1347
1478
|
req.headers = h;
|
|
1348
1479
|
}
|
|
1349
|
-
var res = await _fetchBody(ctx.transport, url
|
|
1480
|
+
var res = await _fetchBody(ctx.transport, _urlHref(url), req, label);
|
|
1350
1481
|
/** @internal The exact-integer rule applies to the NAMED members, the ones `_uintField` reads. RFC 6962
|
|
1351
1482
|
* responses are ordinary JSON, where `1e3` is legal, so the stricter `integersOnly` would refuse a
|
|
1352
1483
|
* document a conforming log may emit; what this refuses is a spelling whose value is not exactly its
|
|
@@ -1517,7 +1648,8 @@ async function verifySth(sth, logKey) {
|
|
|
1517
1648
|
* sth.treeSize; // -> 3n
|
|
1518
1649
|
*/
|
|
1519
1650
|
async function getSth(opts) {
|
|
1520
|
-
|
|
1651
|
+
var door = _clientDoor(opts || {}, "logKey");
|
|
1652
|
+
opts = door.o;
|
|
1521
1653
|
/** @internal ONE read of the option, and the presence check and the copy both decide from it. Reading
|
|
1522
1654
|
* it twice let an accessor answer the two separately: a non-null value for the check and an
|
|
1523
1655
|
* attacker's SPKI for the copy, and the tree head was then verified against a key the caller never
|
|
@@ -1529,7 +1661,7 @@ async function getSth(opts) {
|
|
|
1529
1661
|
throw _ctErr("ct/bad-input", "opts.logKey is required -- the caller-pinned log SPKI; an unverified tree head is not a result");
|
|
1530
1662
|
}
|
|
1531
1663
|
var pinnedKey = _toBuffer(suppliedLogKey, "opts.logKey", _ctErr, "ct/bad-input");
|
|
1532
|
-
var doc = await _clientJson(
|
|
1664
|
+
var doc = await _clientJson(door,"get-sth", "sth");
|
|
1533
1665
|
var sth = {
|
|
1534
1666
|
treeSize: _uintField(doc, "tree_size", "ct/bad-sth", "sth"),
|
|
1535
1667
|
timestamp: _uintField(doc, "timestamp", "ct/bad-sth", "sth"),
|
|
@@ -1575,7 +1707,8 @@ async function getSth(opts) {
|
|
|
1575
1707
|
* p.verified; // -> true
|
|
1576
1708
|
*/
|
|
1577
1709
|
async function getProofByHash(opts) {
|
|
1578
|
-
|
|
1710
|
+
var door = _clientDoor(opts || {}, null);
|
|
1711
|
+
opts = door.o;
|
|
1579
1712
|
var sth = _assertSth(opts.sth, "opts.sth");
|
|
1580
1713
|
/** @internal Copied for the reason the tree head is: it is sent on the wire and then folded against the
|
|
1581
1714
|
* head after the fetch returns, so a view would let the leaf being proved differ from the leaf asked
|
|
@@ -1584,7 +1717,7 @@ async function getProofByHash(opts) {
|
|
|
1584
1717
|
if (leafHash.length !== 32) {
|
|
1585
1718
|
throw _ctErr("ct/bad-input", "opts.leafHash must be exactly 32 bytes (a v1 leaf hash), got " + leafHash.length);
|
|
1586
1719
|
}
|
|
1587
|
-
var doc = await _clientJson(
|
|
1720
|
+
var doc = await _clientJson(door,"get-proof-by-hash", "audit proof", {
|
|
1588
1721
|
hash: _bufToString(leafHash, "base64"), tree_size: _stringOf(sth.treeSize),
|
|
1589
1722
|
});
|
|
1590
1723
|
var leafIndex = _uintField(doc, "leaf_index", "ct/bad-proof", "audit proof");
|
|
@@ -1630,14 +1763,15 @@ async function getProofByHash(opts) {
|
|
|
1630
1763
|
* c.verified; // -> true
|
|
1631
1764
|
*/
|
|
1632
1765
|
async function getSthConsistency(opts) {
|
|
1633
|
-
|
|
1766
|
+
var door = _clientDoor(opts || {}, null);
|
|
1767
|
+
opts = door.o;
|
|
1634
1768
|
var oldSth = _assertSth(opts.oldSth, "opts.oldSth");
|
|
1635
1769
|
var newSth = _assertSth(opts.newSth, "opts.newSth");
|
|
1636
1770
|
if (oldSth.treeSize > newSth.treeSize) {
|
|
1637
1771
|
throw _ctErr("ct/bad-input", "opts.oldSth names tree size " + oldSth.treeSize +
|
|
1638
1772
|
", which is larger than opts.newSth's " + newSth.treeSize);
|
|
1639
1773
|
}
|
|
1640
|
-
var doc = await _clientJson(
|
|
1774
|
+
var doc = await _clientJson(door,"get-sth-consistency", "consistency proof", {
|
|
1641
1775
|
first: _stringOf(oldSth.treeSize), second: _stringOf(newSth.treeSize),
|
|
1642
1776
|
});
|
|
1643
1777
|
var consistency = _b64Array(doc, "consistency", "ct/bad-consistency", "consistency proof", 32, C.LIMITS.MERKLE_MAX_PROOF_NODES);
|
|
@@ -1687,7 +1821,8 @@ async function getSthConsistency(opts) {
|
|
|
1687
1821
|
* e.entries[0].leafInput; // -> <Buffer ...>
|
|
1688
1822
|
*/
|
|
1689
1823
|
async function getEntries(opts) {
|
|
1690
|
-
|
|
1824
|
+
var door = _clientDoor(opts || {}, null);
|
|
1825
|
+
opts = door.o;
|
|
1691
1826
|
var start = guard.range.uint64(opts.start, _ctErr, "ct/bad-input", "opts.start");
|
|
1692
1827
|
var end = guard.range.uint64(opts.end, _ctErr, "ct/bad-input", "opts.end");
|
|
1693
1828
|
if (end < start) {
|
|
@@ -1698,7 +1833,7 @@ async function getEntries(opts) {
|
|
|
1698
1833
|
throw _ctErr("ct/bad-input", "a range of " + requested + " entries exceeds the " +
|
|
1699
1834
|
C.LIMITS.CT_MAX_ENTRIES_PER_REQUEST + "-entry request cap");
|
|
1700
1835
|
}
|
|
1701
|
-
var doc = await _clientJson(
|
|
1836
|
+
var doc = await _clientJson(door,"get-entries", "entries", { start: _stringOf(start), end: _stringOf(end) });
|
|
1702
1837
|
var raw = doc.entries;
|
|
1703
1838
|
if (!intrinsic.isArray(raw)) throw _ctErr("ct/bad-entries", "the entries field is not an array");
|
|
1704
1839
|
if (intrinsic.BigInt(raw.length) > requested) {
|
|
@@ -1743,8 +1878,9 @@ async function getEntries(opts) {
|
|
|
1743
1878
|
* r.certificates.length; // -> 2
|
|
1744
1879
|
*/
|
|
1745
1880
|
async function getRoots(opts) {
|
|
1746
|
-
|
|
1747
|
-
var
|
|
1881
|
+
/** @internal This message carries no options of its own, so the door's record is all that is used. */
|
|
1882
|
+
var door = _clientDoor(opts || {}, null);
|
|
1883
|
+
var doc = await _clientJson(door, "get-roots", "roots");
|
|
1748
1884
|
return guard.verdict.of({ certificates: _b64Array(doc, "certificates", "ct/bad-roots", "roots", undefined, C.LIMITS.CT_MAX_ROOTS) });
|
|
1749
1885
|
}
|
|
1750
1886
|
|
|
@@ -1826,7 +1962,8 @@ async function addPreChain(opts) {
|
|
|
1826
1962
|
* The chain encoding, the response shape, and the refusal when the receipt does not verify are one
|
|
1827
1963
|
* rule stated once. */
|
|
1828
1964
|
async function _addChain(opts, message, entryType) {
|
|
1829
|
-
|
|
1965
|
+
var door = _clientDoor(opts || {}, "logKey");
|
|
1966
|
+
opts = door.o;
|
|
1830
1967
|
/** @internal ONE read of the option, used by the presence check here and by the copy below. Reading it
|
|
1831
1968
|
* twice let an accessor answer the two separately, presenting a non-null value to the check and an
|
|
1832
1969
|
* attacker's SPKI to the copy, and the returned receipt was then verified against a key the caller
|
|
@@ -1859,7 +1996,7 @@ async function _addChain(opts, message, entryType) {
|
|
|
1859
1996
|
: { entryType: 1, tbsCertificate: _snap(opts.tbsCertificate, "opts.tbsCertificate"),
|
|
1860
1997
|
issuerKeyHash: _snap(opts.issuerKeyHash, "opts.issuerKeyHash") };
|
|
1861
1998
|
var pinnedKey = _snap(suppliedLogKey, "opts.logKey");
|
|
1862
|
-
var doc = await _clientJson(
|
|
1999
|
+
var doc = await _clientJson(door,message, "SCT", null, _jsonStringify({ chain: chain }));
|
|
1863
2000
|
var version = doc.sct_version;
|
|
1864
2001
|
if (version !== 0) {
|
|
1865
2002
|
throw _ctErr("ct/bad-sct", "the log returned sct_version " + guard.text.showValue(version) +
|
|
@@ -2056,9 +2193,16 @@ async function fetchLogList(opts) {
|
|
|
2056
2193
|
* once to parse and once to fetch, so an accessor sent the request somewhere the parse never saw. */
|
|
2057
2194
|
opts = guard.identifier.optionsObject(opts, _ctErr, "ct/bad-input", "pki.ct.fetchLogList options");
|
|
2058
2195
|
guard.identifier.assertKnownKeys(opts, KNOWN_FETCH_OPTS, _ctErr, "ct/bad-input", "unknown opts field ");
|
|
2196
|
+
/** @internal Every declared option taken before anything derives from one, for the reason
|
|
2197
|
+
* `_clientDoor` does it: there is no safe order, because reading a NESTED value inside any of them
|
|
2198
|
+
* runs the caller's code and that code can rewrite the ones not yet taken. Parsing the url first let
|
|
2199
|
+
* its `toString` empty the pinned anchor list; taking the tls first let a getter on `tls.anchors`
|
|
2200
|
+
* rewrite the url and the signer key. Taking the top level first closes both. */
|
|
2201
|
+
opts = _settleDecisive(_takeTopLevel(opts, KNOWN_FETCH_OPTS), "signerKey");
|
|
2059
2202
|
/** @internal Read once, at the door, and carried from here. Asking again would let an option that
|
|
2060
2203
|
* answers differently on each read hand back something other than the value that was checked. */
|
|
2061
2204
|
var transport = _assertTransport(opts);
|
|
2205
|
+
var fetchTls = _tlsForFetch(opts);
|
|
2062
2206
|
/** @internal ONE read, and both the presence check and the copy decide from it. Reading it twice let an
|
|
2063
2207
|
* accessor present a non-null value to the check and an attacker's SPKI to the copy, so the list was
|
|
2064
2208
|
* verified under a distributor key the caller never pinned.
|
|
@@ -2070,14 +2214,17 @@ async function fetchLogList(opts) {
|
|
|
2070
2214
|
var pinnedSigner = _toBuffer(suppliedSignerKey, "opts.signerKey", _ctErr, "ct/bad-input");
|
|
2071
2215
|
if (opts.url == null) throw _ctErr("ct/bad-input", "opts.url is required -- the log_list.json URL (there is no baked-in vendor URL)");
|
|
2072
2216
|
var jsonParsed = _parseHttpsUrl(opts.url, "URL");
|
|
2073
|
-
var jsonUrl = jsonParsed
|
|
2217
|
+
var jsonUrl = _urlHref(jsonParsed);
|
|
2074
2218
|
/** @internal ONE read: the presence test and the parse decide from the same value. */
|
|
2075
2219
|
var suppliedSigUrl = opts.sigUrl;
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
*
|
|
2080
|
-
var
|
|
2220
|
+
/** @internal Through the captured accessor like every other read of a parsed destination here. No
|
|
2221
|
+
* caller code can run between the settle above and this line, the bag refusing an accessor and both
|
|
2222
|
+
* urls having to be strings, so this one has no vector of its own; it is read this way because a
|
|
2223
|
+
* site that reads a destination off the prototype is the shape that has to stay absent. */
|
|
2224
|
+
var sigUrl = suppliedSigUrl != null
|
|
2225
|
+
? _urlHref(_parseHttpsUrl(suppliedSigUrl, "signature URL"))
|
|
2226
|
+
: _sigUrlFor(jsonParsed);
|
|
2227
|
+
if (!_sameOrigin(jsonUrl, sigUrl)) throw _ctErr("ct/bad-input", "opts.sigUrl must share the log-list URL's origin (" + _urlOrigin(jsonParsed) + "); a cross-origin detached-signature host is not supported");
|
|
2081
2228
|
if (!transport) {
|
|
2082
2229
|
var hasAnchors = fetchTls.anchors !== undefined && fetchTls.anchors !== null && !(intrinsic.isArray(fetchTls.anchors) && fetchTls.anchors.length === 0);
|
|
2083
2230
|
if (!hasAnchors && fetchTls.useSystemStore !== true) throw _ctErr("ct/no-trust-anchors", "no explicit trust anchor and tls.useSystemStore not set to true -- refusing an unpinned CT server (RFC 6962 sec. 3.2)");
|
package/lib/est.js
CHANGED
|
@@ -80,6 +80,20 @@ var _toLowerCase = intrinsic.uncurry(String.prototype.toLowerCase);
|
|
|
80
80
|
* and both build from the RECEIVER, so reading the global binding at the call decides whether the body
|
|
81
81
|
* runs and whether a refusal stays a refusal. Taken at load. */
|
|
82
82
|
var _Promise = intrinsic.Promise;
|
|
83
|
+
/** @internal The URL parser, taken at load: what it returns names the host an enrollment request goes
|
|
84
|
+
* to, and a redirect is resolved against the current operation URL with it. The conversion the parser
|
|
85
|
+
* is handed is taken with it, since a replacement answers with a host of its own. */
|
|
86
|
+
var _URL = intrinsic.URL;
|
|
87
|
+
var _stringOf = intrinsic.String;
|
|
88
|
+
/** @internal And the accessors its result is read through: capturing the constructor does not capture
|
|
89
|
+
* the prototype the value reads its parts off, and each part here decides something, the scheme
|
|
90
|
+
* whether the request is allowed, the origin whether credentials may still be sent after a redirect. */
|
|
91
|
+
var _urlHref = intrinsic.urlHref;
|
|
92
|
+
var _urlOrigin = intrinsic.urlOrigin;
|
|
93
|
+
var _urlProtocol = intrinsic.urlProtocol;
|
|
94
|
+
var _urlPathname = intrinsic.urlPathname;
|
|
95
|
+
var _urlSearch = intrinsic.urlSearch;
|
|
96
|
+
var _urlHash = intrinsic.urlHash;
|
|
83
97
|
var _promiseResolve = intrinsic.uncurry(intrinsic.promiseResolve);
|
|
84
98
|
var _promiseReject = intrinsic.uncurry(intrinsic.promiseReject);
|
|
85
99
|
var pkix = require("./schema-pkix");
|
|
@@ -803,25 +817,28 @@ function _csrDer(input) {
|
|
|
803
817
|
|
|
804
818
|
function _parseUrl(urlStr) {
|
|
805
819
|
var url;
|
|
806
|
-
try { url = new
|
|
820
|
+
try { url = new _URL(_stringOf(urlStr)); }
|
|
807
821
|
catch (e) { throw E("est/bad-url", "the EST server URL did not parse: " + String(urlStr), e); }
|
|
808
|
-
|
|
822
|
+
var scheme = _urlProtocol(url);
|
|
823
|
+
if (scheme !== "https:") throw E("est/insecure-url", "EST requires https (RFC 7030 sec. 3.3), got " + scheme + " for " + urlStr);
|
|
809
824
|
return url;
|
|
810
825
|
}
|
|
811
826
|
|
|
827
|
+
/** @internal The settings this client forwards to its transport. */
|
|
828
|
+
var TLS_SETTINGS = ["anchors", "useSystemStore", "cert", "key", "minVersion", "servername", "checkServerIdentity"];
|
|
812
829
|
function _tlsForRequest(opts) {
|
|
813
|
-
|
|
814
|
-
return { anchors: t.anchors, useSystemStore: t.useSystemStore, cert: t.cert, key: t.key, minVersion: t.minVersion, servername: t.servername, checkServerIdentity: t.checkServerIdentity };
|
|
830
|
+
return guard.identifier.pickSettings(opts.tls, TLS_SETTINGS);
|
|
815
831
|
}
|
|
816
832
|
|
|
817
833
|
function _redirectTarget(current, location, method, opts) {
|
|
818
834
|
if (location == null || String(location).trim() === "") throw E("est/http-error", "a redirect response carried no Location header");
|
|
819
835
|
var resolved;
|
|
820
|
-
try { resolved = new
|
|
836
|
+
try { resolved = new _URL(_stringOf(location), _urlHref(current)); }
|
|
821
837
|
catch (e) { throw E("est/bad-url", "a redirect Location did not parse: " + location, e); }
|
|
822
|
-
|
|
838
|
+
var resolvedScheme = _urlProtocol(resolved);
|
|
839
|
+
if (resolvedScheme !== "https:") throw E("est/insecure-redirect", "a redirect to a non-https URL is refused (RFC 7030 sec. 3.2.1): " + resolvedScheme);
|
|
823
840
|
var safeMethod = method === "GET" || method === "HEAD";
|
|
824
|
-
if (resolved
|
|
841
|
+
if (_urlOrigin(resolved) !== _urlOrigin(current) && !safeMethod && !opts.allowCrossOriginRedirect) {
|
|
825
842
|
throw E("est/cross-origin-redirect", "a cross-origin redirect on a " + method + " needs opts.allowCrossOriginRedirect (RFC 7030 sec. 3.2.1)");
|
|
826
843
|
}
|
|
827
844
|
return resolved;
|
|
@@ -884,10 +901,10 @@ function _drive(method, url, body, headers, opts, transport, budgets) {
|
|
|
884
901
|
var staleRetries = 0;
|
|
885
902
|
var authSpaces = 0;
|
|
886
903
|
var lastDigestNonce = null;
|
|
887
|
-
var initialOrigin = url
|
|
904
|
+
var initialOrigin = _urlOrigin(url);
|
|
888
905
|
function _tlsFor(u) {
|
|
889
906
|
var t = budgets.tls;
|
|
890
|
-
if (t && u
|
|
907
|
+
if (t && _urlOrigin(u) !== initialOrigin) {
|
|
891
908
|
t = Object.assign({}, t);
|
|
892
909
|
t.cert = null; t.key = null; t.servername = null;
|
|
893
910
|
}
|
|
@@ -899,20 +916,26 @@ function _drive(method, url, body, headers, opts, transport, budgets) {
|
|
|
899
916
|
var digestSent = false;
|
|
900
917
|
function _headersFor(u) {
|
|
901
918
|
digestSent = false;
|
|
902
|
-
|
|
919
|
+
/** @internal The origin and the request target are each read ONCE and both the protection-space
|
|
920
|
+
* test and the answer are built from those locals: the credential is bound to the target it is
|
|
921
|
+
* computed over, so a part answering two reads differently would answer for one target and be
|
|
922
|
+
* sent to another. */
|
|
923
|
+
var uOrigin = _urlOrigin(u);
|
|
924
|
+
var uTarget = _urlPathname(u) + _urlSearch(u);
|
|
925
|
+
if (uOrigin !== initialOrigin || (!authValue && !digestChallenge)) return headers;
|
|
903
926
|
var hh = guard.identifier.ownOptions(headers);
|
|
904
927
|
if (digestChallenge) {
|
|
905
|
-
if (!httpDigest.inProtectionSpace(digestChallenge,
|
|
928
|
+
if (!httpDigest.inProtectionSpace(digestChallenge, uOrigin, uTarget)) return headers;
|
|
906
929
|
var nkey = digestChallenge.nonce;
|
|
907
930
|
digestNcByNonce[nkey] = (digestNcByNonce[nkey] || 0) + 1;
|
|
908
|
-
hh.authorization = httpDigest.answer(digestChallenge, { method: method, uri:
|
|
931
|
+
hh.authorization = httpDigest.answer(digestChallenge, { method: method, uri: uTarget, username: _authUser(opts), password: _authPass(opts), body: body, nc: digestNcByNonce[nkey], policy: _digestPolicy(opts) }, E);
|
|
909
932
|
digestSent = true;
|
|
910
933
|
} else { hh.authorization = authValue; }
|
|
911
934
|
return hh;
|
|
912
935
|
}
|
|
913
936
|
function step() {
|
|
914
937
|
var hopBody = (typeof body === "function" && budgets.onAttempt) ? budgets.onAttempt() : body;
|
|
915
|
-
return guard.async.invoked(transport, [{ method: method, url: url
|
|
938
|
+
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) {
|
|
916
939
|
res = res || {};
|
|
917
940
|
var blen = _bodyLen(res);
|
|
918
941
|
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)");
|
|
@@ -933,7 +956,7 @@ function _drive(method, url, body, headers, opts, transport, budgets) {
|
|
|
933
956
|
return step();
|
|
934
957
|
}
|
|
935
958
|
if (status === 401) {
|
|
936
|
-
if (url
|
|
959
|
+
if (_urlOrigin(url) !== initialOrigin) throw E("est/auth-required", "refusing to send HTTP credentials to a redirected origin (RFC 7030 sec. 3.6)");
|
|
937
960
|
var www = String(h["www-authenticate"] || "");
|
|
938
961
|
if (www.length > constants.LIMITS.HTTP_AUTH_HEADER_MAX_BYTES) throw E("est/auth-required", "the WWW-Authenticate header exceeds the " + constants.LIMITS.HTTP_AUTH_HEADER_MAX_BYTES + "-byte cap (RFC 7030 sec. 3.2.3)");
|
|
939
962
|
var scheme = _authScheme(opts);
|
|
@@ -945,8 +968,8 @@ function _drive(method, url, body, headers, opts, transport, budgets) {
|
|
|
945
968
|
selPol.preferStale = digestSent;
|
|
946
969
|
selPol.priorNonce = lastDigestNonce;
|
|
947
970
|
selPol.priorRealm = digestChallenge && digestChallenge.realm;
|
|
948
|
-
selPol.requestOrigin = url
|
|
949
|
-
selPol.requestTarget = url
|
|
971
|
+
selPol.requestOrigin = _urlOrigin(url);
|
|
972
|
+
selPol.requestTarget = _urlPathname(url) + _urlSearch(url);
|
|
950
973
|
var ch = httpDigest.parseChallenge(www, E, "est/digest-bad-challenge", selPol);
|
|
951
974
|
if (!ch) throw E("est/auth-required", "the server offered no usable Digest challenge: " + www);
|
|
952
975
|
if (digestSent && digestChallenge && ch.realm === digestChallenge.realm) {
|
|
@@ -980,13 +1003,13 @@ function _drive(method, url, body, headers, opts, transport, budgets) {
|
|
|
980
1003
|
* answer, so a verb that holds one passes it and this reads the option only when none was held. */
|
|
981
1004
|
function _client(op, method, baseUrl, body, headers, opts, proxySnap, onAttempt, checked) {
|
|
982
1005
|
var base;
|
|
983
|
-
try { base = new
|
|
1006
|
+
try { base = new _URL(_stringOf(baseUrl)); }
|
|
984
1007
|
catch (e) { throw E("est/bad-url", "the EST base URL did not parse: " + guard.text.showValue(baseUrl), e); }
|
|
985
|
-
if (base
|
|
1008
|
+
if (_urlSearch(base) || _urlHash(base)) throw E("est/bad-url", "the EST base URL must not carry a query or fragment component (RFC 7030 sec. 3.2.2)");
|
|
986
1009
|
var url = _parseUrl(paths(baseUrl, { label: opts.label })[op]);
|
|
987
1010
|
var transport = checked !== undefined ? checked : _assertTransport(opts);
|
|
988
1011
|
if (!transport) {
|
|
989
|
-
var t = opts.tls
|
|
1012
|
+
var t = opts.tls != null ? opts.tls : intrinsic.create(null);
|
|
990
1013
|
var hasAnchors = t.anchors !== undefined && t.anchors !== null && !(Array.isArray(t.anchors) && t.anchors.length === 0);
|
|
991
1014
|
if (!hasAnchors && t.useSystemStore !== true) throw E("est/no-trust-anchors", "no explicit trust anchor and tls.useSystemStore not set to true -- refusing an unpinned server (RFC 7030 sec. 3.6)");
|
|
992
1015
|
transport = httpTransport.https({ E: E, errPrefix: "est" });
|
package/lib/guard-bytes.js
CHANGED
|
@@ -247,14 +247,29 @@ function source(input, ErrorClass, code, label) {
|
|
|
247
247
|
throw _raise(ErrorClass, code, label + ": expected a BufferSource (ArrayBuffer / TypedArray / Buffer)");
|
|
248
248
|
}
|
|
249
249
|
|
|
250
|
+
/** @internal Into a store of its own, not through `Buffer.from`. A small `Buffer.from` takes its result
|
|
251
|
+
* out of the 64 KiB allocation pool, so a caller whose own input came from that pool holds a view of
|
|
252
|
+
* the whole store and the copy lands inside it at another offset. MEASURED through a shipped verb: a
|
|
253
|
+
* caller's transport, which runs after the key is copied and before the signature is checked, found
|
|
254
|
+
* the copy in the pool it shared, wrote an attacker's SPKI over it, and the forged tree head was
|
|
255
|
+
* accepted. Every consumer of this door relies on the copy being out of the caller's reach, which is
|
|
256
|
+
* the whole reason the copy is taken. The deep copy below has always allocated its own store; this is
|
|
257
|
+
* the same allocation. */
|
|
258
|
+
function _ownedCopy(src) {
|
|
259
|
+
var len = _lengthOf(src);
|
|
260
|
+
var owned = new _ArrayBuffer(len);
|
|
261
|
+
_taSet(new _Uint8Array(owned), src);
|
|
262
|
+
return _bufferFrom(owned, 0, len);
|
|
263
|
+
}
|
|
264
|
+
|
|
250
265
|
// @enforced-by behavioral -- a copy has no rename-proof code shape to detect (any
|
|
251
266
|
function snapshot(input, ErrorClass, code, label) {
|
|
252
|
-
return
|
|
267
|
+
return _ownedCopy(view(input, ErrorClass, code, label));
|
|
253
268
|
}
|
|
254
269
|
|
|
255
270
|
// @enforced-by behavioral -- a copy has no rename-proof shape to detect; the guard
|
|
256
271
|
function snapshotSource(input, ErrorClass, code, label) {
|
|
257
|
-
return
|
|
272
|
+
return _ownedCopy(source(input, ErrorClass, code, label));
|
|
258
273
|
}
|
|
259
274
|
|
|
260
275
|
// @enforced-by behavioral -- a copy has no rename-proof code shape to detect (any `Buffer.from(x)`
|
package/lib/guard-identifier.js
CHANGED
|
@@ -124,6 +124,27 @@ function optionNames(obj, known) {
|
|
|
124
124
|
return out;
|
|
125
125
|
}
|
|
126
126
|
|
|
127
|
+
/** @internal The named settings of a caller's nested record, taken into a record with NO prototype.
|
|
128
|
+
* Each module declares its own `names`, because which settings a verb forwards is that verb's
|
|
129
|
+
* statement and a shared list would name settings for a verb that does not read them; what is shared
|
|
130
|
+
* is that the result cannot inherit. An absent record yields the same shape with every name
|
|
131
|
+
* undefined, where the empty object literal it replaces inherited from `Object.prototype`: MEASURED,
|
|
132
|
+
* a `useSystemStore` installed there was read as a caller's opt-in and the refusal of a server with
|
|
133
|
+
* no pinned anchor was skipped. A name is read off the record itself, so a caller's own defaults
|
|
134
|
+
* object on the chain still answers for it.
|
|
135
|
+
*
|
|
136
|
+
* @enforced-by behavioral -- a read into a fresh record has no rename-proof shape of its own; the
|
|
137
|
+
* conformance vectors are each module's own refusal of an unpinned server under a polluted prototype.
|
|
138
|
+
*/
|
|
139
|
+
function pickSettings(record, names) {
|
|
140
|
+
var out = _create(null);
|
|
141
|
+
var src = record === null || record === undefined ? null : record;
|
|
142
|
+
for (var i = 0; i < names.length; i++) {
|
|
143
|
+
out[names[i]] = src === null ? undefined : src[names[i]];
|
|
144
|
+
}
|
|
145
|
+
return out;
|
|
146
|
+
}
|
|
147
|
+
|
|
127
148
|
/** @internal A caller's options bag copied onto a prototype-less object, keyed by every own string
|
|
128
149
|
* property, not the enumerable ones alone. Each is read once, so an accessor-backed option
|
|
129
150
|
* answers the copy and not the later reads. Inherited properties are left behind: the copy is
|
|
@@ -609,6 +630,7 @@ module.exports = intrinsic.freeze({
|
|
|
609
630
|
snapshotOptions: snapshotOptions,
|
|
610
631
|
optionNames: optionNames,
|
|
611
632
|
ownOptions: ownOptions,
|
|
633
|
+
pickSettings: pickSettings,
|
|
612
634
|
readableNames: readableNames,
|
|
613
635
|
readableIndices: readableIndices
|
|
614
636
|
});
|