@blamejs/pki 0.5.9 → 0.5.11

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 (54) hide show
  1. package/CHANGELOG.md +80 -19
  2. package/README.md +1 -1
  3. package/lib/acme.js +5 -5
  4. package/lib/asn1-der.js +1 -1
  5. package/lib/attrcert-sign.js +1 -1
  6. package/lib/cbor-det.js +4 -4
  7. package/lib/cmc-build.js +5 -5
  8. package/lib/cmc-verify.js +14 -11
  9. package/lib/cms-decrypt.js +2 -2
  10. package/lib/cms-sign.js +3 -5
  11. package/lib/cms-verify.js +9 -6
  12. package/lib/constants.js +1 -1
  13. package/lib/crl-sign.js +1 -1
  14. package/lib/crmf-sign.js +1 -1
  15. package/lib/ct.js +8 -5
  16. package/lib/est.js +20 -14
  17. package/lib/guard-all.js +12 -12
  18. package/lib/guard-bytes.js +568 -82
  19. package/lib/guard-compress.js +2 -2
  20. package/lib/guard-der.js +1 -1
  21. package/lib/guard-identifier.js +694 -6
  22. package/lib/guard-limits.js +2 -2
  23. package/lib/guard-parsed.js +10 -6
  24. package/lib/guard-range.js +1 -1
  25. package/lib/guard-secret.js +1 -1
  26. package/lib/guard-text.js +1 -1
  27. package/lib/guard-time.js +65 -4
  28. package/lib/hpke.js +1 -1
  29. package/lib/http-digest.js +1 -1
  30. package/lib/inspect.js +8 -3
  31. package/lib/jose.js +1 -1
  32. package/lib/key.js +48 -6
  33. package/lib/lint.js +15 -8
  34. package/lib/ocsp-verify.js +10 -4
  35. package/lib/ocsp.js +35 -6
  36. package/lib/oid.js +1 -1
  37. package/lib/path-validate.js +99 -22
  38. package/lib/schema-c509.js +5 -5
  39. package/lib/schema-cmp.js +1 -1
  40. package/lib/schema-crl.js +1 -1
  41. package/lib/schema-engine.js +2 -2
  42. package/lib/schema-pkix.js +1 -1
  43. package/lib/sigstore.js +6 -2
  44. package/lib/smime.js +1 -1
  45. package/lib/trust.js +2 -2
  46. package/lib/tsp-sign.js +2 -2
  47. package/lib/validator-attcert.js +3 -3
  48. package/lib/validator-cose.js +3 -3
  49. package/lib/webauthn-mds.js +17 -17
  50. package/lib/webauthn.js +12 -12
  51. package/lib/webcrypto.js +16 -7
  52. package/lib/x509-sign.js +1 -1
  53. package/package.json +4 -2
  54. package/sbom.cdx.json +6 -6
@@ -306,7 +306,7 @@ function resolveDescriptor(sigAlg) {
306
306
  // defense). For the one-shot families whose public key shares the signature OID
307
307
  // (EdDSA, ML-DSA, SLH-DSA), Node's WebCrypto imports an SPKI of a DIFFERENT type
308
308
  // under the requested name and verifies with the real key, so an Ed25519-signed
309
- // certificate labelled SLH-DSA would otherwise validate. Enforce structurally:
309
+ // certificate labeled SLH-DSA would otherwise validate. Enforce structurally:
310
310
  // the issuer SPKI's algorithm OID MUST equal the signature algorithm OID. (For
311
311
  // RSA/ECDSA -- different key vs signature OIDs -- WebCrypto's import already rejects
312
312
  // a mismatched key type, so `sameKeyOid` is not set and this is a no-op.)
@@ -1170,8 +1170,28 @@ function validateCriticalExtensionStructure(cert) {
1170
1170
  * });
1171
1171
  * res.valid; // boolean; res.results[0].checks carries the per-check codes
1172
1172
  */
