@blamejs/pki 0.8.55 → 0.8.56

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 (50) hide show
  1. package/CHANGELOG.md +66 -0
  2. package/lib/acme.js +2 -2
  3. package/lib/asn1-der.js +17 -5
  4. package/lib/attrcert-verify.js +1 -1
  5. package/lib/cmc-verify.js +1 -1
  6. package/lib/cmp-session.js +2 -2
  7. package/lib/cmp-verify.js +2 -2
  8. package/lib/cms-decrypt.js +4 -1
  9. package/lib/cms-encrypt.js +4 -1
  10. package/lib/cms-sign.js +26 -12
  11. package/lib/cms-verify.js +1 -1
  12. package/lib/composite-kem.js +30 -12
  13. package/lib/composite-sig.js +42 -23
  14. package/lib/constants.js +7 -2
  15. package/lib/crl-verify.js +1 -1
  16. package/lib/crmf-sign.js +72 -43
  17. package/lib/crmf-verify.js +1 -1
  18. package/lib/csr-verify.js +1 -1
  19. package/lib/ct.js +2 -2
  20. package/lib/edwards-point.js +1 -1
  21. package/lib/est.js +3 -3
  22. package/lib/guard-async.js +22 -1
  23. package/lib/guard-bytes.js +6 -3
  24. package/lib/guard-identifier.js +1 -1
  25. package/lib/guard-intrinsic.js +474 -0
  26. package/lib/http-digest.js +146 -62
  27. package/lib/http-transport.js +82 -17
  28. package/lib/identity-match.js +2 -2
  29. package/lib/merkle.js +2 -2
  30. package/lib/ocsp-request-verify.js +1 -1
  31. package/lib/ocsp.js +46 -22
  32. package/lib/path-validate.js +169 -76
  33. package/lib/pbes2.js +14 -7
  34. package/lib/schema-attrcert.js +2 -2
  35. package/lib/schema-cmc.js +2 -2
  36. package/lib/schema-cmp.js +2 -2
  37. package/lib/schema-cms.js +2 -2
  38. package/lib/schema-crmf.js +2 -2
  39. package/lib/schema-csr.js +2 -2
  40. package/lib/schema-pkix.js +3 -3
  41. package/lib/schema-x509.js +2 -2
  42. package/lib/sigstore.js +1 -1
  43. package/lib/tlog.js +7 -4
  44. package/lib/trust.js +187 -104
  45. package/lib/tsp-sign.js +1 -1
  46. package/lib/webauthn-mds.js +257 -121
  47. package/lib/webauthn.js +327 -164
  48. package/lib/webcrypto.js +10 -3
  49. package/package.json +1 -1
  50. package/sbom.cdx.json +6 -6
@@ -8,6 +8,7 @@ var frameworkError = require("./framework-error");
8
8
  var x509 = require("./schema-x509");
9
9
  var guard = require("./guard-all");
10
10
  var intrinsic = require("./guard-intrinsic");
11
+ var _nodeCrypto = require("crypto");
11
12
  /** @internal The seed of the fold below is a RESOLVED promise carrying `false`, the verdict for a chain
12
13
  * that matched no anchor, and `Promise.resolve` builds from its RECEIVER. Read from the global binding,
13
14
  * that seed is constructed by whatever the binding holds, so a replacement settling truthy starts the
@@ -18,6 +19,19 @@ var _hasOwn = intrinsic.hasOwn;
18
19
  var _reverse = intrinsic.reverse;
19
20
  var _arrSlice = intrinsic.arraySlice;
20
21
  var _charAt = intrinsic.uncurry(String.prototype.charAt);
22
+ /** @internal The string operations an identifier is normalized and sliced with. A metadata lookup key
23
+ * is built out of these, and the entry the key finds supplies both the status reports that refuse a
24
+ * revoked authenticator and the attestation roots a chain is anchored to, so an operation answering
25
+ * with another identifier selects another entry's status and another entry's roots. */
26
+ var _strToLowerCase = intrinsic.toLowerCase;
27
+ var _strSlice = intrinsic.uncurry(String.prototype.slice);
28
+ var _strSplit = intrinsic.uncurry(String.prototype.split);
29
+ /** @internal The hash constructor and the methods of the object it returns. What a digest here
30
+ * produces is a metadata lookup key and the identity two certificates are told apart by, so a
31
+ * replaced constructor, update or digest decides which entry governs an attestation. */
32
+ var _createHash = _nodeCrypto.createHash;
33
+ var _hashUpdate = intrinsic.uncurry(_nodeCrypto.createHash("sha256").update);
34
+ var _hashDigest = intrinsic.uncurry(_nodeCrypto.createHash("sha256").digest);
21
35
  var jose = require("./jose");
22
36
  var rfc3339 = require("./rfc3339");
23
37
  var constants = require("./constants");
@@ -25,7 +39,6 @@ var pathValidate = require("./path-validate");
25
39
  var signScheme = require("./sign-scheme");
26
40
  var edwardsPoint = require("./edwards-point");
27
41
  var webcrypto = require("./webcrypto");
28
- var nodeCrypto = require("crypto");
29
42
 
30
43
  var oid = require("./oid");
31
44
  var pkix = require("./schema-pkix");
@@ -50,9 +63,13 @@ var C = constants.LIMITS;
50
63
  * and carries genuine doubles: the published BLOB has members whose values are not integers, among them
51
64
  * the biometric `selfAttestedFRR` and `selfAttestedFAR` rates and `iAPARThreshold`. A document-wide rule
52
65
  * refuses the real catalogue. */
53
- var EXACT_INTEGER_BLOB_MEMBERS = intrinsic.assign(intrinsic.create(null), { no: 1 });
66
+ var EXACT_INTEGER_BLOB_MEMBERS = intrinsic.freeze(intrinsic.assign(intrinsic.create(null), { no: 1 }));
54
67
 
