@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.
- package/CHANGELOG.md +80 -19
- package/README.md +1 -1
- package/lib/acme.js +5 -5
- package/lib/asn1-der.js +1 -1
- package/lib/attrcert-sign.js +1 -1
- package/lib/cbor-det.js +4 -4
- package/lib/cmc-build.js +5 -5
- package/lib/cmc-verify.js +14 -11
- package/lib/cms-decrypt.js +2 -2
- package/lib/cms-sign.js +3 -5
- package/lib/cms-verify.js +9 -6
- package/lib/constants.js +1 -1
- package/lib/crl-sign.js +1 -1
- package/lib/crmf-sign.js +1 -1
- package/lib/ct.js +8 -5
- package/lib/est.js +20 -14
- package/lib/guard-all.js +12 -12
- package/lib/guard-bytes.js +568 -82
- package/lib/guard-compress.js +2 -2
- package/lib/guard-der.js +1 -1
- package/lib/guard-identifier.js +694 -6
- package/lib/guard-limits.js +2 -2
- package/lib/guard-parsed.js +10 -6
- package/lib/guard-range.js +1 -1
- package/lib/guard-secret.js +1 -1
- package/lib/guard-text.js +1 -1
- package/lib/guard-time.js +65 -4
- package/lib/hpke.js +1 -1
- package/lib/http-digest.js +1 -1
- package/lib/inspect.js +8 -3
- package/lib/jose.js +1 -1
- package/lib/key.js +48 -6
- package/lib/lint.js +15 -8
- package/lib/ocsp-verify.js +10 -4
- package/lib/ocsp.js +35 -6
- package/lib/oid.js +1 -1
- package/lib/path-validate.js +99 -22
- package/lib/schema-c509.js +5 -5
- package/lib/schema-cmp.js +1 -1
- package/lib/schema-crl.js +1 -1
- package/lib/schema-engine.js +2 -2
- package/lib/schema-pkix.js +1 -1
- package/lib/sigstore.js +6 -2
- package/lib/smime.js +1 -1
- package/lib/trust.js +2 -2
- package/lib/tsp-sign.js +2 -2
- package/lib/validator-attcert.js +3 -3
- package/lib/validator-cose.js +3 -3
- package/lib/webauthn-mds.js +17 -17
- package/lib/webauthn.js +12 -12
- package/lib/webcrypto.js +16 -7
- package/lib/x509-sign.js +1 -1
- package/package.json +4 -2
- package/sbom.cdx.json +6 -6
package/lib/path-validate.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
|
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
|
|
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;
|
|
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 ||
|
|
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 &&
|
|
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,
|
|
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
|
|
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
|
|
2670
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
2969
|
-
|
|
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
|
|
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
|
package/lib/schema-c509.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
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");
|
package/lib/schema-engine.js
CHANGED
|
@@ -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
|
|
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
|
|
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";
|
package/lib/schema-pkix.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
-
|
|
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
|
|
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]
|
|
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]
|
|
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
|
|
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(
|
|
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.
|
package/lib/validator-attcert.js
CHANGED
|
@@ -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
|
|
11
|
-
// set once, so the packed and AIK profiles cannot drift apart or lose a gate
|
|
12
|
-
// check
|
|
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 }
|
package/lib/validator-cose.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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");
|
package/lib/webauthn-mds.js
CHANGED
|
@@ -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
|
|
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
|
|
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.
|
|
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.
|
|
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
|
|
254
|
-
// against `true` silently records the policy as off and the authenticator carrying an
|
|
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
|
|
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
|
-
//
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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,
|