1173
+ // Every option pki.path.validate reads. The anchor option here is the singular `trustAnchor`,
1174
+ // while pki.path.build in this same module spells it `trustAnchors` and takes a list. A caller
1175
+ // moving between the two verbs carries the wrong spelling without leaving the namespace, and
1176
+ // before this it bought them no anchoring and no error, just "a trustAnchor is required" about
1177
+ // an option they believed they had passed.
1178
+ // Derived from three sources, because no one of them is complete: the `opts.*` reads in this
1179
+ // function, the `params.*` reads in the RFC 5280 6.1.1 state it is passed down to, and the
1180
+ // documented prose above, which is the only place `requiredEku` and the three initial-policy
1181
+ // inputs appear together. A grep of one function would have shipped a list missing four real
1182
+ // options and rejected callers that work today.
1183
+ var _VALIDATE_OPTS = {
1184
+ checkPurpose: 1, historicalMode: 1, initialAnyPolicyInhibit: 1, initialExcludedSubtrees: 1,
1185
+ initialExplicitPolicy: 1, initialPermittedSubtrees: 1, initialPolicyMappingInhibit: 1,
1186
+ maxPathCerts: 1, maxPolicyNodes: 1, requireRevocation: 1, requiredEku: 1, revocationChecker: 1,
1187
+ softFail: 1, time: 1, trustAnchor: 1, userInitialPolicySet: 1, verifier: 1
1188
+ };
1189
+
1173
1190
  async function validate(path, opts) {
1174
- opts = opts || {};
1191
+ opts = guard.identifier.optionsObject(opts, E, "path/bad-input", "validate: opts");
1192
+ guard.identifier.assertKnownKeys(opts, _VALIDATE_OPTS, E, "path/bad-input",
1193
+ "pki.path.validate has an unknown option. The anchor here is `trustAnchor`, singular; " +
1194
+ "pki.path.build takes `trustAnchors`. The unknown option was: ");
1175
1195
  if (!Array.isArray(path)) throw E("path/bad-input", "validate: path must be an array of certificates");
1176
1196
  // Bound the per-cert asymmetric-verify work BEFORE parsing an untrusted bundle
1177
1197
  // (the policy-tree cap guards CVE-2023-0464-style blow-up; this guards linear
@@ -1288,10 +1308,15 @@ async function validate(path, opts) {
1288
1308
  }
1289
1309
 
1290
1310
  // 6.1.3(a)(2) validity window.
1291
- var t = opts.time;
1311
+ //
1312
+ // Instants, never the Date objects. Comparing two Dates coerces each one, and coercion runs
1313
+ // `Symbol.toPrimitive` or `valueOf`, which a caller's subclass answers. opts.time was checked
1314
+ // for a valid held instant at entry, and comparing the object here asked it again through a
1315
+ // door the check never used -- so the value validated and the value compared were two reads.
1316
+ var t = guard.time.instantOf(opts.time);
1292
1317
  var vOk = true, vCode;
1293
- if (t < cert.validity.notBefore) { vOk = false; vCode = "path/not-yet-valid"; }
1294
- else if (t > cert.validity.notAfter) { vOk = false; vCode = "path/expired"; }
1318
+ if (t < guard.time.instantOf(cert.validity.notBefore)) { vOk = false; vCode = "path/not-yet-valid"; }
1319
+ else if (t > guard.time.instantOf(cert.validity.notAfter)) { vOk = false; vCode = "path/expired"; }
1295
1320
  checks.push({ name: "validity", ok: vOk, code: vCode });
1296
1321
  if (!vOk) failed = true;
1297
1322
 
@@ -1435,7 +1460,7 @@ async function validate(path, opts) {
1435
1460
  // (Mozilla certverifier isDistrustedCertificateChain: endEntityNotBefore
1436
1461
  // <= distrustAfterTime -> not distrusted; the end-of-day ...235959Z
1437
1462
  // convention keeps the whole boundary day trusted).
1438
- if (cert.validity.notBefore > distrustDate) {
1463
+ if (guard.time.instantOf(cert.validity.notBefore) > guard.time.instantOf(distrustDate)) {
1439
1464
  checks.push({ name: "distrustAfter", ok: false, code: "path/distrusted-after" }); failed = true;
1440
1465
  }
1441
1466
  }
@@ -1753,7 +1778,7 @@ function classifyCrls(parsed) {
1753
1778
  // sec. 5.2.4 @3447 makes deltaCRLIndicator a MUST-be-critical extension. A non-critical one
1754
1779
  // is non-conforming, so it does not earn the NEW capability of being merged -- merging can
1755
1780
  // RELEASE a certificate, and that must rest on a conforming indicator. It is still classified
1756
- // as a delta and still consulted for revocation, which is the shipped behaviour and the
1781
+ // as a delta and still consulted for revocation, which is the shipped behavior and the
1757
1782
  // conservative direction.
1758
1783
  if (crlNumberWithinBound(n) && deltaCritical) { rec.baseCrlNumber = n; rec.mergeable = true; }
1759
1784
  } catch (_e) {
@@ -1808,7 +1833,7 @@ function selectDelta(candidates) {
1808
1833
  for (var i = 0; i < candidates.length; i++) {
1809
1834
  var c = candidates[i];
1810
1835
  if (best === null) { best = c; continue; }
1811
- var t = c.crl.thisUpdate.getTime(), bt = best.crl.thisUpdate.getTime();
1836
+ var t = guard.time.instantOf(c.crl.thisUpdate), bt = guard.time.instantOf(best.crl.thisUpdate);
1812
1837
  if (t > bt) { best = c; continue; }
1813
1838
  if (t === bt && c.crlNumber !== null && best.crlNumber !== null && c.crlNumber > best.crlNumber) best = c;
1814
1839
  }
@@ -2053,11 +2078,12 @@ function crlChecker(crls, opts) {
2053
2078
  }
2054
2079
  }
2055
2080
  }
2056
- if (theCrl.thisUpdate > time) return null; // not yet valid
2081
+ if (guard.time.instantOf(theCrl.thisUpdate) > guard.time.instantOf(time)) return null; // not yet valid
2057
2082
  // A CRL with no nextUpdate has no bounded validity -- its currency
2058
2083
  // cannot be confirmed (RFC 5280 sec. 5.1.2.5 requires nextUpdate), so a
2059
2084
  // replayed old CRL must not read "good". Treat it as unusable.
2060
- if (!theCrl.nextUpdate || theCrl.nextUpdate < time) return null; // stale / no bound
2085
+ if (!theCrl.nextUpdate ||
2086
+ guard.time.instantOf(theCrl.nextUpdate) < guard.time.instantOf(time)) return null; // stale / no bound
2061
2087
 
2062
2088
  var sigOk = await crlVerify.verifyCrlSignature(theCrl, issuer.workingPublicKey);
2063
2089
  if (!sigOk) return null; // unverifiable -> not authoritative
@@ -2085,7 +2111,8 @@ function crlChecker(crls, opts) {
2085
2111
  // e.g. a timestamped signature -- does an entry dated AFTER the
2086
2112
  // validation time not yet apply.
2087
2113
  // allow:nan-date-comparison-unguarded -- revocationDate is codec-parsed (NaN-rejected); a NaN check time makes this FAIL CLOSED (the skip is not taken -> the entry is treated as revoked), and `time` is validated at the path.validate / crlChecker entry points.
2088
- if (historical && entry.revocationDate instanceof Date && entry.revocationDate.getTime() > time.getTime()) continue;
2114
+ if (historical && guard.time.isDate(entry.revocationDate) &&
2115
+ guard.time.instantOf(entry.revocationDate) > guard.time.instantOf(time)) continue;
2089
2116
  var rc = crlEntryReason(entry);
2090
2117
  return rc === null ? 0 : rc;
2091
2118
  }
@@ -2393,7 +2420,7 @@ function ocspChecker(responses) {
2393
2420
  * `pki.schema.ocsp.parseResponse` result. A REBUILT parsed response is refused. A
2394
2421
  * signature check has three parts -- the signature, the algorithm that verifies it,
2395
2422
  * and the bytes it covers -- and on a parsed object all three are separate properties:
2396
- * a genuine CA signature over a certificate that CA issued, relabelled, verifies as a
2423
+ * a genuine CA signature over a certificate that CA issued, relabeled, verifies as a
2397
2424
  * ResponseData signature for a response that never existed. The parser marks what it
2398
2425
  * returns, so those three are known to have been derived together from one byte
2399
2426
  * string; `Object.assign`, spread and a JSON round-trip all drop the mark, which is
@@ -2460,7 +2487,7 @@ function _verifyOcspParsed(parsedResponse, cert, issuerCert, time, opts) {
2460
2487
  opts = opts || {};
2461
2488
  // The currency + responder-cert validity windows compare against `time`; a missing or invalid
2462
2489
  // check date must fail closed (a NaN compares false against every bound), never silently pass.
2463
- if (!(time instanceof Date) || isNaN(time.getTime())) {
2490
+ if (!guard.time.isDate(time) || isNaN(guard.time.instantOf(time))) {
2464
2491
  return Promise.reject(E("path/bad-input", "verifyOcspResponse: time must be a valid Date (the currency + responder-validity check date)"));
2465
2492
  }
2466
2493
  // Bind the supplied issuer certificate to the target: the target's issuer DN must equal the
@@ -2666,8 +2693,9 @@ function scoreCandidate(cand, childAki, anchors, time) {
2666
2693
  if (ku && ku.value && ku.value.keyCertSign === true) score += 10; // sec. 3.5.3 keyUsage keyCertSign
2667
2694
  var v = cand.validity;
2668
2695
  // allow:nan-date-comparison-unguarded -- cand.validity dates are codec-parsed (asn1 readTime rejects a NaN instant) and time is guard.time.assertValid'd at entry; the validity term is a fail-safe sort hint regardless.
2669
- if (v && v.notBefore instanceof Date && v.notAfter instanceof Date &&
2670
- v.notBefore.getTime() <= time.getTime() && time.getTime() <= v.notAfter.getTime()) score += 5; // sec. 3.5.4 validity
2696
+ if (v && guard.time.isDate(v.notBefore) && guard.time.isDate(v.notAfter) &&
2697
+ guard.time.instantOf(v.notBefore) <= guard.time.instantOf(time) &&
2698
+ guard.time.instantOf(time) <= guard.time.instantOf(v.notAfter)) score += 5; // sec. 3.5.4 validity
2671
2699
  return score;
2672
2700
  }
2673
2701
 
@@ -2795,7 +2823,7 @@ async function _fetchAiaIssuers(current, aia) {
2795
2823
  for (var c = 0; c < certs.length; c++) {
2796
2824
  var parsed;
2797
2825
  try { parsed = coerceCert(certs[c]); }
2798
- catch (_e3) { /* allow:swallow-unverified verified-unreachable: every cert here already passed the IDENTICAL x509.parse in _aiaParseBody (single-DER validates `body`; certs-only validates each via parseCertsOnly), so coerceCert re-parsing the same bytes cannot throw -- the guard stays as defence-in-depth */ continue; }
2826
+ catch (_e3) { /* allow:swallow-unverified verified-unreachable: every cert here already passed the IDENTICAL x509.parse in _aiaParseBody (single-DER validates `body`; certs-only validates each via parseCertsOnly), so coerceCert re-parsing the same bytes cannot throw -- the guard stays as defense-in-depth */ continue; }
2799
2827
  out.push(parsed);
2800
2828
  }
2801
2829
  }
@@ -2870,9 +2898,30 @@ async function _fetchAiaIssuers(current, aia) {
2870
2898
  * });
2871
2899
  * result.valid; // true when a path to a trust anchor was found and validated
2872
2900
  */
2901
+ // pki.path.build takes its own options AND forwards every pki.path.validate option to each
2902
+ // internal validate() call. The documented contract above says so, and validateOpts() copies
2903
+ // them wholesale. So the accepted set is the union, derived from _VALIDATE_OPTS. Retyping it
2904
+ // would let the two lists drift the first time validate gains an option.
2905
+ //
2906
+ // `trustAnchor` is removed from the union on purpose: build assigns it per candidate anchor,
2907
+ // so a caller passing the singular would have it overwritten and silently ignored. Refusing it
2908
+ // is what makes the singular/plural message below actionable rather than trivia.
2909
+ var _BUILD_OPTS = (function () {
2910
+ var o = {
2911
+ aiaTimeout: 1, candidates: 1, fetchAia: 1, intermediates: 1, maxAiaFetches: 1,
2912
+ maxAiaPerCert: 1, maxCandidatesConsidered: 1, maxDepth: 1, maxPathCerts: 1,
2913
+ maxResponseBytes: 1, time: 1, tls: 1, transport: 1, trustAnchors: 1, validate: 1
2914
+ };
2915
+ Object.keys(_VALIDATE_OPTS).forEach(function (k) { o[k] = 1; });
2916
+ delete o.trustAnchor;
2917
+ return o;
2918
+ })();
2919
+
2873
2920
  async function build(leaf, opts) {
2874
- opts = opts || {};
2875
- if (typeof opts !== "object" || Buffer.isBuffer(opts)) throw E("path/bad-input", "build: opts must be an object");
2921
+ opts = guard.identifier.optionsObject(opts, E, "path/bad-input", "build: opts");
2922
+ guard.identifier.assertKnownKeys(opts, _BUILD_OPTS, E, "path/bad-input",
2923
+ "pki.path.build has an unknown option. The anchors here are `trustAnchors`, plural; " +
2924
+ "pki.path.validate takes `trustAnchor`. The unknown option was: ");
2876
2925
  var leafCert;
2877
2926
  try { leafCert = coerceCert(leaf); }
2878
2927
  catch (e) { throw E("path/bad-input", "build: the leaf certificate did not parse", e); }
@@ -2965,10 +3014,38 @@ async function build(leaf, opts) {
2965
3014
  // enumerates only own enumerable properties, so no prototype-pollution belt.
2966
3015
  var BUILD_ONLY_OPT = { candidates: 1, intermediates: 1, trustAnchors: 1, maxDepth: 1, maxCandidatesConsidered: 1, validate: 1,
2967
3016
  fetchAia: 1, transport: 1, tls: 1, maxAiaFetches: 1, maxAiaPerCert: 1, aiaTimeout: 1, maxResponseBytes: 1 };
2968
- var forwarded = {};
2969
- Object.keys(opts).forEach(function (k) { if (!BUILD_ONLY_OPT[k]) forwarded[k] = opts[k]; });
3017
+ // No prototype, so the set forwarded to each internal validate holds what was put in it and
3018
+ // nothing a runtime carries. A plain `{}` here re-acquired a polluted name after the caller's
3019
+ // own value had been dropped, which is how an explicit `softFail: false` became a waiver.
3020
+ var forwarded = Object.create(null);
3021
+ // Driven by the contract. Walk the validate options this verb forwards and take the ones the
3022
+ // bag answers for.
3023
+ //
3024
+ // Enumerating the bag reads whatever a property walk chooses to report, and that walk skips a
3025
+ // method so a caller's class keeps its own. A known option whose value were a bare function
3026
+ // would be indistinguishable from one, and a defaults-style bag like
3027
+ // `Object.create({ someOption: fn })` would have it dropped here, leaving each internal
3028
+ // validate running without something the caller supplied.
3029
+ //
3030
+ // No option takes a bare function today. `revocationChecker` is `{ check }`, `verifier` is
3031
+ // `{ verify }`, and `transport` is build-only and never forwarded. So this is a hardening
3032
+ // rather than a live defect, and there is no vector that fails without it. It is here because
3033
+ // asking the contract cannot drop a known option whatever its value, and the alternative makes
3034
+ // the forwarded set depend on a walk whose rules exist for an unrelated question.
3035
+ //
3036
+ // `in` rather than an own check, because an option carried on a prototype is one the caller
3037
+ // supplied and `opts.softFail` answers for it. Own membership decides only whether a name is
3038
+ // build-only, which is a question about this module's own table and never about the caller.
3039
+ Object.keys(_VALIDATE_OPTS).forEach(function (k) {
3040
+ if (Object.prototype.hasOwnProperty.call(BUILD_ONLY_OPT, k)) return;
3041
+ if (k in opts) forwarded[k] = opts[k];
3042
+ });
2970
3043
  function validateOpts(anchor) {
2971
- var vo = {};
3044
+ var vo = Object.create(null); // same reason as `forwarded` above
3045
+ // Every name in the forwarded set, which was built from the contract above and has no
3046
+ // prototype, so its own keys are exactly what was put in it. A property walk here would
3047
+ // apply the method rule a second time and drop a function-valued option all over again;
3048
+ // forwarding less than was validated is worse than refusing the bag.
2972
3049
  Object.keys(forwarded).forEach(function (f) { vo[f] = forwarded[f]; });
2973
3050
  vo.trustAnchor = anchor;
2974
3051
  return vo;
@@ -3111,7 +3188,7 @@ module.exports = {
3111
3188
  // The set of extension OIDs whose CRITICAL semantics this validator processes (RFC 5280 sec. 6.1).
3112
3189
  // Exposed so a linter can distinguish "processed" from "merely decoded" and stay consistent with
3113
3190
  // the path-validation verdict on a critical extension -- a decoder in certExtensionDecoders is NOT
3114
- // by itself proof the criticality is honoured. Both sets are frozen so a caller cannot mutate them.
3191
+ // by itself proof the criticality is honored. Both sets are frozen so a caller cannot mutate them.
3115
3192
  PROCESSED_EXTENSIONS: PROCESSED_EXTENSIONS,
3116
3193
  // Extensions that ARE processed for an intermediate CA but are unprocessed on the target/leaf, so a
3117
3194
  // critical instance on the target fails closed (RFC 5280 sec. 6.1.5(f)) -- policyMappings is
@@ -1650,7 +1650,7 @@ function _sdaToDer(node, isNative) {
1650
1650
  if (tname === undefined) throw _err("c509/bad-extensions", "a subjectDirectoryAttributes attribute type int " + ti + " has no C509 sec. 8.6 registry row");
1651
1651
  // countryName / serialNumber carry a CHARACTER restriction (draft sec. 3.1.4 "SHALL contain only
1652
1652
  // characters from the 74-character ASCII subset permitted by PrintableString"), and not a sign override --
1653
- // _reconAttrValue asserts the charset and honours the declared string type. Requiring the negative sign
1653
+ // _reconAttrValue asserts the charset and honors the declared string type. Requiring the negative sign
1654
1654
  // here would also make these attributes unrepresentable in a NATIVE certificate, whose ints SHALL all be
1655
1655
  // non-negative (same sec.), so the rule is the charset, not the sign.
1656
1656
  for (var vi = 0; vi < valuesNode.children.length; vi++) {
@@ -1846,7 +1846,7 @@ function _reconAttrValue(rdn) {
1846
1846
  if (rdn.type === "emailAddress") return b.ia5(s);
1847
1847
  // serialNumber / countryName carry a CHARACTER restriction, not a string-type override: draft sec. 3.1.4
1848
1848
  // "SHALL contain only characters from the 74-character ASCII subset permitted by PrintableString". Enforce
1849
- // that on the CHARACTERS and still honour the sign for the string type. Coercing them to PrintableString
1849
+ // that on the CHARACTERS and still honor the sign for the string type. Coercing them to PrintableString
1850
1850
  // regardless of sign would make the +N and -N encodings of one value reconstruct IDENTICAL DER, so a single
1851
1851
  // X.509 signature would cover two distinct C509 encodings (a malleability window in the type-3 transform).
1852
1852
  // b.printable IS the PrintableString charset authority -- run it for the assert even when the sign selects
@@ -2205,14 +2205,14 @@ function _requireResultShape(r) {
2205
2205
  if (r.serialNumber == null && r.serialNumberHex == null) throw _err("c509/bad-input", "a C509 result must carry serialNumber or serialNumberHex");
2206
2206
  if (!r.signatureAlgorithm || typeof r.signatureAlgorithm.name !== "string") throw _err("c509/bad-input", "a C509 result must carry signatureAlgorithm.name");
2207
2207
  if (!r.subjectPublicKeyAlgorithm || typeof r.subjectPublicKeyAlgorithm.name !== "string") throw _err("c509/bad-input", "a C509 result must carry subjectPublicKeyAlgorithm.name");
2208
- if (!r.validity || !(r.validity.notBefore instanceof Date) || (r.validity.notAfter !== null && !(r.validity.notAfter instanceof Date))) throw _err("c509/bad-input", "a C509 result must carry validity.notBefore (Date) and notAfter (Date or null)");
2208
+ if (!r.validity || !guard.time.isDate(r.validity.notBefore) || (r.validity.notAfter !== null && !guard.time.isDate(r.validity.notAfter))) throw _err("c509/bad-input", "a C509 result must carry validity.notBefore (Date) and notAfter (Date or null)");
2209
2209
  if (!Array.isArray(r.extensions)) throw _err("c509/bad-input", "a C509 result must carry an extensions array");
2210
2210
  if (!Buffer.isBuffer(r.signatureValue)) throw _err("c509/bad-input", "a C509 result must carry a Buffer signatureValue");
2211
2211
  }
2212
2212
  // A validity Date -> its C509 ~time (a non-negative CBOR epoch uint). A pre-epoch date cannot be
2213
2213
  // represented (the parser accepts only an unwrapped major-type-0 integer) and fails closed here.
2214
2214
  function _validityUint(date, label) {
2215
- var secs = Math.floor(date.getTime() / 1000);
2215
+ var secs = Math.floor(guard.time.instantOf(date) / 1000);
2216
2216
  if (!isFinite(secs) || secs < 0) throw _err("c509/bad-validity", label + " is before the Unix epoch or not a valid date; C509 ~time is a non-negative CBOR epoch");
2217
2217
  return cbor.build.uint(BigInt(secs));
2218
2218
  }
@@ -2433,7 +2433,7 @@ _derToType3 = function (input, opts) {
2433
2433
  // against a UTF8String of the same characters) would rebuild different bytes and break the
2434
2434
  // signature that covers them. Byte-identical is exactly the condition under which that is safe.
2435
2435
  issuer: c.issuer.bytes.equals(c.subject.bytes) ? null : _c509NameFromDer(c.issuer.bytes),
2436
- validity: { notBefore: c.validity.notBefore, notAfter: c.validity.notAfter.getTime() === _NO_EXPIRY ? null : c.validity.notAfter },
2436
+ validity: { notBefore: c.validity.notBefore, notAfter: guard.time.instantOf(c.validity.notAfter) === _NO_EXPIRY ? null : c.validity.notAfter },
2437
2437
  subject: _c509NameFromDer(c.subject.bytes),
2438
2438
  subjectPublicKeyAlgorithm: { name: "ecPublicKey", oid: c.subjectPublicKeyInfo.algorithm.oid, curve: curve },
2439
2439
  subjectPublicKey: _compressEcPoint(asn1.read.bitString(spkiNode.children[1]).bytes, coordLen), // 0x04||X||Y -> C509 compressed marker
package/lib/schema-cmp.js CHANGED
@@ -460,7 +460,7 @@ var CERT_RESPONSE = schema.seq([
460
460
  // accepted (0) or grantedWithMods (1). Any other status (rejection, waiting,
461
461
  // the revocation / keyUpdate warnings) denies or defers the request, so a
462
462
  // certificate under it is a malformed response even when no explicit failInfo
463
- // bit is set (a rejection is commonly signalled by status alone). Keying the
463
+ // bit is set (a rejection is commonly signaled by status alone). Keying the
464
464
  // rule off failInfo presence alone would let a bare-rejection status ship a
465
465
  // certificate (RFC 9810 sec. 5.3.4).
466
466
  if (certifiedKeyPair !== null && status.status.code !== 0 && status.status.code !== 1) {
package/lib/schema-crl.js CHANGED
@@ -41,7 +41,7 @@ var CRL_REASONS = pkix.CRL_REASON_NAMES;
41
41
 
42
42
  // Extension-value decoding is keyed off the stable dotted OID (resolved once at
43
43
  // load from the canonical name) and not the mutable display name, so a caller's
44
- // pki.oid.register() display override cannot change parse behaviour.
44
+ // pki.oid.register() display override cannot change parse behavior.
45
45
  var OID_CRL_NUMBER = oid.byName("cRLNumber");
46
46
  var OID_REASON_CODE = oid.byName("reasonCode");
47
47
  var OID_INVALIDITY_DATE = oid.byName("invalidityDate");
@@ -40,11 +40,11 @@ function _fail(ctx, code, message) {
40
40
  }
41
41
 
42
42
  // ---- shape assertions ------------------------------------------------
43
- // A schema's `assert` mode is a real behaviour-preservation control: some
43
+ // A schema's `assert` mode is a real behavior-preservation control: some
44
44
  // hand-written guards checked the universal SEQUENCE tag (algorithmIdentifier,
45
45
  // Name), others checked only that the node had children (Validity, SPKI, the
46
46
  // tbs body, an AttributeTypeAndValue). Collapsing them to one "is a SEQUENCE"
47
- // check would silently change behaviour on SET-wrapped input, so each is kept.
47
+ // check would silently change behavior on SET-wrapped input, so each is kept.
48
48
 
49
49
  function _assertShape(schema, node, ctx) {
50
50
  var mode = schema.assert || "sequence";
@@ -1228,7 +1228,7 @@ function certExtensionDecoders(ns) {
1228
1228
 
1229
1229
  // qcStatements ::= SEQUENCE OF QCStatement (RFC 3739 sec. 3.2.6). QCStatement ::= SEQUENCE {
1230
1230
  // statementId OBJECT IDENTIFIER, statementInfo ANY DEFINED BY statementId OPTIONAL }. Known statementIds
1231
- // (RFC 3739 id-qcs + the ETSI EN 319 412-5 esi4 catalog) decode their statementInfo; an unknown statementId
1231
+ // (RFC 3739 id-qcs + the ETSI EN 319 412-5 esi4 catalogue) decode their statementInfo; an unknown statementId
1232
1232
  // is preserved OPAQUE (the RFC 3739 open type), never rejected. The DEFINED-BY selection is an OID re-dispatch.
1233
1233
  function _qcStr(node, tag, C, what) {
1234
1234
  if (!node || node.tagClass !== "universal" || node.tagNumber !== tag) throw ns.E(C, what);
package/lib/sigstore.js CHANGED
@@ -228,7 +228,7 @@ function _toMs(x) {
228
228
  // Coverage residual -- _inWindow gates every _toMs call behind a != null check; the null guard is dead defensive code.
229
229
  if (x == null) return null;
230
230
  if (typeof x === "number") return Number.isFinite(x) ? x : null;
231
- if (x instanceof Date) { var d = x.getTime(); return isNaN(d) ? null : d; }
231
+ if (guard.time.isDate(x)) { var d = guard.time.instantOf(x); return isNaN(d) ? null : d; }
232
232
  if (typeof x === "string") { var t = Date.parse(x); return isNaN(t) ? null : t; }
233
233
  return null;
234
234
  }
@@ -671,7 +671,11 @@ async function verifyBundle(bundle, opts) {
671
671
  if (integratedTime === null) throw lastErr;
672
672
 
673
673
  // Leg 3 -- Fulcio chain, validated as of the (trusted) log time; then identity.
674
- var checkTime = (opts.time instanceof Date) ? opts.time.getTime() : C.TIME.seconds(integratedTime);
674
+ // The instant comes through the time guard's intrinsic read. `getTime` is an ordinary method a
675
+ // subclass answers, and the slot predicate above accepts a subclass from any realm, so reading
676
+ // the method off the value let a caller name the moment the Fulcio chain is validated at --
677
+ // epoch zero, say, where every certificate in it is still unissued.
678
+ var checkTime = guard.time.isDate(opts.time) ? guard.time.instantOf(opts.time) : C.TIME.seconds(integratedTime);
675
679
  await _verifyChain(leaf, _chainDers(vm), fulcioRoots, checkTime);
676
680
  var identity = _identity(leaf);
677
681
  var identityChecked = _checkIdentity(identity, opts.identity);
package/lib/smime.js CHANGED
@@ -846,7 +846,7 @@ function _capped(msg) {
846
846
  * compared under RFC 5280 sec. 7.5: the local-part exactly, the host-part case-insensitively. The
847
847
  * address is read from the `subjectAltName` `rfc822Name` entries (RFC 8550 sec. 4.4.3) and, where the
848
848
  * extension carries none, from the subject DN's PKCS #9 `emailAddress` attribute, which RFC 8550 sec. 3
849
- * requires a receiving agent to recognise. Where both are present the extension wins. Only a signer
849
+ * requires a receiving agent to recognize. Where both are present the extension wins. Only a signer
850
850
  * whose signature verified contributes an identity, so a tampered message cannot report a binding. `match` is THREE-valued and a caller enforcing sender binding tests
851
851
  * `match === true` -- `false` means every identity was comparable and none matched, and `null` means the
852
852
  * question was not answered (no `expectedSender` and no single outer `From`, a signer certificate
package/lib/trust.js CHANGED
@@ -347,7 +347,7 @@ function _copyDistrustAfter(src) {
347
347
  var out = {};
348
348
  if (!src || typeof src !== "object") return out;
349
349
  Object.keys(src).forEach(function (k) {
350
- out[k] = src[k] instanceof Date ? new Date(src[k].getTime()) : src[k];
350
+ out[k] = guard.time.isDate(src[k]) ? new Date(guard.time.instantOf(src[k])) : src[k];
351
351
  });
352
352
  return out;
353
353
  }
@@ -375,7 +375,7 @@ function _copyName(name) {
375
375
  function _datesEqual(x, y) {
376
376
  var kx = Object.keys(x).sort(), ky = Object.keys(y).sort();
377
377
  if (kx.join(",") !== ky.join(",")) return false;
378
- return kx.every(function (k) { return x[k].getTime() === y[k].getTime(); });
378
+ return kx.every(function (k) { return guard.time.instantOf(x[k]) === guard.time.instantOf(y[k]); });
379
379
  }
380
380
 
381
381
  function _purposesEqual(x, y) {
package/lib/tsp-sign.js CHANGED
@@ -161,7 +161,7 @@ function _sign(messageImprint, tsa, opts) {
161
161
  // genTime defaults to now; a supplied value MUST be a valid Date (never a silently-ignored
162
162
  // non-Date or an Invalid Date that would encode a garbage GeneralizedTime).
163
163
  if (opts.genTime != null) guard.time.assertValid(opts.genTime, _err, "tsp/bad-input", "genTime");
164
- var genTime = opts.genTime instanceof Date ? opts.genTime : new Date();
164
+ var genTime = guard.time.isDate(opts.genTime) ? opts.genTime : new Date();
165
165
  // A caller-authored INTEGER is coerced through the shared guard, so a value that is not one is a
166
166
  // typed tsp/bad-input rather than a raw SyntaxError or RangeError out of BigInt() -- an untyped
167
167
  // fault escaping a public verb, which a caller cannot catch by code.
@@ -761,7 +761,7 @@ async function verify(token, data, opts) {
761
761
  // notBefore is rejected at the floor (ceil >= notBefore but floor < notBefore). Certificate
762
762
  // validity is seconds-precise (RFC 5280 sec. 4.1.2.5), so this never over-rejects a valid token.
763
763
  var floorT = tst.genTime;
764
- var ceilT = (tst.genTimeFraction != null && /[1-9]/.test(tst.genTimeFraction.slice(3))) ? new Date(floorT.getTime() + 1) : floorT;
764
+ var ceilT = (tst.genTimeFraction != null && /[1-9]/.test(tst.genTimeFraction.slice(3))) ? new Date(guard.time.instantOf(floorT) + 1) : floorT;
765
765
  try {
766
766
  // parseToken surfaces each embedded cert as { bytes, tagClass, ... }; take the X.509 certs
767
767
  // (universal SEQUENCE), skipping any attribute-certificate choices, and parse their raw bytes.
@@ -7,9 +7,9 @@
7
7
  //
8
8
  // validator-attcert -- the SINGLE home for "does this X.509 leaf meet the WebAuthn
9
9
  // attestation-certificate profile" (WebAuthn sec. 8.2.1 packed, sec. 8.3.1 TPM AIK).
10
- // Sibling to the guard family: a validator owns a decoded TYPE's COMPLETE conformance rule
11
- // set once, so the packed and AIK profiles cannot drift apart or lose a gate (e.g. the v3
12
- // check that previously lived split out in the tpm verifier, away from the profile).
10
+ // Sibling to the guard family: a validator owns a decoded type's complete conformance rule
11
+ // set once, so the packed and AIK profiles cannot drift apart or lose a gate. The version
12
+ // check belongs to the profile, not to whichever verifier reaches it first.
13
13
  //
14
14
  // The caller supplies its parsed certificate + an extension-accessor object `exts`
15
15
  // { find(cert, oidName) -> rawExt|null, decode(cert, oidName) -> {critical, value}|null }
@@ -8,7 +8,7 @@
8
8
  // validator-cose -- the SINGLE home for "is this a conformant WebAuthn credential
9
9
  // COSE_Key" (RFC 9052 sec. 7 structure + RFC 9053 EC2/OKP/RSA key parameters + the
10
10
  // CTAP2 canonical-CBOR profile WebAuthn sec. 6.5.1 imposes). Sibling to the guard
11
- // family: where a guard owns a CVE-class fail-closed defence once, a validator owns a
11
+ // family: where a guard owns a CVE-class fail-closed defense once, a validator owns a
12
12
  // decoded TYPE's COMPLETE conformance rule set once, so a format module composes the
13
13
  // family instead of re-deriving a partial subset inline (the drift that leaks MUSTs
14
14
  // out one review round at a time). Enforced by the validator-shape-reinlined
@@ -242,10 +242,10 @@ function assertKeyMaterial(key, E, code, unsupportedCode) {
242
242
  // PROFILE: the declared alg must match the key type (and, for EC2, the curve).
243
243
  var prof = ALG_PROFILE[String(key.alg)];
244
244
  // An algorithm this verifier does not implement is not a malformed key. The key can be perfectly
245
- // well-formed (the same bytes may parse under a neighbouring algorithm id), and a relying
245
+ // well-formed (the same bytes may parse under a neighboring algorithm id), and a relying
246
246
  // party migrating credential rows written elsewhere needs to tell "I cannot check this
247
247
  // algorithm" from "these bytes are wrong", since only one of those is fixable by re-registering.
248
- // Callers that do not distinguish the two pass one code and keep the previous behaviour.
248
+ // Callers that do not distinguish the two pass one code and keep the previous behavior.
249
249
  if (!prof) throw new E(unsupportedCode || code, "unsupported credential key algorithm " + key.alg);
250
250
  if (prof.kty !== key.kty) throw bad("credential key algorithm " + key.alg + " is inconsistent with key type " + key.kty);
251
251
  if (prof.crv != null && prof.crv !== key.crv) throw bad("credential key algorithm " + key.alg + " requires a different curve");
@@ -85,7 +85,7 @@ function _schemeE(kind, message, cause) { return new WebauthnError("webauthn/" +
85
85
  // WebCrypto algorithms.
86
86
  //
87
87
  // Null-prototype: `alg` is attacker-supplied, and an inherited Object member would otherwise
88
- // resolve to a truthy non-row and read as a recognised algorithm.
88
+ // resolve to a truthy non-row and read as a recognized algorithm.
89
89
  function _deriveBlobAlgs() {
90
90
  var out = Object.create(null);
91
91
  jose.sigAlgs().forEach(function (row) {
@@ -120,7 +120,7 @@ function _blobAlgParams(algRow, leafAlgName) {
120
120
 
121
121
  // The status values that deny trust (MDS v3.0 sec. 3.1.4). An unknown status is IGNORED for the
122
122
  // gate and surfaced raw -- the specification requires that a verifier not fail on a status it does
123
- // not recognise -- unless the caller opts into refusing them.
123
+ // not recognize -- unless the caller opts into refusing them.
124
124
  var DISQUALIFYING = Object.assign(Object.create(null), {
125
125
  REVOKED: 1, ATTESTATION_KEY_COMPROMISE: 1, USER_KEY_REMOTE_COMPROMISE: 1,
126
126
  USER_KEY_PHYSICAL_COMPROMISE: 1, USER_VERIFICATION_BYPASS: 1,
@@ -140,7 +140,7 @@ var _BLOB_OPTS = Object.assign(Object.create(null), {
140
140
  function _isPlainObject(v) { return !!v && typeof v === "object" && !Array.isArray(v); }
141
141
 
142
142
  // The results this module actually produced. Membership is the PROVENANCE check: a metadata object
143
- // is only allowed to decide trust if verifyMetadataBlob made it. Recognising it by shape instead
143
+ // is only allowed to decide trust if verifyMetadataBlob made it. Recognizing it by shape instead
144
144
  // would accept anything carrying the right property names, including a catalogue deserialized
145
145
  // from a cache, where nothing establishes that the signature and chain were ever checked, and an
146
146
  // attacker who can write that cache chooses which roots an authenticator is allowed to chain to.
@@ -207,7 +207,7 @@ function _isAnchorItself(cert, anchor) {
207
207
  //
208
208
  // An already-parsed certificate is taken as-is, never re-encoded and re-parsed, but the
209
209
  // recognition test names every field the anchor is later read for: the subject name, the public
210
- // key bytes, and the signature algorithm OID. Recognising an object on a looser test lets something
210
+ // key bytes, and the signature algorithm OID. Recognizing an object on a looser test lets something
211
211
  // that is merely certificate-SHAPED through: a parsed CSR satisfies "has a subject and an SPKI", and
212
212
  // a hand-built object literal satisfies it too and then raises a raw TypeError from deep inside the
213
213
  // path validator, which is an untyped throw escaping a public verb.
@@ -250,8 +250,8 @@ function _verifyMetadataBlob(blob, opts) {
250
250
  throw _err("webauthn/bad-input", "opts.previousNo must be a non-negative safe integer");
251
251
  }
252
252
  // The boolean options are checked for TYPE, not merely compared against true. A caller who wrote
253
- // `rejectUnknownStatus: "true"` from a config file is asking for the stricter behaviour; comparing
254
- // against `true` silently records the policy as off and the authenticator carrying an unrecognised
253
+ // `rejectUnknownStatus: "true"` from a config file is asking for the stricter behavior; comparing
254
+ // against `true` silently records the policy as off and the authenticator carrying an unrecognized
255
255
  // status is then accepted -- the fail-open the option exists to prevent.
256
256
  ["requireRollbackCheck", "allowStale", "rejectUnknownStatus"].forEach(function (k) {
257
257
  if (opts[k] !== undefined && typeof opts[k] !== "boolean") {
@@ -326,7 +326,7 @@ function _verifyMetadataBlob(blob, opts) {
326
326
  maxBytes: C.MDS_BLOB_HEADER_MAX_BYTES, maxDepth: C.JSON_MAX_DEPTH,
327
327
  tooLarge: "webauthn/too-large", badJson: "webauthn/bad-metadata-blob",
328
328
  // Every code the guard can raise is named. An omitted one falls back to the framework default
329
- // and the module's own defences -- duplicate-member smuggling, the depth cap -- surface under
329
+ // and the module's own defenses -- duplicate-member smuggling, the depth cap -- surface under
330
330
  // a generic code no webauthn/* consumer can switch on.
331
331
  tooDeep: "webauthn/bad-metadata-blob", duplicateMember: "webauthn/bad-metadata-blob",
332
332
  badInput: "webauthn/bad-metadata-blob", label: "the metadata BLOB header",
@@ -449,7 +449,7 @@ function _verifyMetadataBlob(blob, opts) {
449
449
  // anchors through this same walk while keeping the verdict its own callers already handle. A second copy
450
450
  // of the walk is not merely duplication: the anchor-stripping rule is subtle (a terminal
451
451
  // certificate that IS the anchor is identified by subject name AND public key, which is what
452
- // recognises a cross-signed root), and a copy that got it slightly differently would refuse a valid
452
+ // recognizes a cross-signed root), and a copy that got it slightly differently would refuse a valid
453
453
  // chain in one place and accept a self-validated one in another.
454
454
  function _chainToAnchor(chain, anchors, at, what, code) {
455
455
  var subject = what || "metadata BLOB certificate chain";
@@ -502,18 +502,18 @@ function _chainToAnchor(chain, anchors, at, what, code) {
502
502
  function _staleAfter(nextUpdate) {
503
503
  var d = rfc3339.parseDate(nextUpdate, function (c, m) { return _err("webauthn/bad-metadata-blob", m); },
504
504
  "webauthn/bad-metadata-blob", "the metadata BLOB nextUpdate");
505
- return d.getTime() + constants.TIME.days(1);
505
+ return guard.time.instantOf(d) + constants.TIME.days(1);
506
506
  }
507
507
 
508
508
  // A verified result is a plain object the caller may hold for as long as it likes, so its freshness
509
509
  // has to be re-established every time it decides something, not only when it was parsed. A
510
510
  // catalogue fetched before its nextUpdate and reused a month later would otherwise keep authorizing
511
511
  // an authenticator whose status reports have since revoked it, which is precisely what nextUpdate
512
- // exists to prevent. The caller's original allowStale decision is carried on the result and honoured
512
+ // exists to prevent. The caller's original allowStale decision is carried on the result and honored
513
513
  // here, so opting out stays opted out and never silently reappears at the point of use.
514
514
  function assertFresh(metadata, at, label) {
515
515
  if (!metadata || metadata.allowStale === true || typeof metadata.nextUpdate !== "string") return;
516
- var atMs = at instanceof Date ? at.getTime() : NaN;
516
+ var atMs = guard.time.isDate(at) ? guard.time.instantOf(at) : NaN;
517
517
  var limit = _staleAfter(metadata.nextUpdate);
518
518
  if (!isFinite(atMs) || !isFinite(limit)) return;
519
519
  if (atMs >= limit) {
@@ -547,7 +547,7 @@ function _parsePayload(seg, at, opts) {
547
547
  throw _err("webauthn/metadata-rollback", "the metadata BLOB no " + payload.no + " does not exceed the previously held " + opts.previousNo);
548
548
  }
549
549
  var staleAfter = _staleAfter(payload.nextUpdate);
550
- var atMs = at.getTime();
550
+ var atMs = guard.time.instantOf(at);
551
551
  // An unusable comparison instant must not read as "fresh": without this, a NaN date makes every
552
552
  // comparison false and the staleness check silently passes.
553
553
  //
@@ -696,7 +696,7 @@ function metadataFor(metadata, identifier) {
696
696
  return null;
697
697
  }
698
698
 
699
- // The options metadataAnchors recognises. Null-prototype for the same reason every other
699
+ // The options metadataAnchors recognizes. Null-prototype for the same reason every other
700
700
  // caller-keyed table in this namespace is: a supplied key must not resolve to an inherited member.
701
701
  var _ANCHOR_OPTS = Object.assign(Object.create(null), { metadata: 1, time: 1, certificate: 1 });
702
702
 
@@ -791,7 +791,7 @@ function _reportInForceAt(report, atMs) {
791
791
  // parseDate throws and never returns an Invalid Date, so d is finite; atMs was isFinite-
792
792
  // checked by the caller before this runs.
793
793
  // allow:nan-date-comparison-unguarded -- both operands are source-validated, as described above.
794
- return d.getTime() <= atMs;
794
+ return guard.time.instantOf(d) <= atMs;
795
795
  }
796
796
 
797
797
  function statusDenied(entry, metadata, leaf, at) {
@@ -817,7 +817,7 @@ function statusDenied(entry, metadata, leaf, at) {
817
817
  // after the time it asks about. When every dated report is still in the future, the answer is
818
818
  // that none of them is in force, not that all of them are.
819
819
  var isDated = function (r) { return r && typeof r.effectiveDate === "string" && rfc3339.isValidDate(r.effectiveDate); };
820
- var atMs = (at instanceof Date && isFinite(at.getTime())) ? at.getTime() : null;
820
+ var atMs = (guard.time.isDate(at) && isFinite(guard.time.instantOf(at))) ? guard.time.instantOf(at) : null;
821
821
  if (atMs !== null) {
822
822
  reports = reports.filter(function (r) { return !isDated(r) || _reportInForceAt(r, atMs); });
823
823
  }
@@ -846,13 +846,13 @@ function statusDenied(entry, metadata, leaf, at) {
846
846
  if (s === null) return false;
847
847
  // Scope was settled above, so anything still here applies to this authenticator.
848
848
  if (DISQUALIFYING[s]) return true;
849
- // An unrecognised status is IGNORED for the gate unless the caller opted in: the specification
849
+ // An unrecognized status is IGNORED for the gate unless the caller opted in: the specification
850
850
  // requires a verifier not to fail on a status value it does not know.
851
851
  return rejectUnknown && !_KNOWN_STATUS[s];
852
852
  });
853
853
  }
854
854
 
855
- // Status values this library recognises as non-disqualifying, so `rejectUnknownStatus` can tell an
855
+ // Status values this library recognizes as non-disqualifying, so `rejectUnknownStatus` can tell an
856
856
  // unknown value from a known-good one.
857
857
  var _KNOWN_STATUS = Object.assign(Object.create(null), {
858
858
  NOT_FIDO_CERTIFIED: 1, SELF_ASSERTION_SUBMITTED: 1, FIDO_CERTIFIED: 1, FIDO_CERTIFIED_L1: 1,