55
- var LEAF_SCHEMES_BY_SPKI_ALG = Object.assign(Object.create(null), {
68
+ /** @internal Sealed all the way down. Each row says which signature schemes a leaf key of that type
69
+ * may be used with, and the refusal reads the scheme straight out of the row, so a row that still
70
+ * inherits admits a scheme the key type does not support: `RSASSA-PKCS1-v1_5` installed on
71
+ * `Object.prototype` is read as present on the Ed25519 row. */
72
+ var LEAF_SCHEMES_BY_SPKI_ALG = _sealTable(intrinsic.assign(intrinsic.create(null), {
56
73
  ecPublicKey: { ECDSA: 1 },
57
74
  rsaEncryption: { "RSASSA-PKCS1-v1_5": 1, "RSA-PSS": 1 },
58
75
  rsassaPss: { "RSA-PSS": 1 },
@@ -61,13 +78,13 @@ var LEAF_SCHEMES_BY_SPKI_ALG = Object.assign(Object.create(null), {
61
78
  "id-ml-dsa-44": { "ML-DSA-44": 1 },
62
79
  "id-ml-dsa-65": { "ML-DSA-65": 1 },
63
80
  "id-ml-dsa-87": { "ML-DSA-87": 1 },
64
- });
81
+ }));
65
82
 
66
83
  function _schemeE(kind, message, cause) { return new WebauthnError("webauthn/" + kind, message, cause); }
67
84
 
68
85
  function _deriveBlobAlgs() {
69
- var out = Object.create(null);
70
- jose.sigAlgs().forEach(function (row) {
86
+ var out = intrinsic.create(null);
87
+ intrinsic.forEach(jose.sigAlgs(), function (row) {
71
88
  if (row.kty === "EC") {
72
89
  out[row.alg] = { scheme: "ECDSA", hash: row.hash,
73
90
  imp: { name: "ECDSA", namedCurve: row.crv }, ver: { name: "ECDSA", hash: row.hash } };
@@ -85,38 +102,65 @@ function _deriveBlobAlgs() {
85
102
  });
86
103
  return out;
87
104
  }
88
- var BLOB_ALGS = _deriveBlobAlgs();
105
+ /** @internal Sealed all the way down, because this table is EXPORTED and its NESTED rows carry the
106
+ * import and verify parameters a BLOB signature is checked under. A caller holding the object could
107
+ * redefine `RS256`'s hash, and the leaf key would then be imported for a weaker digest than the
108
+ * header declares. Freezing alone is not enough: a row written as an object literal still INHERITS,
109
+ * so `fromLeaf` installed on `Object.prototype` is read off every frozen row and sends the strongest
110
+ * algorithm down the take-it-from-the-leaf branch, which is why the prototype goes too. */
111
+ var BLOB_ALGS = _sealTable(_deriveBlobAlgs());
89
112
 
90
113
  function _blobAlgParams(algRow, leafAlgName) {
91
114
  if (!algRow.fromLeaf) return algRow;
92
- return { scheme: algRow.scheme, hash: null, imp: { name: leafAlgName }, ver: { name: leafAlgName } };
115
+ return _sealTable({ scheme: algRow.scheme, hash: null, imp: { name: leafAlgName }, ver: { name: leafAlgName } });
93
116
  }
94
117
 
95
- var DISQUALIFYING = Object.assign(Object.create(null), {
118
+ /** @internal Frozen, because this table is EXPORTED and membership in it is what makes a status report
119
+ * disqualifying. A caller holding the object could redefine `REVOKED` so the lookup stops recognizing
120
+ * revocation, and `statusDenied` then permits a revoked authenticator with every operation around the
121
+ * lookup still captured. */
122
+ var DISQUALIFYING = intrinsic.freeze(intrinsic.assign(intrinsic.create(null), {
96
123
  REVOKED: 1, ATTESTATION_KEY_COMPROMISE: 1, USER_KEY_REMOTE_COMPROMISE: 1,
97
124
  USER_KEY_PHYSICAL_COMPROMISE: 1, USER_VERIFICATION_BYPASS: 1,
98
- });
125
+ }));
99
126
 
100
- var CERT_SCOPED_STATUS = Object.assign(Object.create(null), { ATTESTATION_KEY_COMPROMISE: 1 });
127
+ var CERT_SCOPED_STATUS = intrinsic.freeze(intrinsic.assign(intrinsic.create(null), { ATTESTATION_KEY_COMPROMISE: 1 }));
101
128
 
102
- var _BLOB_OPTS = Object.assign(Object.create(null), {
129
+ var _BLOB_OPTS = intrinsic.assign(intrinsic.create(null), {
103
130
  rootCertificates: 1, time: 1, previousNo: 1, requireRollbackCheck: 1, allowStale: 1,
104
131
  statusPolicy: 1, rejectUnknownStatus: 1,
105
132
  });
106
133
 
107
- function _isPlainObject(v) { return !!v && typeof v === "object" && !Array.isArray(v); }
134
+ function _isPlainObject(v) { return !!v && typeof v === "object" && !intrinsic.isArray(v); }
108
135
 
136
+ /** @internal Both markers are read through captures. Membership here is what says an object is a
137
+ * verified BLOB result rather than a raw one a caller assembled, so a replacement answering `true`
138
+ * admits unverified metadata wherever `opts.metadata` is accepted; the second binds an entry to the
139
+ * result it was read out of, and one answering with the caller's own object passes an entry off as
140
+ * belonging to metadata that never carried it. */
109
141
  var _verifiedResults = new WeakSet();
110
- function isVerifiedResult(v) { return _isPlainObject(v) && _verifiedResults.has(v); }
142
+ function isVerifiedResult(v) { return _isPlainObject(v) && intrinsic.weakSetHas(_verifiedResults, v); }
111
143
 
112
144
  var _entryOrigin = new WeakMap();
113
- function _isEntryOf(entry, metadata) { return _entryOrigin.get(entry) === metadata; }
145
+ function _isEntryOf(entry, metadata) { return intrinsic.weakGet(_entryOrigin, entry) === metadata; }
146
+
147
+ /** @internal A decision table and every row in it, with no prototype and nothing writable. Each row is
148
+ * looked up by a name taken from the input, so an inherited property answers for a row the table does
149
+ * not hold and a writable one answers differently than the table says. Detaching the prototype has to
150
+ * come first, since a frozen object's prototype can no longer be set. Arrays keep theirs: a row list
151
+ * is still indexed and walked. */
152
+ function _sealTable(v) {
153
+ if (!v || typeof v !== "object" || intrinsic.isView(v)) return v;
154
+ if (!intrinsic.isArray(v)) intrinsic.setPrototypeOf(v, null);
155
+ intrinsic.forEach(intrinsic.getOwnPropertyNames(v), function (k) { _sealTable(v[k]); });
156
+ return intrinsic.freeze(v);
157
+ }
114
158
 
115
159
  function _deepFreeze(v, depth) {
116
- if (!v || typeof v !== "object" || Object.isFrozen(v) || ArrayBuffer.isView(v)) return v;
160
+ if (!v || typeof v !== "object" || intrinsic.isFrozen(v) || intrinsic.isView(v)) return v;
117
161
  if (depth > C.JSON_MAX_DEPTH) return v;
118
- Object.freeze(v);
119
- Object.keys(v).forEach(function (k) { _deepFreeze(v[k], depth + 1); });
162
+ intrinsic.freeze(v);
163
+ intrinsic.forEach(intrinsic.keys(v), function (k) { _deepFreeze(v[k], depth + 1); });
120
164
  return v;
121
165
  }
122
166
  function _isAaguid(s) {
@@ -132,9 +176,9 @@ function _isAaguid(s) {
132
176
  return true;
133
177
  }
134
178
  var ZERO_AAGUID = "00000000-0000-0000-0000-000000000000";
135
- var UNDERSTOOD_HEADER = Object.assign(Object.create(null), {
179
+ var UNDERSTOOD_HEADER = intrinsic.freeze(intrinsic.assign(intrinsic.create(null), {
136
180
  alg: 1, typ: 1, cty: 1, crit: 1, jku: 1, jwk: 1, kid: 1, x5u: 1, x5c: 1, x5t: 1, "x5t#S256": 1,
137
- });
181
+ }));
138
182
 
139
183
  function _assertLeafSigns(leaf) {
140
184
  var ku = pkix.keyUsageOf(_KU_NS, leaf, _err, "webauthn/bad-att-cert", "metadata BLOB x5c leaf");
@@ -143,9 +187,12 @@ function _assertLeafSigns(leaf) {
143
187
  }
144
188
  }
145
189
 
190
+ /** @internal The key comparison comes from the capture. Answering `true` makes a certificate that
191
+ * merely carries the anchor's subject count as the anchor itself, and a single-certificate chain is
192
+ * then reported trusted with no path validation run over it. */
146
193
  function _isAnchorItself(cert, anchor) {
147
194
  return guard.name.dnEqual(cert.subject.rdns, anchor.subject.rdns, _err, "webauthn/bad-att-cert", "the anchor subject") &&
148
- cert.subjectPublicKeyInfo.bytes.equals(anchor.subjectPublicKeyInfo.bytes);
195
+ intrinsic.bufferEquals(cert.subjectPublicKeyInfo.bytes, anchor.subjectPublicKeyInfo.bytes);
149
196
  }
150
197
 
151
198
  function _asCert(v, label) {
@@ -160,7 +207,10 @@ function verifyMetadataBlob(blob, opts) {
160
207
  }
161
208
 
162
209
  function _verifyMetadataBlob(blob, opts) {
163
- opts = opts || {};
210
+ /** @internal A caller who passes no options gets a record with NO prototype. Defaulted to `{}` it
211
+ * inherits, and the snapshot below materializes every declared option name, so `allowStale`
212
+ * installed on `Object.prototype` reached the freshness check as an opt-in the caller never made. */
213
+ opts = opts || intrinsic.create(null);
164
214
  if (!_isPlainObject(opts)) throw _err("webauthn/bad-input", "opts must be an object");
165
215
  guard.identifier.assertKnownKeys(opts, _BLOB_OPTS, _err, "webauthn/bad-input", "opts has an unknown key ");
166
216
  /** @internal Every option is read here, once, into a plain snapshot, and validation, the checks and
@@ -171,15 +221,15 @@ function _verifyMetadataBlob(blob, opts) {
171
221
  var o = guard.identifier.snapshotOptions(opts, _BLOB_OPTS);
172
222
 
173
223
  var roots = o.rootCertificates;
174
- if (!Array.isArray(roots) || roots.length === 0) {
224
+ if (!intrinsic.isArray(roots) || roots.length === 0) {
175
225
  throw _err("webauthn/metadata-no-root", "verifying a metadata BLOB requires opts.rootCertificates -- the FIDO root(s) to anchor it to; this library bundles none");
176
226
  }
177
227
  if (o.time !== undefined) guard.time.assertValid(o.time, _err, "webauthn/bad-input", "opts.time");
178
- var at = o.time === undefined ? new Date() : o.time;
179
- if (o.previousNo !== undefined && (!Number.isSafeInteger(o.previousNo) || o.previousNo < 0)) {
228
+ var at = o.time === undefined ? new intrinsic.Date() : o.time;
229
+ if (o.previousNo !== undefined && (!intrinsic.isSafeInteger(o.previousNo) || o.previousNo < 0)) {
180
230
  throw _err("webauthn/bad-input", "opts.previousNo must be a non-negative safe integer");
181
231
  }
182
- ["requireRollbackCheck", "allowStale", "rejectUnknownStatus"].forEach(function (k) {
232
+ intrinsic.forEach(["requireRollbackCheck", "allowStale", "rejectUnknownStatus"], function (k) {
183
233
  if (o[k] !== undefined && typeof o[k] !== "boolean") {
184
234
  throw _err("webauthn/bad-input", "opts." + k + " must be a boolean");
185
235
  }
@@ -192,40 +242,43 @@ function _verifyMetadataBlob(blob, opts) {
192
242
  throw _err("webauthn/metadata-no-baseline", "opts.requireRollbackCheck was set without opts.previousNo, so there is no baseline to compare against");
193
243
  }
194
244
  opts = o;
195
- var anchors = roots.map(function (r, i) { return _asCert(r, "opts.rootCertificates[" + i + "]"); });
245
+ var anchors = intrinsic.mapList(roots, function (r, i) { return _asCert(r, "opts.rootCertificates[" + i + "]"); });
196
246
 
197
247
  var raw = guard.bytes.isByteSource(blob)
198
248
  ? guard.bytes.source(blob, WebauthnError, "webauthn/bad-input", "the metadata BLOB") : null;
199
- var declaredLength = raw ? raw.length : (typeof blob === "string" ? Buffer.byteLength(blob, "utf8") : null);
249
+ var declaredLength = raw ? raw.length : (typeof blob === "string" ? intrinsic.byteLength(blob, "utf8") : null);
200
250
  if (declaredLength !== null && declaredLength > C.MDS_BLOB_MAX_BYTES) {
201
251
  throw _err("webauthn/too-large", "the metadata BLOB is " + declaredLength + " bytes, above the " + C.MDS_BLOB_MAX_BYTES + "-byte ceiling");
202
252
  }
203
253
  if (!raw && typeof blob !== "string") {
204
254
  throw _err("webauthn/bad-input", "the metadata BLOB must be bytes or a string");
205
255
  }
206
- var bytes = raw || Buffer.from(blob, "utf8");
256
+ var bytes = raw || intrinsic.bufferFrom(blob, "utf8");
207
257
  if (bytes.length > C.MDS_BLOB_MAX_BYTES) {
208
258
  throw _err("webauthn/too-large", "the metadata BLOB is " + bytes.length + " bytes, above the " + C.MDS_BLOB_MAX_BYTES + "-byte ceiling");
209
259
  }
210
- var segs = bytes.toString("utf8").split(".");
260
+ /** @internal The decode and the split that divides the JWS are captured. They decide which bytes are
261
+ * the signed input and which are the signature, so a replacement chooses what the verification
262
+ * below covers. */
263
+ var segs = intrinsic.splitChar(intrinsic.bufToString(bytes, "utf8"), 46);
211
264
  if (segs.length !== 3) throw _err("webauthn/bad-metadata-blob", "the metadata BLOB is not a three-part JWS compact serialization (RFC 7515 sec. 3.1)");
212
- if (segs[0].length > Math.ceil(C.MDS_BLOB_HEADER_MAX_BYTES / 3) * 4) {
265
+ if (segs[0].length > intrinsic.ceil(C.MDS_BLOB_HEADER_MAX_BYTES / 3) * 4) {
213
266
  throw _err("webauthn/too-large", "the metadata BLOB protected header is above the " + C.MDS_BLOB_HEADER_MAX_BYTES + "-byte ceiling");
214
267
  }
215
- if (segs[2].length > Math.ceil(C.MDS_BLOB_SIG_MAX_BYTES / 3) * 4) {
268
+ if (segs[2].length > intrinsic.ceil(C.MDS_BLOB_SIG_MAX_BYTES / 3) * 4) {
216
269
  throw _err("webauthn/too-large", "the metadata BLOB signature is above the " + C.MDS_BLOB_SIG_MAX_BYTES + "-byte ceiling");
217
270
  }
218
271
  var header, sig;
219
272
  try {
220
273
  /** @internal The header carries `alg`, `typ` and the `x5c` chain, all strings, and no number this
221
274
  * module reads as an integer, so there is nothing here for the rule to apply to. */
222
- header = guard.json.parse(Buffer.from(jose.base64url.decode(segs[0])), _err, {
275
+ header = guard.json.parse(intrinsic.bufferFrom(jose.base64url.decode(segs[0])), _err, {
223
276
  maxBytes: C.MDS_BLOB_HEADER_MAX_BYTES, maxDepth: C.JSON_MAX_DEPTH,
224
277
  tooLarge: "webauthn/too-large", badJson: "webauthn/bad-metadata-blob",
225
278
  tooDeep: "webauthn/bad-metadata-blob", duplicateMember: "webauthn/bad-metadata-blob",
226
279
  badInput: "webauthn/bad-metadata-blob", label: "the metadata BLOB header",
227
280
  });
228
- sig = Buffer.from(jose.base64url.decode(segs[2]));
281
+ sig = intrinsic.bufferFrom(jose.base64url.decode(segs[2]));
229
282
  } catch (e) {
230
283
  if (e instanceof WebauthnError) throw e;
231
284
  throw _err("webauthn/bad-metadata-blob", "the metadata BLOB header or signature is not decodable", e);
@@ -236,30 +289,32 @@ function _verifyMetadataBlob(blob, opts) {
236
289
  }
237
290
  if (_hasOwn(header, "crit")) {
238
291
  var crit = header.crit;
239
- if (!Array.isArray(crit) || crit.length === 0) {
292
+ if (!intrinsic.isArray(crit) || crit.length === 0) {
240
293
  throw _err("webauthn/bad-metadata-blob", "the metadata BLOB header crit must be a non-empty array (RFC 7515 sec. 4.1.11)");
241
294
  }
242
- var critSeen = Object.create(null);
295
+ var critSeen = intrinsic.create(null);
243
296
  for (var ci = 0; ci < crit.length; ci++) {
244
297
  var critName = crit[ci];
245
298
  if (typeof critName !== "string") throw _err("webauthn/bad-metadata-blob", "the metadata BLOB header crit entries must be strings");
246
- if (critSeen[critName]) throw _err("webauthn/bad-metadata-blob", "the metadata BLOB header crit repeats " + JSON.stringify(critName));
299
+ if (critSeen[critName]) throw _err("webauthn/bad-metadata-blob", "the metadata BLOB header crit repeats " + intrinsic.stringify(critName));
247
300
  critSeen[critName] = 1;
248
301
  if (UNDERSTOOD_HEADER[critName]) {
249
- throw _err("webauthn/bad-metadata-blob", "the metadata BLOB header crit must not name the standard header parameter " + JSON.stringify(critName) + " (RFC 7515 sec. 4.1.11)");
302
+ throw _err("webauthn/bad-metadata-blob", "the metadata BLOB header crit must not name the standard header parameter " + intrinsic.stringify(critName) + " (RFC 7515 sec. 4.1.11)");
250
303
  }
251
- throw _err("webauthn/bad-metadata-blob", "the metadata BLOB header names critical header parameter " + JSON.stringify(critName) + ", which this reader does not process");
304
+ throw _err("webauthn/bad-metadata-blob", "the metadata BLOB header names critical header parameter " + intrinsic.stringify(critName) + ", which this reader does not process");
252
305
  }
253
306
  }
254
307
  var algRow = typeof header.alg === "string" ? BLOB_ALGS[header.alg] : undefined;
255
- if (!algRow) throw _err("webauthn/unsupported-algorithm", "the metadata BLOB alg " + JSON.stringify(header.alg) + " is not a supported JWS signature algorithm");
256
- if (!Array.isArray(header.x5c) || header.x5c.length === 0) {
308
+ if (!algRow) throw _err("webauthn/unsupported-algorithm", "the metadata BLOB alg " + intrinsic.stringify(header.alg) + " is not a supported JWS signature algorithm");
309
+ if (!intrinsic.isArray(header.x5c) || header.x5c.length === 0) {
257
310
  throw _err("webauthn/bad-metadata-blob", "the metadata BLOB header carries no x5c certificate chain (RFC 7515 sec. 4.1.6)");
258
311
  }
259
312
  if (header.x5c.length > C.WEBAUTHN_X5C_MAX_CERTS) {
260
313
  throw _err("webauthn/too-large", "the metadata BLOB x5c carries " + header.x5c.length + " certificates, above the " + C.WEBAUTHN_X5C_MAX_CERTS + " this library will parse");
261
314
  }
262
- var chain = header.x5c.map(function (entry, i) {
315
+ /** @internal The walk over the x5c entries is captured. It produces the chain the BLOB signature is
316
+ * verified under and anchored with, so a walk answering with certificates of its own replaces both. */
317
+ var chain = intrinsic.mapList(header.x5c, function (entry, i) {
263
318
  if (typeof entry !== "string") throw _err("webauthn/bad-metadata-blob", "the metadata BLOB x5c entry " + i + " is not a string");
264
319
  var der;
265
320
  try { der = guard.encoding.base64(entry, C.MDS_BLOB_MAX_BYTES, _err, "webauthn/bad-metadata-blob", "a metadata BLOB x5c entry"); }
@@ -272,7 +327,7 @@ function _verifyMetadataBlob(blob, opts) {
272
327
  var leafAlg = (leaf.subjectPublicKeyInfo.algorithm || {}).name;
273
328
  var leafSchemes = typeof leafAlg === "string" ? LEAF_SCHEMES_BY_SPKI_ALG[leafAlg] : undefined;
274
329
  if (!leafSchemes || !leafSchemes[algRow.scheme]) {
275
- throw _err("webauthn/unsupported-algorithm", "the metadata BLOB alg " + header.alg + " does not match the x5c leaf key type " + JSON.stringify(leafAlg));
330
+ throw _err("webauthn/unsupported-algorithm", "the metadata BLOB alg " + header.alg + " does not match the x5c leaf key type " + intrinsic.stringify(leafAlg));
276
331
  }
277
332
  if (leafAlg === "rsassaPss") {
278
333
  var pinnedHash = signScheme.pssSpkiPinnedHash(leaf, _schemeE);
@@ -286,41 +341,64 @@ function _verifyMetadataBlob(blob, opts) {
286
341
  WebauthnError, "webauthn/bad-att-cert");
287
342
  }
288
343
  var params = _blobAlgParams(algRow, leafAlg);
289
- var signingInput = Buffer.from(segs[0] + "." + segs[1], "ascii");
290
- return _subtleImportKey(webcrypto.webcrypto.subtle, "spki", leaf.subjectPublicKeyInfo.bytes, params.imp, false, ["verify"])
291
- .then(function (key) {
292
- return _subtleVerify(webcrypto.webcrypto.subtle, params.ver, key, sig, signingInput)
293
- .catch(function (e) { throw _err("webauthn/verify-error", "the metadata BLOB signature could not be evaluated under its x5c leaf key", e); });
294
- }, function (e) { throw _err("webauthn/unsupported-algorithm", "the metadata BLOB x5c leaf key could not be imported for " + header.alg, e); })
295
- .then(function (ok) {
296
- if (!ok) throw _err("webauthn/verify-failed", "the metadata BLOB signature does not verify under its x5c leaf key");
297
- return _chainToAnchor(chain, anchors, at);
298
- })
299
- .then(function () {
300
- return _parsePayload(segs[1], at, opts);
344
+ var signingInput = intrinsic.bufferFrom(segs[0] + "." + segs[1], "ascii");
345
+ /** @internal Each step of this chain is continued through the captured `then`, because each one's
346
+ * resolved value decides the next: a replacement settling the import step hands a key nothing
347
+ * imported to the verify, settling the verify step reports a signature that was never checked as
348
+ * one that verified, and settling the anchoring step brands the catalogue verified without the
349
+ * chain having reached a root. The catalogue's status entries and attestation roots govern
350
+ * WebAuthn verification once it is branded. */
351
+ var imported = _subtleImportKey(webcrypto.webcrypto.subtle, "spki", leaf.subjectPublicKeyInfo.bytes, params.imp, false, ["verify"]);
352
+ var verified = intrinsic.chain(imported, function (key) {
353
+ var checked = _subtleVerify(webcrypto.webcrypto.subtle, params.ver, key, sig, signingInput);
354
+ return intrinsic.chain(checked, undefined, function (e) {
355
+ throw _err("webauthn/verify-error", "the metadata BLOB signature could not be evaluated under its x5c leaf key", e);
301
356
  });
357
+ }, function (e) { throw _err("webauthn/unsupported-algorithm", "the metadata BLOB x5c leaf key could not be imported for " + header.alg, e); });
358
+ var anchored = intrinsic.chain(verified, function (ok) {
359
+ if (!ok) throw _err("webauthn/verify-failed", "the metadata BLOB signature does not verify under its x5c leaf key");
360
+ return _chainToAnchor(chain, anchors, at);
361
+ });
362
+ return intrinsic.chain(anchored, function () {
363
+ return _parsePayload(segs[1], at, opts);
364
+ });
302
365
  }
303
366
 
304
367
  function _chainToAnchor(chain, anchors, at, what, code) {
305
368
  var subject = what || "metadata BLOB certificate chain";
306
369
  var faultCode = code || "webauthn/metadata-untrusted";
307
- var ordered = _reverse(_arrSlice(chain));
370
+ var ordered = _reverse(intrinsic.copyList(chain));
308
371
  var lastFault = null;
309
- return anchors.reduce(function (p, anchor) {
310
- return p.then(function (done) {
372
+ /** @internal The fold over the anchors is captured. It is what walks each candidate anchor until
373
+ * one validates, so a replacement handing back a settled `true` without visiting an anchor reports
374
+ * the chain trusted against a root nothing compared it with. */
375
+ var walked = intrinsic.reduce(anchors, function (p, anchor) {
376
+ /** @internal The continuation is taken through the captured `then`. Read off the promise at the
377
+ * call, a replacement settles `true` without running this callback at all, which feeds `true`
378
+ * into the `false` seed, skips the chain validation below, and reports a chain unrelated to
379
+ * every supplied root as trusted. */
380
+ return intrinsic.chain(p, function (done) {
311
381
  if (done) return true;
312
- var path = ordered.slice();
382
+ /** @internal Both copies are built without a construction protocol. The first is the chain this
383
+ * anchor is tried against, and the second is what is left once a self-presented anchor is
384
+ * stripped, so a copy answering with fewer elements leaves nothing to validate and the empty
385
+ * path reports trusted on the strength of the strip alone. A captured `slice` is not enough,
386
+ * since it runs ArraySpeciesCreate and hands its elements to whatever the species built. */
387
+ var path = intrinsic.copyList(ordered);
313
388
  var strippedAnchor = false;
314
- if (path.length && _isAnchorItself(path[0], anchor)) { path = path.slice(1); strippedAnchor = true; }
389
+ if (path.length && _isAnchorItself(path[0], anchor)) { path = intrinsic.copyList(path, 1); strippedAnchor = true; }
315
390
  if (path.length === 0) return strippedAnchor;
316
- return pathValidate.validate(path, {
391
+ var verdict = pathValidate.validate(path, {
317
392
  time: at,
318
393
  trustAnchors: { name: anchor.subject, publicKey: anchor.subjectPublicKeyInfo.bytes,
319
394
  algorithm: anchor.subjectPublicKeyInfo.algorithm.oid,
320
395
  parameters: anchor.subjectPublicKeyInfo.algorithm.parameters },
321
- }).then(function (r) { return !!(r && r.valid); }, function (e) { lastFault = e; return false; });
396
+ });
397
+ return intrinsic.chain(verdict, function (r) { return !!(r && r.valid); },
398
+ function (e) { lastFault = e; return false; });
322
399
  });
323
- }, _promiseResolve(_Promise, false)).then(function (trusted) {
400
+ }, _promiseResolve(_Promise, false));
401
+ return intrinsic.chain(walked, function (trusted) {
324
402
  if (!trusted) {
325
403
  throw _err(faultCode, "the " + subject + " does not validate to any of the roots it must reach", lastFault);
326
404
  }
@@ -337,7 +415,7 @@ function assertFresh(metadata, at, label) {
337
415
  if (!metadata || metadata.allowStale === true || typeof metadata.nextUpdate !== "string") return;
338
416
  var atMs = guard.time.isDate(at) ? guard.time.instantOf(at) : NaN;
339
417
  var limit = _staleAfter(metadata.nextUpdate);
340
- if (!isFinite(atMs) || !isFinite(limit)) return;
418
+ if (!intrinsic.numberIsFinite(atMs) || !intrinsic.numberIsFinite(limit)) return;
341
419
  if (atMs >= limit) {
342
420
  throw _err("webauthn/metadata-stale", (label || "the metadata") + " expired after " + metadata.nextUpdate +
343
421
  "; re-verify a current BLOB, or pass opts.allowStale when verifying it");
@@ -357,7 +435,7 @@ function _parsePayload(seg, at, opts) {
357
435
  * `selfAttestedFRR` and `selfAttestedFAR`, the biometric false-reject and false-accept rates, and
358
436
  * `iAPARThreshold`. Measured on the live BLOB from mds3.fidoalliance.org rather than read off the
359
437
  * specification, because the catalogue is what has to be ingestible. */
360
- payload = guard.json.parse(Buffer.from(jose.base64url.decode(seg)), _err, {
438
+ payload = guard.json.parse(intrinsic.bufferFrom(jose.base64url.decode(seg)), _err, {
361
439
  maxBytes: C.MDS_BLOB_MAX_BYTES, maxDepth: C.JSON_MAX_DEPTH,
362
440
  exactIntegerMembers: EXACT_INTEGER_BLOB_MEMBERS,
363
441
  tooLarge: "webauthn/too-large", badJson: "webauthn/bad-metadata-blob",
@@ -370,36 +448,40 @@ function _parsePayload(seg, at, opts) {
370
448
  }
371
449
  if (!_isPlainObject(payload)) throw _err("webauthn/bad-metadata-blob", "the metadata BLOB payload must be a JSON object");
372
450
  if (typeof payload.legalHeader !== "string") throw _err("webauthn/bad-metadata-blob", "the metadata BLOB payload must carry a string legalHeader");
373
- if (!Number.isSafeInteger(payload.no) || payload.no < 0) throw _err("webauthn/bad-metadata-blob", "the metadata BLOB payload must carry a non-negative integer no");
451
+ if (!intrinsic.isSafeInteger(payload.no) || payload.no < 0) throw _err("webauthn/bad-metadata-blob", "the metadata BLOB payload must carry a non-negative integer no");
374
452
  if (opts.previousNo !== undefined && payload.no <= opts.previousNo) {
375
453
  throw _err("webauthn/metadata-rollback", "the metadata BLOB no " + payload.no + " does not exceed the previously held " + opts.previousNo);
376
454
  }
377
455
  var staleAfter = _staleAfter(payload.nextUpdate);
378
456
  var atMs = guard.time.instantOf(at);
379
- if (!isFinite(atMs) || !isFinite(staleAfter)) {
457
+ if (!intrinsic.numberIsFinite(atMs) || !intrinsic.numberIsFinite(staleAfter)) {
380
458
  throw _err("webauthn/bad-input", "the metadata freshness comparison has no usable instant");
381
459
  }
382
460
  var stale = atMs >= staleAfter;
383
461
  if (stale && opts.allowStale !== true) {
384
462
  throw _err("webauthn/metadata-stale", "the metadata BLOB expired after " + payload.nextUpdate + "; pass opts.allowStale to accept it anyway");
385
463
  }
386
- if (!Array.isArray(payload.entries)) throw _err("webauthn/bad-metadata-blob", "the metadata BLOB payload must carry an entries array");
464
+ if (!intrinsic.isArray(payload.entries)) throw _err("webauthn/bad-metadata-blob", "the metadata BLOB payload must carry an entries array");
387
465
  if (payload.entries.length > C.MDS_MAX_ENTRIES) {
388
466
  throw _err("webauthn/too-large", "the metadata BLOB declares " + payload.entries.length + " entries, above the " + C.MDS_MAX_ENTRIES + " ceiling");
389
467
  }
390
468
 
391
- var byAaguid = Object.create(null);
392
- var byKeyIdentifier = Object.create(null);
393
- var entries = payload.entries.map(function (e, i) {
469
+ /** @internal The two lookup indexes and the walk that fills them are captured. An entry is found
470
+ * through these, and the entry supplies both the status reports that refuse a revoked authenticator
471
+ * and the attestation roots a chain is anchored to, so an index handed back already holding a key,
472
+ * or a walk that drops an entry, decides which entry an attestation is governed by. */
473
+ var byAaguid = intrinsic.create(null);
474
+ var byKeyIdentifier = intrinsic.create(null);
475
+ var entries = intrinsic.mapList(payload.entries, function (e, i) {
394
476
  if (!_isPlainObject(e)) throw _err("webauthn/bad-metadata-blob", "metadata entry " + i + " is not an object");
395
- if (!Array.isArray(e.statusReports) || e.statusReports.length === 0) {
477
+ if (!intrinsic.isArray(e.statusReports) || e.statusReports.length === 0) {
396
478
  throw _err("webauthn/bad-metadata-blob", "metadata entry " + i + " must carry a non-empty statusReports array");
397
479
  }
398
480
  if (e.statusReports.length > C.MDS_MAX_STATUS_REPORTS_PER_ENTRY) {
399
481
  throw _err("webauthn/too-large", "metadata entry " + i + " declares " + e.statusReports.length +
400
482
  " status reports, above the " + C.MDS_MAX_STATUS_REPORTS_PER_ENTRY + " ceiling");
401
483
  }
402
- e.statusReports.forEach(function (r, ri) {
484
+ intrinsic.forEach(e.statusReports, function (r, ri) {
403
485
  if (!_isPlainObject(r)) throw _err("webauthn/bad-metadata-blob", "metadata entry " + i + " status report " + ri + " is not an object");
404
486
  if (typeof r.status !== "string" || !r.status) {
405
487
  throw _err("webauthn/bad-metadata-blob", "metadata entry " + i + " status report " + ri + " has no status (MDS v3.0 sec. 3.1.3 requires one)");
@@ -407,31 +489,32 @@ function _parsePayload(seg, at, opts) {
407
489
  });
408
490
  var aaguid = null;
409
491
  if (e.aaguid !== undefined) {
410
- if (typeof e.aaguid !== "string" || !_isAaguid(e.aaguid.toLowerCase())) {
492
+ if (typeof e.aaguid !== "string" || !_isAaguid(_strToLowerCase(e.aaguid))) {
411
493
  throw _err("webauthn/bad-metadata-blob", "metadata entry " + i + " has a malformed aaguid");
412
494
  }
413
- aaguid = e.aaguid.toLowerCase();
495
+ aaguid = _strToLowerCase(e.aaguid);
414
496
  }
415
497
  var st = e.metadataStatement;
416
498
  var keyIds = [];
417
- var seenKeyId = Object.create(null);
418
- [[e.attestationCertificateKeyIdentifiers, "entry"], [st && st.attestationCertificateKeyIdentifiers, "metadataStatement"]]
419
- .forEach(function (pair) {
499
+ var seenKeyId = intrinsic.create(null);
500
+ intrinsic.forEach(
501
+ [[e.attestationCertificateKeyIdentifiers, "entry"], [st && st.attestationCertificateKeyIdentifiers, "metadataStatement"]],
502
+ function (pair) {
420
503
  var list = pair[0];
421
504
  if (list === undefined) return;
422
- if (!Array.isArray(list)) {
505
+ if (!intrinsic.isArray(list)) {
423
506
  throw _err("webauthn/bad-metadata-blob", "metadata entry " + i + " " + pair[1] + " attestationCertificateKeyIdentifiers is not an array");
424
507
  }
425
508
  if (list.length > C.MDS_MAX_KEY_IDS_PER_ENTRY) {
426
509
  throw _err("webauthn/too-large", "metadata entry " + i + " declares " + list.length +
427
510
  " attestation certificate key identifiers, above the " + C.MDS_MAX_KEY_IDS_PER_ENTRY + " ceiling");
428
511
  }
429
- list.forEach(function (k) {
512
+ intrinsic.forEach(list, function (k) {
430
513
  if (typeof k !== "string" || k.length !== 40 || !pkix.allCharsIn(k, _KEYID_HEX_CI)) {
431
514
  throw _err("webauthn/bad-metadata-blob", "metadata entry " + i + " has a malformed attestation certificate key identifier");
432
515
  }
433
- var lower = k.toLowerCase();
434
- if (!seenKeyId[lower]) { seenKeyId[lower] = 1; keyIds.push(lower); }
516
+ var lower = _strToLowerCase(k);
517
+ if (!seenKeyId[lower]) { seenKeyId[lower] = 1; guard.list.append(keyIds, lower); }
435
518
  });
436
519
  });
437
520
  var out = { index: i, aaguid: aaguid, keyIdentifiers: keyIds, statusReports: e.statusReports,
@@ -440,7 +523,7 @@ function _parsePayload(seg, at, opts) {
440
523
  if (byAaguid[aaguid]) throw _err("webauthn/duplicate-metadata-entry", "two metadata entries claim aaguid " + aaguid);
441
524
  byAaguid[aaguid] = out;
442
525
  }
443
- keyIds.forEach(function (k) {
526
+ intrinsic.forEach(keyIds, function (k) {
444
527
  if (byKeyIdentifier[k]) throw _err("webauthn/duplicate-metadata-entry", "two metadata entries claim attestation certificate key identifier " + k);
445
528
  byKeyIdentifier[k] = out;
446
529
  });
@@ -453,15 +536,18 @@ function _parsePayload(seg, at, opts) {
453
536
  entries: entries, byAaguid: byAaguid, byKeyIdentifier: byKeyIdentifier,
454
537
  statusPolicy: opts.statusPolicy || "any", rejectUnknownStatus: opts.rejectUnknownStatus === true };
455
538
  _deepFreeze(result, 0);
456
- _verifiedResults.add(result);
457
- entries.forEach(function (e) { _entryOrigin.set(e, result); });
539
+ intrinsic.weakSetAdd(_verifiedResults, result);
540
+ intrinsic.forEach(entries, function (e) { intrinsic.weakSet(_entryOrigin, e, result); });
458
541
  return result;
459
542
  }
460
543
 
461
544
  function metadataFor(metadata, identifier) {
462
545
  if (!isVerifiedResult(metadata)) throw _err("webauthn/bad-input", "metadataFor expects a verifyMetadataBlob result -- an object that merely resembles one, such as a catalogue restored from a cache, has not been through the signature and chain checks");
463
546
  if (typeof identifier !== "string") return null;
464
- var key = identifier.toLowerCase();
547
+ /** @internal The fold that builds the lookup key is captured. The entry this key finds governs the
548
+ * attestation, supplying its status reports and its attestation roots, so a fold answering with
549
+ * another identifier hands the attestation another authenticator's entry. */
550
+ var key = _strToLowerCase(identifier);
465
551
  if (_isAaguid(key)) {
466
552
  if (key === ZERO_AAGUID) return null;
467
553
  return metadata.byAaguid[key] || null;
@@ -470,12 +556,12 @@ function metadataFor(metadata, identifier) {
470
556
  return null;
471
557
  }
472
558
 
473
- var _ANCHOR_OPTS = Object.assign(Object.create(null), { metadata: 1, time: 1, certificate: 1 });
559
+ var _ANCHOR_OPTS = intrinsic.assign(intrinsic.create(null), { metadata: 1, time: 1, certificate: 1 });
474
560
 
475
561
  function metadataAnchors(entry, opts) {
476
562
  if (!entry || typeof entry !== "object") throw _err("webauthn/bad-input", "metadataAnchors expects a metadata entry");
477
- opts = opts || {};
478
- if (typeof opts !== "object" || Array.isArray(opts)) throw _err("webauthn/bad-input", "metadataAnchors opts must be an object");
563
+ opts = opts || intrinsic.create(null);
564
+ if (typeof opts !== "object" || intrinsic.isArray(opts)) throw _err("webauthn/bad-input", "metadataAnchors opts must be an object");
479
565
  guard.identifier.assertKnownKeys(opts, _ANCHOR_OPTS, _err, "webauthn/bad-input", "metadataAnchors opts has an unknown key ");
480
566
  opts = guard.identifier.ownOptions(opts);
481
567
  if (opts.time !== undefined) guard.time.assertValid(opts.time, _err, "webauthn/bad-input", "opts.time");
@@ -487,17 +573,24 @@ function metadataAnchors(entry, opts) {
487
573
  throw _err("webauthn/bad-input", "metadataAnchors was given an entry from a different catalogue than opts.metadata, so the status reports would be judged under a policy and freshness that are not theirs");
488
574
  }
489
575
  }
490
- var at = opts.time === undefined ? new Date() : opts.time;
576
+ /** @internal The default instant is built with the captured constructor. It is the instant metadata
577
+ * freshness and every status report's effective date are compared against, so a replacement
578
+ * answering with an earlier one accepts expired metadata and suppresses a report already in
579
+ * effect. */
580
+ var at = opts.time === undefined ? new intrinsic.Date() : opts.time;
491
581
  if (opts.metadata !== undefined) assertFresh(opts.metadata, at, "metadataAnchors");
492
582
  if (statusDenied(entry, opts.metadata, opts.certificate, at)) {
493
583
  throw _err("webauthn/metadata-status", "the metadata entry for this authenticator carries a disqualifying status report, so it registers no anchors to trust");
494
584
  }
495
585
  var st = entry.metadataStatement;
496
- var list = st && Array.isArray(st.attestationRootCertificates) ? st.attestationRootCertificates : [];
586
+ var list = st && intrinsic.isArray(st.attestationRootCertificates) ? st.attestationRootCertificates : [];
497
587
  if (list.length > C.MDS_MAX_ANCHORS_PER_ENTRY) {
498
588
  throw _err("webauthn/too-large", "metadata entry " + entry.index + " declares " + list.length + " attestation roots, above the " + C.MDS_MAX_ANCHORS_PER_ENTRY + " ceiling");
499
589
  }
500
- return list.map(function (b64, i) {
590
+ /** @internal The walk that parses the registered roots is captured. These are the anchors an
591
+ * attestation path is validated to, so a walk answering with a certificate of its own supplies the
592
+ * anchor rather than reading it out of the entry. */
593
+ return intrinsic.mapList(list, function (b64, i) {
501
594
  var der;
502
595
  try { der = guard.encoding.base64(b64, C.MDS_BLOB_MAX_BYTES, _err, "webauthn/bad-metadata-entry", "an attestation root certificate"); }
503
596
  catch (e) { throw _err("webauthn/bad-metadata-entry", "metadata entry " + entry.index + " attestation root " + i + " is not canonical base64", e); }
@@ -523,63 +616,106 @@ function _reportInForceAt(report, atMs) {
523
616
  return guard.time.instantOf(d) <= atMs;
524
617
  }
525
618
 
619
+ /** @internal The one refusal that stops a revoked or compromised authenticator being trusted, written
620
+ * without any array operation that consults a replaceable protocol. A captured `filter` and `map`
621
+ * still build their result through ArraySpeciesCreate, which reads a constructor off the receiver, so
622
+ * a species replacement returning an ordinary object collects the matches as indexed properties while
623
+ * its `length` stays 0 and the membership test at the end visits nothing. `concat` reads
624
+ * `Symbol.isConcatSpreadable` the same way, and a `false` there leaves the two selections nested
625
+ * inside the result where no report has a `status` to recognize. Both end in a permit. Every
626
+ * selection here is therefore an index loop appending into an array literal, which creates through
627
+ * no protocol, and each report field is read as an OWN property so a caller-supplied record cannot
628
+ * inherit `effectiveDate` and have an undated revocation read as one that is not yet in force. */
526
629
  function statusDenied(entry, metadata, leaf, at) {
527
630
  var policy = (metadata && metadata.statusPolicy) || "any";
528
631
  var reports = entry.statusReports || [];
529
632
  if (typeof policy === "function") return policy(reports) === true;
530
- reports = reports.filter(function (r) {
531
- return !(r && typeof r.status === "string" && CERT_SCOPED_STATUS[r.status] && _reportNamesOtherCert(r, leaf));
532
- });
533
- var isDated = function (r) { return r && typeof r.effectiveDate === "string" && rfc3339.isValidDate(r.effectiveDate); };
534
- var atMs = (guard.time.isDate(at) && isFinite(guard.time.instantOf(at))) ? guard.time.instantOf(at) : null;
535
- if (atMs !== null) {
536
- reports = reports.filter(function (r) { return !isDated(r) || _reportInForceAt(r, atMs); });
537
- }
538
- var considered = reports;
633
+ function dateOf(r) {
634
+ if (!r || !_hasOwn(r, "effectiveDate")) return null;
635
+ var d = r.effectiveDate;
636
+ return (typeof d === "string" && rfc3339.isValidDate(d)) ? d : null;
637
+ }
638
+ function statusOf(r) {
639
+ if (!r || !_hasOwn(r, "status")) return null;
640
+ return typeof r.status === "string" ? r.status : null;
641
+ }
642
+ var atMs = (guard.time.isDate(at) && intrinsic.numberIsFinite(guard.time.instantOf(at))) ? guard.time.instantOf(at) : null;
643
+ var i;
644
+ var inForce = [];
645
+ for (i = 0; i < reports.length; i++) {
646
+ var r0 = reports[i];
647
+ var s0 = statusOf(r0);
648
+ if (s0 !== null && CERT_SCOPED_STATUS[s0] && _reportNamesOtherCert(r0, leaf)) continue;
649
+ var d0 = dateOf(r0);
650
+ if (atMs !== null && d0 !== null && !_reportInForceAt(r0, atMs)) continue;
651
+ guard.list.append(inForce, r0);
652
+ }
653
+ var considered = inForce;
539
654
  if (policy === "latest-by-date") {
540
- var dated = reports.filter(isDated);
541
- if (dated.length) {
542
- var newest = dated.reduce(function (a, b) { return a.effectiveDate >= b.effectiveDate ? a : b; }).effectiveDate;
543
- considered = dated.filter(function (r) { return r.effectiveDate === newest; })
544
- .concat(reports.filter(function (r) { return !isDated(r); }));
655
+ var newest = null;
656
+ for (i = 0; i < inForce.length; i++) {
657
+ var dN = dateOf(inForce[i]);
658
+ if (dN !== null && (newest === null || dN >= newest)) newest = dN;
659
+ }
660
+ if (newest !== null) {
661
+ considered = [];
662
+ for (i = 0; i < inForce.length; i++) {
663
+ var dK = dateOf(inForce[i]);
664
+ if (dK === null || dK === newest) guard.list.append(considered, inForce[i]);
665
+ }
545
666
  }
546
667
  }
547
668
  var rejectUnknown = !!(metadata && metadata.rejectUnknownStatus);
548
- return considered.some(function (r) {
549
- var s = r && typeof r.status === "string" ? r.status : null;
550
- if (s === null) return false;
669
+ for (i = 0; i < considered.length; i++) {
670
+ var s = statusOf(considered[i]);
671
+ if (s === null) continue;
551
672
  if (DISQUALIFYING[s]) return true;
552
- return rejectUnknown && !_KNOWN_STATUS[s];
553
- });
673
+ if (rejectUnknown && !_KNOWN_STATUS[s]) return true;
674
+ }
675
+ return false;
554
676
  }
555
677
 
556
- var _KNOWN_STATUS = Object.assign(Object.create(null), {
678
+ /** @internal Frozen. Under `rejectUnknownStatus` a status NOT in this table is disqualifying, so a
679
+ * caller able to add an entry makes an unrecognized status read as a known-good one. */
680
+ var _KNOWN_STATUS = intrinsic.freeze(intrinsic.assign(intrinsic.create(null), {
557
681
  NOT_FIDO_CERTIFIED: 1, SELF_ASSERTION_SUBMITTED: 1, FIDO_CERTIFIED: 1, FIDO_CERTIFIED_L1: 1,
558
682
  FIDO_CERTIFIED_L1plus: 1, FIDO_CERTIFIED_L2: 1, FIDO_CERTIFIED_L2plus: 1, FIDO_CERTIFIED_L3: 1,
559
683
  FIDO_CERTIFIED_L3plus: 1, UPDATE_AVAILABLE: 1,
560
- });
684
+ }));
561
685
 
686
+ /** @internal The rendering of an AAGUID is captured end to end. The string it returns is the metadata
687
+ * lookup key, so an encoding or a slice answering with another authenticator's AAGUID selects that
688
+ * authenticator's entry, with its status reports and its attestation roots. */
562
689
  function aaguidToString(buf) {
563
- if (!Buffer.isBuffer(buf) || buf.length !== 16) return null;
564
- var h = buf.toString("hex");
565
- return h.slice(0, 8) + "-" + h.slice(8, 12) + "-" + h.slice(12, 16) + "-" + h.slice(16, 20) + "-" + h.slice(20);
690
+ if (!intrinsic.isBuffer(buf) || buf.length !== 16) return null;
691
+ var h = intrinsic.bufToString(buf, "hex");
692
+ return _strSlice(h, 0, 8) + "-" + _strSlice(h, 8, 12) + "-" + _strSlice(h, 12, 16) + "-" +
693
+ _strSlice(h, 16, 20) + "-" + _strSlice(h, 20);
566
694
  }
567
695
 
696
+ /** @internal The digest, its update and its encoding all come from captures. What this returns is both
697
+ * a metadata lookup key and the identity two certificates are told apart by where a status report
698
+ * names one, so an operation answering with another certificate's identifier selects another entry
699
+ * and makes a certificate-scoped compromise report apply to the wrong leaf. */
568
700
  function certKeyIdentifier(cert) {
569
701
  var pk = cert && cert.subjectPublicKeyInfo && cert.subjectPublicKeyInfo.publicKey;
570
- if (!pk || !Buffer.isBuffer(pk.bytes)) {
702
+ if (!pk || !intrinsic.isBuffer(pk.bytes)) {
571
703
  throw _err("webauthn/bad-input", "certKeyIdentifier expects a parsed certificate carrying a subject public key");
572
704
  }
573
- return nodeCrypto.createHash("sha1").update(pk.bytes).digest("hex");
705
+ return _hashDigest(_hashUpdate(_createHash("sha1"), pk.bytes), "hex");
574
706
  }
575
707
 
576
708
  function metadataForKeyIdentifier(metadata, keyId) {
577
709
  if (!isVerifiedResult(metadata)) throw _err("webauthn/bad-input", "metadataForKeyIdentifier expects a verifyMetadataBlob result -- an object that merely resembles one has not been through the signature and chain checks");
578
710
  if (typeof keyId !== "string") return null;
579
- return metadata.byKeyIdentifier[keyId.toLowerCase()] || null;
711
+ return metadata.byKeyIdentifier[_strToLowerCase(keyId)] || null;
580
712
  }
581
713
 
582
- module.exports = {
714
+ /** @internal Frozen, because a consumer reads these off this object at the call. `metadataFor` is what
715
+ * finds the entry a status refusal is read out of, and `chainToAnchor` IS the chain validation, so a
716
+ * writable export lets a replacement answer `null` for a disqualified entry or settle without walking
717
+ * a path, and the consumer's own captures decide nothing after that. */
718
+ module.exports = intrinsic.freeze({
583
719
  verifyMetadataBlob: verifyMetadataBlob,
584
720
  metadataFor: metadataFor,
585
721
  metadataForKeyIdentifier: metadataForKeyIdentifier,
@@ -594,4 +730,4 @@ module.exports = {
594
730
  DISQUALIFYING: DISQUALIFYING,
595
731
  // @internal
596
732
  BLOB_ALGS: BLOB_ALGS,
597
- };
733
+ });