@proof-holdings/sdk 0.0.0-stage → 1.2.0
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 +54 -0
- package/LICENSE +21 -0
- package/README.md +239 -2
- package/dist/cjs/client.d.ts +48 -0
- package/dist/cjs/client.d.ts.map +1 -0
- package/dist/cjs/client.js +73 -0
- package/dist/cjs/client.js.map +1 -0
- package/dist/cjs/credentials.d.ts +46 -0
- package/dist/cjs/credentials.d.ts.map +1 -0
- package/dist/cjs/credentials.js +137 -0
- package/dist/cjs/credentials.js.map +1 -0
- package/dist/cjs/crypto/hitl-crypto.d.ts +95 -0
- package/dist/cjs/crypto/hitl-crypto.d.ts.map +1 -0
- package/dist/cjs/crypto/hitl-crypto.js +338 -0
- package/dist/cjs/crypto/hitl-crypto.js.map +1 -0
- package/dist/cjs/errors.d.ts +52 -0
- package/dist/cjs/errors.d.ts.map +1 -0
- package/dist/cjs/errors.js +121 -0
- package/dist/cjs/errors.js.map +1 -0
- package/dist/cjs/http.d.ts +62 -0
- package/dist/cjs/http.d.ts.map +1 -0
- package/dist/cjs/http.js +192 -0
- package/dist/cjs/http.js.map +1 -0
- package/dist/cjs/index.d.ts +26 -0
- package/dist/cjs/index.d.ts.map +1 -0
- package/dist/cjs/index.js +79 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/jwks/key-set-cache.d.ts +105 -0
- package/dist/cjs/jwks/key-set-cache.d.ts.map +1 -0
- package/dist/cjs/jwks/key-set-cache.js +277 -0
- package/dist/cjs/jwks/key-set-cache.js.map +1 -0
- package/dist/cjs/jwks/keyset.d.ts +139 -0
- package/dist/cjs/jwks/keyset.d.ts.map +1 -0
- package/dist/cjs/jwks/keyset.js +503 -0
- package/dist/cjs/jwks/keyset.js.map +1 -0
- package/dist/cjs/jwks/support.d.ts +9 -0
- package/dist/cjs/jwks/support.d.ts.map +1 -0
- package/dist/cjs/jwks/support.js +12 -0
- package/dist/cjs/jwks/support.js.map +1 -0
- package/dist/cjs/polling.d.ts +10 -0
- package/dist/cjs/polling.d.ts.map +1 -0
- package/dist/cjs/polling.js +74 -0
- package/dist/cjs/polling.js.map +1 -0
- package/dist/cjs/resources/auth-sessions.d.ts +41 -0
- package/dist/cjs/resources/auth-sessions.d.ts.map +1 -0
- package/dist/cjs/resources/auth-sessions.js +34 -0
- package/dist/cjs/resources/auth-sessions.js.map +1 -0
- package/dist/cjs/resources/authorizations.d.ts +23 -0
- package/dist/cjs/resources/authorizations.d.ts.map +1 -0
- package/dist/cjs/resources/authorizations.js +29 -0
- package/dist/cjs/resources/authorizations.js.map +1 -0
- package/dist/cjs/resources/circles.d.ts +38 -0
- package/dist/cjs/resources/circles.d.ts.map +1 -0
- package/dist/cjs/resources/circles.js +67 -0
- package/dist/cjs/resources/circles.js.map +1 -0
- package/dist/cjs/resources/confirmations.d.ts +111 -0
- package/dist/cjs/resources/confirmations.d.ts.map +1 -0
- package/dist/cjs/resources/confirmations.js +86 -0
- package/dist/cjs/resources/confirmations.js.map +1 -0
- package/dist/cjs/resources/delegations.d.ts +36 -0
- package/dist/cjs/resources/delegations.d.ts.map +1 -0
- package/dist/cjs/resources/delegations.js +47 -0
- package/dist/cjs/resources/delegations.js.map +1 -0
- package/dist/cjs/resources/hitl-chat-id.d.ts +42 -0
- package/dist/cjs/resources/hitl-chat-id.d.ts.map +1 -0
- package/dist/cjs/resources/hitl-chat-id.js +37 -0
- package/dist/cjs/resources/hitl-chat-id.js.map +1 -0
- package/dist/cjs/resources/hitl-keys.d.ts +13 -0
- package/dist/cjs/resources/hitl-keys.d.ts.map +1 -0
- package/dist/cjs/resources/hitl-keys.js +22 -0
- package/dist/cjs/resources/hitl-keys.js.map +1 -0
- package/dist/cjs/resources/hitl.d.ts +39 -0
- package/dist/cjs/resources/hitl.d.ts.map +1 -0
- package/dist/cjs/resources/hitl.js +58 -0
- package/dist/cjs/resources/hitl.js.map +1 -0
- package/dist/cjs/resources/me.d.ts +102 -0
- package/dist/cjs/resources/me.d.ts.map +1 -0
- package/dist/cjs/resources/me.js +85 -0
- package/dist/cjs/resources/me.js.map +1 -0
- package/dist/cjs/resources/profiles.d.ts +47 -0
- package/dist/cjs/resources/profiles.d.ts.map +1 -0
- package/dist/cjs/resources/profiles.js +70 -0
- package/dist/cjs/resources/profiles.js.map +1 -0
- package/dist/cjs/resources/proof-me.d.ts +48 -0
- package/dist/cjs/resources/proof-me.d.ts.map +1 -0
- package/dist/cjs/resources/proof-me.js +24 -0
- package/dist/cjs/resources/proof-me.js.map +1 -0
- package/dist/cjs/resources/proofs.d.ts +81 -0
- package/dist/cjs/resources/proofs.d.ts.map +1 -0
- package/dist/cjs/resources/proofs.js +437 -0
- package/dist/cjs/resources/proofs.js.map +1 -0
- package/dist/cjs/resources/public-profiles.d.ts +36 -0
- package/dist/cjs/resources/public-profiles.d.ts.map +1 -0
- package/dist/cjs/resources/public-profiles.js +49 -0
- package/dist/cjs/resources/public-profiles.js.map +1 -0
- package/dist/cjs/resources/sessions.d.ts +15 -0
- package/dist/cjs/resources/sessions.d.ts.map +1 -0
- package/dist/cjs/resources/sessions.js +26 -0
- package/dist/cjs/resources/sessions.js.map +1 -0
- package/dist/cjs/resources/templates.d.ts +25 -0
- package/dist/cjs/resources/templates.d.ts.map +1 -0
- package/dist/cjs/resources/templates.js +46 -0
- package/dist/cjs/resources/templates.js.map +1 -0
- package/dist/cjs/resources/verification-requests.d.ts +56 -0
- package/dist/cjs/resources/verification-requests.d.ts.map +1 -0
- package/dist/cjs/resources/verification-requests.js +61 -0
- package/dist/cjs/resources/verification-requests.js.map +1 -0
- package/dist/cjs/resources/verifications.d.ts +60 -0
- package/dist/cjs/resources/verifications.d.ts.map +1 -0
- package/dist/cjs/resources/verifications.js +91 -0
- package/dist/cjs/resources/verifications.js.map +1 -0
- package/dist/cjs/resources/webhook-deliveries.d.ts +15 -0
- package/dist/cjs/resources/webhook-deliveries.d.ts.map +1 -0
- package/dist/cjs/resources/webhook-deliveries.js +26 -0
- package/dist/cjs/resources/webhook-deliveries.js.map +1 -0
- package/dist/cjs/streaming.d.ts +87 -0
- package/dist/cjs/streaming.d.ts.map +1 -0
- package/dist/cjs/streaming.js +289 -0
- package/dist/cjs/streaming.js.map +1 -0
- package/dist/cjs/types.d.ts +1483 -0
- package/dist/cjs/types.d.ts.map +1 -0
- package/dist/cjs/types.js +6 -0
- package/dist/cjs/types.js.map +1 -0
- package/dist/cjs/version.d.ts +2 -0
- package/dist/cjs/version.d.ts.map +1 -0
- package/dist/cjs/version.js +5 -0
- package/dist/cjs/version.js.map +1 -0
- package/dist/esm/client.d.ts +48 -0
- package/dist/esm/client.d.ts.map +1 -0
- package/dist/esm/client.js +69 -0
- package/dist/esm/client.js.map +1 -0
- package/dist/esm/credentials.d.ts +46 -0
- package/dist/esm/credentials.d.ts.map +1 -0
- package/dist/esm/credentials.js +127 -0
- package/dist/esm/credentials.js.map +1 -0
- package/dist/esm/crypto/hitl-crypto.d.ts +95 -0
- package/dist/esm/crypto/hitl-crypto.d.ts.map +1 -0
- package/dist/esm/crypto/hitl-crypto.js +321 -0
- package/dist/esm/crypto/hitl-crypto.js.map +1 -0
- package/dist/esm/errors.d.ts +52 -0
- package/dist/esm/errors.d.ts.map +1 -0
- package/dist/esm/errors.js +106 -0
- package/dist/esm/errors.js.map +1 -0
- package/dist/esm/http.d.ts +62 -0
- package/dist/esm/http.d.ts.map +1 -0
- package/dist/esm/http.js +187 -0
- package/dist/esm/http.js.map +1 -0
- package/dist/esm/index.d.ts +26 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +33 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/jwks/key-set-cache.d.ts +105 -0
- package/dist/esm/jwks/key-set-cache.d.ts.map +1 -0
- package/dist/esm/jwks/key-set-cache.js +268 -0
- package/dist/esm/jwks/key-set-cache.js.map +1 -0
- package/dist/esm/jwks/keyset.d.ts +139 -0
- package/dist/esm/jwks/keyset.d.ts.map +1 -0
- package/dist/esm/jwks/keyset.js +489 -0
- package/dist/esm/jwks/keyset.js.map +1 -0
- package/dist/esm/jwks/support.d.ts +9 -0
- package/dist/esm/jwks/support.d.ts.map +1 -0
- package/dist/esm/jwks/support.js +9 -0
- package/dist/esm/jwks/support.js.map +1 -0
- package/dist/esm/package.json +1 -0
- package/dist/esm/polling.d.ts +10 -0
- package/dist/esm/polling.d.ts.map +1 -0
- package/dist/esm/polling.js +71 -0
- package/dist/esm/polling.js.map +1 -0
- package/dist/esm/resources/auth-sessions.d.ts +41 -0
- package/dist/esm/resources/auth-sessions.d.ts.map +1 -0
- package/dist/esm/resources/auth-sessions.js +30 -0
- package/dist/esm/resources/auth-sessions.js.map +1 -0
- package/dist/esm/resources/authorizations.d.ts +23 -0
- package/dist/esm/resources/authorizations.d.ts.map +1 -0
- package/dist/esm/resources/authorizations.js +25 -0
- package/dist/esm/resources/authorizations.js.map +1 -0
- package/dist/esm/resources/circles.d.ts +38 -0
- package/dist/esm/resources/circles.d.ts.map +1 -0
- package/dist/esm/resources/circles.js +63 -0
- package/dist/esm/resources/circles.js.map +1 -0
- package/dist/esm/resources/confirmations.d.ts +111 -0
- package/dist/esm/resources/confirmations.d.ts.map +1 -0
- package/dist/esm/resources/confirmations.js +82 -0
- package/dist/esm/resources/confirmations.js.map +1 -0
- package/dist/esm/resources/delegations.d.ts +36 -0
- package/dist/esm/resources/delegations.d.ts.map +1 -0
- package/dist/esm/resources/delegations.js +43 -0
- package/dist/esm/resources/delegations.js.map +1 -0
- package/dist/esm/resources/hitl-chat-id.d.ts +42 -0
- package/dist/esm/resources/hitl-chat-id.d.ts.map +1 -0
- package/dist/esm/resources/hitl-chat-id.js +33 -0
- package/dist/esm/resources/hitl-chat-id.js.map +1 -0
- package/dist/esm/resources/hitl-keys.d.ts +13 -0
- package/dist/esm/resources/hitl-keys.d.ts.map +1 -0
- package/dist/esm/resources/hitl-keys.js +18 -0
- package/dist/esm/resources/hitl-keys.js.map +1 -0
- package/dist/esm/resources/hitl.d.ts +39 -0
- package/dist/esm/resources/hitl.d.ts.map +1 -0
- package/dist/esm/resources/hitl.js +54 -0
- package/dist/esm/resources/hitl.js.map +1 -0
- package/dist/esm/resources/me.d.ts +102 -0
- package/dist/esm/resources/me.d.ts.map +1 -0
- package/dist/esm/resources/me.js +81 -0
- package/dist/esm/resources/me.js.map +1 -0
- package/dist/esm/resources/profiles.d.ts +47 -0
- package/dist/esm/resources/profiles.d.ts.map +1 -0
- package/dist/esm/resources/profiles.js +66 -0
- package/dist/esm/resources/profiles.js.map +1 -0
- package/dist/esm/resources/proof-me.d.ts +48 -0
- package/dist/esm/resources/proof-me.d.ts.map +1 -0
- package/dist/esm/resources/proof-me.js +20 -0
- package/dist/esm/resources/proof-me.js.map +1 -0
- package/dist/esm/resources/proofs.d.ts +81 -0
- package/dist/esm/resources/proofs.d.ts.map +1 -0
- package/dist/esm/resources/proofs.js +433 -0
- package/dist/esm/resources/proofs.js.map +1 -0
- package/dist/esm/resources/public-profiles.d.ts +36 -0
- package/dist/esm/resources/public-profiles.d.ts.map +1 -0
- package/dist/esm/resources/public-profiles.js +45 -0
- package/dist/esm/resources/public-profiles.js.map +1 -0
- package/dist/esm/resources/sessions.d.ts +15 -0
- package/dist/esm/resources/sessions.d.ts.map +1 -0
- package/dist/esm/resources/sessions.js +22 -0
- package/dist/esm/resources/sessions.js.map +1 -0
- package/dist/esm/resources/templates.d.ts +25 -0
- package/dist/esm/resources/templates.d.ts.map +1 -0
- package/dist/esm/resources/templates.js +42 -0
- package/dist/esm/resources/templates.js.map +1 -0
- package/dist/esm/resources/verification-requests.d.ts +56 -0
- package/dist/esm/resources/verification-requests.d.ts.map +1 -0
- package/dist/esm/resources/verification-requests.js +57 -0
- package/dist/esm/resources/verification-requests.js.map +1 -0
- package/dist/esm/resources/verifications.d.ts +60 -0
- package/dist/esm/resources/verifications.d.ts.map +1 -0
- package/dist/esm/resources/verifications.js +87 -0
- package/dist/esm/resources/verifications.js.map +1 -0
- package/dist/esm/resources/webhook-deliveries.d.ts +15 -0
- package/dist/esm/resources/webhook-deliveries.d.ts.map +1 -0
- package/dist/esm/resources/webhook-deliveries.js +22 -0
- package/dist/esm/resources/webhook-deliveries.js.map +1 -0
- package/dist/esm/streaming.d.ts +87 -0
- package/dist/esm/streaming.d.ts.map +1 -0
- package/dist/esm/streaming.js +286 -0
- package/dist/esm/streaming.js.map +1 -0
- package/dist/esm/types.d.ts +1483 -0
- package/dist/esm/types.d.ts.map +1 -0
- package/dist/esm/types.js +5 -0
- package/dist/esm/types.js.map +1 -0
- package/dist/esm/version.d.ts +2 -0
- package/dist/esm/version.d.ts.map +1 -0
- package/dist/esm/version.js +2 -0
- package/dist/esm/version.js.map +1 -0
- package/package.json +64 -4
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The issuer's key set as offline verification holds it — the I/O half around `keyset.ts`, which
|
|
4
|
+
* owns the rules. Mirrors `packages/delegation-verifier/src/jwks.ts`, minus persistence: this SDK
|
|
5
|
+
* keeps tombstones in memory only.
|
|
6
|
+
*
|
|
7
|
+
* Replaces jose's `createRemoteJWKSet`, which hid the fetch time and the registry's own JWK members
|
|
8
|
+
* and so could enforce neither tombstones, windows nor staleness. The behaviours it supplied are
|
|
9
|
+
* kept: a 10-minute reuse window, one forced refetch per 30 s for an unknown `kid`, one fetch in
|
|
10
|
+
* flight at a time. What it did not do, and this does:
|
|
11
|
+
*
|
|
12
|
+
* - A fetch is MERGED into what the verifier knows (`mergeFetchedDocument`), never swapped in.
|
|
13
|
+
* - A failed refetch does not discard the cache. Within `maxJwksAgeMs` its CONFIRMED keys keep
|
|
14
|
+
* answering; beyond it the answer is `stale` — except an applied tombstone, which is `revoked`
|
|
15
|
+
* at any age. A set never fetched has nothing to fall back on and stays `unavailable`.
|
|
16
|
+
* - A failed refetch of a cached set is retried at most once per cooldown, so an outage does not
|
|
17
|
+
* turn every verification into an outbound request.
|
|
18
|
+
* - Age is measured on a monotonic clock; claim checks use the wall clock.
|
|
19
|
+
*/
|
|
20
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
+
exports.KeySetCache = exports.MAX_JWKS_AGE_CAP_MS = exports.DEFAULT_MAX_JWKS_AGE_MS = exports.KEY_SET_TTL_MS = exports.KEY_MISS_COOLDOWN_MS = exports.SUPPORTED_ALGORITHMS = void 0;
|
|
22
|
+
exports.setJwksClockForTests = setJwksClockForTests;
|
|
23
|
+
exports.setJwksEventListenerForTests = setJwksEventListenerForTests;
|
|
24
|
+
exports.nowWallMs = nowWallMs;
|
|
25
|
+
exports.assertMaxJwksAgeMs = assertMaxJwksAgeMs;
|
|
26
|
+
exports.isSupportedAlgorithm = isSupportedAlgorithm;
|
|
27
|
+
const jose_1 = require("jose");
|
|
28
|
+
const http_js_1 = require("../http.js");
|
|
29
|
+
const keyset_js_1 = require("./keyset.js");
|
|
30
|
+
const support_js_1 = require("./support.js");
|
|
31
|
+
exports.SUPPORTED_ALGORITHMS = ['RS256', 'ES256'];
|
|
32
|
+
/** How long a `kid` miss — or a failed refetch of a cached set — suppresses further fetches. */
|
|
33
|
+
exports.KEY_MISS_COOLDOWN_MS = 30000;
|
|
34
|
+
/** How long a fetched key set is reused without refetching (jose's default, kept). */
|
|
35
|
+
exports.KEY_SET_TTL_MS = 10 * 60000;
|
|
36
|
+
/** Default `maxJwksAgeMs`. */
|
|
37
|
+
exports.DEFAULT_MAX_JWKS_AGE_MS = 24 * 60 * 60000;
|
|
38
|
+
/** Largest accepted `maxJwksAgeMs` — below the issuer's ~30-day lead on its next key. */
|
|
39
|
+
exports.MAX_JWKS_AGE_CAP_MS = 25 * 24 * 60 * 60000;
|
|
40
|
+
const JWKS_TIMEOUT_MS = 5000;
|
|
41
|
+
/** A key set is kilobytes; anything larger is not one. */
|
|
42
|
+
const MAX_JWKS_BYTES = 512 * 1024;
|
|
43
|
+
const systemClock = {
|
|
44
|
+
monotonicMs: () => performance.now(),
|
|
45
|
+
wallMs: () => Date.now(),
|
|
46
|
+
};
|
|
47
|
+
let clock = systemClock;
|
|
48
|
+
let eventListener = null;
|
|
49
|
+
/** Test seam — replaces both clocks; `null` restores the system clocks. Not exported from the package. */
|
|
50
|
+
function setJwksClockForTests(replacement) {
|
|
51
|
+
clock = replacement ?? systemClock;
|
|
52
|
+
}
|
|
53
|
+
/** Test seam — receives every key-set event; `null` detaches. Not exported from the package. */
|
|
54
|
+
function setJwksEventListenerForTests(listener) {
|
|
55
|
+
eventListener = listener;
|
|
56
|
+
}
|
|
57
|
+
function nowWallMs() {
|
|
58
|
+
return clock.wallMs();
|
|
59
|
+
}
|
|
60
|
+
/** Throws unless `value` is a usable `maxJwksAgeMs`: whole milliseconds, 1 … 25 days. */
|
|
61
|
+
function assertMaxJwksAgeMs(value) {
|
|
62
|
+
if (typeof value !== 'number' || !Number.isInteger(value) || value <= 0 || value > exports.MAX_JWKS_AGE_CAP_MS) {
|
|
63
|
+
throw new Error(`maxJwksAgeMs must be a whole number of milliseconds between 1 and ${exports.MAX_JWKS_AGE_CAP_MS} (25 days)`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function isSupportedAlgorithm(alg) {
|
|
67
|
+
return typeof alg === 'string' && exports.SUPPORTED_ALGORITHMS.includes(alg);
|
|
68
|
+
}
|
|
69
|
+
class KeySetCache {
|
|
70
|
+
constructor(http, jwksUri, maxJwksAgeMs) {
|
|
71
|
+
this.http = http;
|
|
72
|
+
this.jwksUri = jwksUri;
|
|
73
|
+
this.maxJwksAgeMs = maxJwksAgeMs;
|
|
74
|
+
this.state = (0, keyset_js_1.emptyKeySetState)();
|
|
75
|
+
/** Imported keys by key material + algorithm; `null` records a JWK that failed to import. */
|
|
76
|
+
this.imported = new Map();
|
|
77
|
+
this.lastMissAt = null;
|
|
78
|
+
this.lastFailedAt = null;
|
|
79
|
+
this.inFlight = null;
|
|
80
|
+
this.forceNext = false;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* The keys that may verify a token of `family` carrying `kid` — or why none may. Fetches when the
|
|
84
|
+
* set is older than the TTL, after `forceRefresh()`, or once per cooldown when `kid` is unknown.
|
|
85
|
+
*/
|
|
86
|
+
async resolve(kid, alg, family) {
|
|
87
|
+
const now = clock.monotonicMs();
|
|
88
|
+
const forced = this.forceNext;
|
|
89
|
+
if (!forced && this.isFresh(now)) {
|
|
90
|
+
const cached = await this.resolveFromState(this.state, kid, family, alg);
|
|
91
|
+
if (cached.kind !== 'unknown')
|
|
92
|
+
return cached;
|
|
93
|
+
if (this.lastMissAt !== null && now - this.lastMissAt < exports.KEY_MISS_COOLDOWN_MS) {
|
|
94
|
+
// The kid may have been published since the last refetch: "could not check", not "no such key".
|
|
95
|
+
return { kind: 'unavailable', message: 'unknown key; the key set is refetched again after the cooldown' };
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
const recentlyFailed = !forced &&
|
|
99
|
+
this.state.fetchedAt !== null &&
|
|
100
|
+
this.lastFailedAt !== null &&
|
|
101
|
+
now - this.lastFailedAt < exports.KEY_MISS_COOLDOWN_MS;
|
|
102
|
+
const outcome = recentlyFailed
|
|
103
|
+
? { ok: false, message: 'JWKS refetch failed recently; it is retried after the cooldown' }
|
|
104
|
+
: await this.runFetch();
|
|
105
|
+
if (!outcome.ok) {
|
|
106
|
+
if (this.state.fetchedAt === null)
|
|
107
|
+
return { kind: 'unavailable', message: outcome.message };
|
|
108
|
+
// An applied tombstone is final whatever the cache's age: no refetch could lift it.
|
|
109
|
+
const known = (0, keyset_js_1.selectKeys)(this.state, kid, family);
|
|
110
|
+
if (known.kind === 'revoked')
|
|
111
|
+
return known;
|
|
112
|
+
const age = this.cacheAgeMs(clock.monotonicMs());
|
|
113
|
+
if (age > this.maxJwksAgeMs) {
|
|
114
|
+
return { kind: 'stale', cacheAgeSeconds: Number.isFinite(age) ? Math.floor(age / 1000) : null };
|
|
115
|
+
}
|
|
116
|
+
const fallback = (0, keyset_js_1.withConfirmedKeysOnly)(this.state);
|
|
117
|
+
// A no-kid token tries every candidate: if the fallback lost one, a signature matching none of
|
|
118
|
+
// the rest may still be that key's — "could not check", not a forgery.
|
|
119
|
+
if (kid === undefined && candidateCount(fallback, family) < candidateCount(this.state, family)) {
|
|
120
|
+
return { kind: 'unavailable', message: outcome.message };
|
|
121
|
+
}
|
|
122
|
+
const cached = await this.resolveFromState(fallback, kid, family, alg);
|
|
123
|
+
return cached.kind === 'unknown' ? { kind: 'unavailable', message: outcome.message } : cached;
|
|
124
|
+
}
|
|
125
|
+
const resolved = await this.resolveFromState(this.state, kid, family, alg);
|
|
126
|
+
if (resolved.kind === 'unknown')
|
|
127
|
+
this.lastMissAt = clock.monotonicMs();
|
|
128
|
+
return resolved;
|
|
129
|
+
}
|
|
130
|
+
/** Make the next resolution refetch, ignoring the TTL and both cooldowns. Keeps what is known. */
|
|
131
|
+
forceRefresh() {
|
|
132
|
+
this.forceNext = true;
|
|
133
|
+
}
|
|
134
|
+
/** Forget every applied and pending tombstone — the documented manual reset. */
|
|
135
|
+
clearTombstones() {
|
|
136
|
+
this.state = (0, keyset_js_1.withoutTombstones)(this.state);
|
|
137
|
+
}
|
|
138
|
+
/** What the cache holds, for tests. */
|
|
139
|
+
diagnostics() {
|
|
140
|
+
const age = this.cacheAgeMs(clock.monotonicMs());
|
|
141
|
+
return {
|
|
142
|
+
cacheAgeSeconds: Number.isFinite(age) ? Math.floor(age / 1000) : null,
|
|
143
|
+
registryLive: (0, keyset_js_1.isRegistryLive)(this.state),
|
|
144
|
+
vanishedKids: [...this.state.keys.values()]
|
|
145
|
+
.filter((record) => record.vanished && record.kid)
|
|
146
|
+
.map((record) => record.kid),
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
cacheAgeMs(now) {
|
|
150
|
+
if (this.state.fetchedAt === null || this.state.fetchedAtUntrusted)
|
|
151
|
+
return Number.POSITIVE_INFINITY;
|
|
152
|
+
return now - this.state.fetchedAt.monotonicMs;
|
|
153
|
+
}
|
|
154
|
+
isFresh(now) {
|
|
155
|
+
return this.cacheAgeMs(now) < exports.KEY_SET_TTL_MS;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* One fetch at a time. The shared promise RESOLVES to the outcome rather than recording it on
|
|
159
|
+
* the instance, so a waiter cannot be handed the result of the NEXT fetch.
|
|
160
|
+
*/
|
|
161
|
+
runFetch() {
|
|
162
|
+
if (this.inFlight)
|
|
163
|
+
return this.inFlight;
|
|
164
|
+
this.forceNext = false;
|
|
165
|
+
const running = (async () => {
|
|
166
|
+
let failure;
|
|
167
|
+
try {
|
|
168
|
+
const body = await this.fetchDocument();
|
|
169
|
+
const merged = (0, keyset_js_1.mergeFetchedDocument)(this.state, body, {
|
|
170
|
+
monotonicMs: clock.monotonicMs(),
|
|
171
|
+
wallMs: clock.wallMs(),
|
|
172
|
+
});
|
|
173
|
+
for (const event of merged.events)
|
|
174
|
+
emit(event);
|
|
175
|
+
if (merged.accepted) {
|
|
176
|
+
this.state = merged.state;
|
|
177
|
+
this.lastFailedAt = null;
|
|
178
|
+
return { ok: true };
|
|
179
|
+
}
|
|
180
|
+
failure = merged.events[0]?.message ?? 'JWKS refused';
|
|
181
|
+
}
|
|
182
|
+
catch (error) {
|
|
183
|
+
failure = error instanceof Error && error.message ? error.message : 'JWKS fetch failed';
|
|
184
|
+
}
|
|
185
|
+
// Only a set with a cache to fall back on is held back; one never fetched is retried every call.
|
|
186
|
+
if (this.state.fetchedAt !== null)
|
|
187
|
+
this.lastFailedAt = clock.monotonicMs();
|
|
188
|
+
return { ok: false, message: failure };
|
|
189
|
+
})();
|
|
190
|
+
this.inFlight = running;
|
|
191
|
+
void running.finally(() => {
|
|
192
|
+
if (this.inFlight === running)
|
|
193
|
+
this.inFlight = null;
|
|
194
|
+
});
|
|
195
|
+
return running;
|
|
196
|
+
}
|
|
197
|
+
async fetchDocument() {
|
|
198
|
+
const response = await this.http.fetchPublic(this.jwksUri, {
|
|
199
|
+
timeoutMs: JWKS_TIMEOUT_MS,
|
|
200
|
+
headers: { accept: 'application/json' },
|
|
201
|
+
});
|
|
202
|
+
if (!response.ok)
|
|
203
|
+
throw new Error(`JWKS fetch failed with HTTP ${response.status}`);
|
|
204
|
+
const declaredLength = Number(response.headers.get('content-length'));
|
|
205
|
+
if (Number.isFinite(declaredLength) && declaredLength > MAX_JWKS_BYTES) {
|
|
206
|
+
throw new Error('JWKS response is too large');
|
|
207
|
+
}
|
|
208
|
+
const raw = await (0, http_js_1.readCapped)(response, MAX_JWKS_BYTES);
|
|
209
|
+
if (raw === null)
|
|
210
|
+
throw new Error('JWKS response is too large');
|
|
211
|
+
let body;
|
|
212
|
+
try {
|
|
213
|
+
body = JSON.parse(raw);
|
|
214
|
+
}
|
|
215
|
+
catch {
|
|
216
|
+
throw new Error('JWKS response is not JSON');
|
|
217
|
+
}
|
|
218
|
+
if (!(0, support_js_1.isRecord)(body) || !Array.isArray(body.keys))
|
|
219
|
+
throw new Error('JWKS response has no `keys` array');
|
|
220
|
+
return body;
|
|
221
|
+
}
|
|
222
|
+
async resolveFromState(state, kid, family, alg) {
|
|
223
|
+
const selection = (0, keyset_js_1.selectKeys)(state, kid, family);
|
|
224
|
+
if (selection.kind !== 'candidates')
|
|
225
|
+
return selection;
|
|
226
|
+
const candidates = [];
|
|
227
|
+
for (const record of selection.records) {
|
|
228
|
+
const key = await this.importRecord(record, alg);
|
|
229
|
+
if (key)
|
|
230
|
+
candidates.push({ key, record });
|
|
231
|
+
}
|
|
232
|
+
if (candidates.length === 0)
|
|
233
|
+
return { kind: 'unknown' };
|
|
234
|
+
return { kind: 'keys', candidates, registryLive: (0, keyset_js_1.isRegistryLive)(state) };
|
|
235
|
+
}
|
|
236
|
+
async importRecord(record, alg) {
|
|
237
|
+
// A key declared for another algorithm, or for anything but signatures (RFC 7517 §4.2, §4.3), is
|
|
238
|
+
// not a candidate. Checked before the import cache, which is keyed by material and shared across kids.
|
|
239
|
+
if (typeof record.jwk.alg === 'string' && record.jwk.alg !== alg)
|
|
240
|
+
return null;
|
|
241
|
+
const { use, key_ops: keyOps } = record.jwk;
|
|
242
|
+
if (use !== undefined && use !== 'sig')
|
|
243
|
+
return null;
|
|
244
|
+
if (keyOps !== undefined && !(Array.isArray(keyOps) && keyOps.includes('verify')))
|
|
245
|
+
return null;
|
|
246
|
+
const cacheKey = `${record.fingerprint}|${alg}`;
|
|
247
|
+
if (this.imported.has(cacheKey))
|
|
248
|
+
return this.imported.get(cacheKey) ?? null;
|
|
249
|
+
let key;
|
|
250
|
+
try {
|
|
251
|
+
const { kty, crv, x, y, n, e } = record.jwk;
|
|
252
|
+
const material = Object.fromEntries(Object.entries({ kty, crv, x, y, n, e }).filter(([, value]) => value !== undefined));
|
|
253
|
+
key = await (0, jose_1.importJWK)(material, alg);
|
|
254
|
+
}
|
|
255
|
+
catch {
|
|
256
|
+
key = null;
|
|
257
|
+
}
|
|
258
|
+
this.imported.set(cacheKey, key);
|
|
259
|
+
return key;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
exports.KeySetCache = KeySetCache;
|
|
263
|
+
function candidateCount(state, family) {
|
|
264
|
+
const selection = (0, keyset_js_1.selectKeys)(state, undefined, family);
|
|
265
|
+
return selection.kind === 'candidates' ? selection.records.length : 0;
|
|
266
|
+
}
|
|
267
|
+
function emit(event) {
|
|
268
|
+
if (!eventListener)
|
|
269
|
+
return;
|
|
270
|
+
try {
|
|
271
|
+
eventListener(event);
|
|
272
|
+
}
|
|
273
|
+
catch {
|
|
274
|
+
// A throwing listener must not turn into a verification failure.
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
//# sourceMappingURL=key-set-cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"key-set-cache.js","sourceRoot":"","sources":["../../../src/jwks/key-set-cache.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LOCKSTEP COPY of `packages/delegation-verifier/src/keyset.ts` — the key-rotation rules, free of
|
|
3
|
+
* I/O. Everything below the imports must stay byte-identical to that file:
|
|
4
|
+
* `src/__tests__/drift/jwks-rotation-fixtures.test.ts` in the repository root fails otherwise, and
|
|
5
|
+
* `sdks/shared/fixtures/jwks_rotation/` pins the outcome of each rule across all five verifiers.
|
|
6
|
+
* Change a rule there, then copy it here; do not edit this file on its own.
|
|
7
|
+
*
|
|
8
|
+
* The persistence half (`serializeKeySetState` and friends) is carried along unused: this SDK keeps
|
|
9
|
+
* its key set in memory only, but trimming the copy would turn "identical" into "identical except".
|
|
10
|
+
*/
|
|
11
|
+
import type { JwksEventType } from './support.js';
|
|
12
|
+
export declare const KEY_FAMILIES: readonly ["proof", "delegation", "status-list"];
|
|
13
|
+
export type KeyFamily = (typeof KEY_FAMILIES)[number];
|
|
14
|
+
/** Clock skew tolerated on each edge of a key's `[not_before, not_after]` window. */
|
|
15
|
+
export declare const KEY_WINDOW_SKEW_SECONDS = 300;
|
|
16
|
+
/** Minimum spacing between the two fetches that confirm a key, a narrowed window or a tombstone. */
|
|
17
|
+
export declare const CONFIRM_SPACING_MS: number;
|
|
18
|
+
/** An applied tombstone is kept until `revoked_at` plus this. */
|
|
19
|
+
export declare const TOMBSTONE_RETENTION_SECONDS: number;
|
|
20
|
+
/** Both clocks at one instant: monotonic for in-process intervals, wall for anything persisted. */
|
|
21
|
+
export interface KeySetClockReading {
|
|
22
|
+
monotonicMs: number;
|
|
23
|
+
wallMs: number;
|
|
24
|
+
}
|
|
25
|
+
export interface KeyRecord {
|
|
26
|
+
kid?: string;
|
|
27
|
+
/** The JWK as last accepted, with any narrowing of its window applied. */
|
|
28
|
+
jwk: Record<string, unknown>;
|
|
29
|
+
/** Identity of the key material: a protected kid keeps its first material, an unprotected one takes the latest. */
|
|
30
|
+
fingerprint: string;
|
|
31
|
+
/** `alg`, `use` and `key_ops` — the members that decide whether the key may verify at all. */
|
|
32
|
+
usage: string;
|
|
33
|
+
hasPurposeMember: boolean;
|
|
34
|
+
purpose?: KeyFamily;
|
|
35
|
+
legacy: boolean;
|
|
36
|
+
notBefore?: number;
|
|
37
|
+
notAfter?: number;
|
|
38
|
+
maxTokenLifetime?: number;
|
|
39
|
+
vanished: boolean;
|
|
40
|
+
/** When this exact description of the key was first served. */
|
|
41
|
+
firstSeen: KeySetClockReading;
|
|
42
|
+
/** Served unchanged by two fetches at least `CONFIRM_SPACING_MS` apart. */
|
|
43
|
+
confirmed: boolean;
|
|
44
|
+
/** A narrower window served for a confirmed key, applied once a later fetch confirms it. */
|
|
45
|
+
pendingWindow?: PendingWindow;
|
|
46
|
+
}
|
|
47
|
+
export interface PendingWindow {
|
|
48
|
+
notBefore: number;
|
|
49
|
+
notAfter: number;
|
|
50
|
+
firstSeen: KeySetClockReading;
|
|
51
|
+
}
|
|
52
|
+
export interface PendingTombstone {
|
|
53
|
+
kid: string;
|
|
54
|
+
revokedAt: number;
|
|
55
|
+
firstSeen: KeySetClockReading;
|
|
56
|
+
}
|
|
57
|
+
export interface AppliedTombstone {
|
|
58
|
+
kid: string;
|
|
59
|
+
revokedAt: number;
|
|
60
|
+
}
|
|
61
|
+
export interface KeySetState {
|
|
62
|
+
keys: Map<string, KeyRecord>;
|
|
63
|
+
/** Keys served without a `kid` — tolerant mode only ever needs them. */
|
|
64
|
+
anonymous: KeyRecord[];
|
|
65
|
+
pending: Map<string, PendingTombstone>;
|
|
66
|
+
applied: Map<string, AppliedTombstone>;
|
|
67
|
+
fetchedAt: KeySetClockReading | null;
|
|
68
|
+
/** A persisted `fetched_at` lay in the future: its age is unknown, so it counts as stale. */
|
|
69
|
+
fetchedAtUntrusted: boolean;
|
|
70
|
+
}
|
|
71
|
+
export type KeySetEventType = Exclude<JwksEventType, 'store_error'>;
|
|
72
|
+
export interface KeySetEvent {
|
|
73
|
+
type: KeySetEventType;
|
|
74
|
+
kid?: string;
|
|
75
|
+
message: string;
|
|
76
|
+
}
|
|
77
|
+
export interface MergeResult {
|
|
78
|
+
state: KeySetState;
|
|
79
|
+
events: KeySetEvent[];
|
|
80
|
+
/** False when the document was refused whole; `state` is then the unchanged input. */
|
|
81
|
+
accepted: boolean;
|
|
82
|
+
}
|
|
83
|
+
export type KeySelection = {
|
|
84
|
+
kind: 'candidates';
|
|
85
|
+
records: KeyRecord[];
|
|
86
|
+
} | {
|
|
87
|
+
kind: 'revoked';
|
|
88
|
+
kid: string;
|
|
89
|
+
} | {
|
|
90
|
+
kind: 'wrong_purpose';
|
|
91
|
+
kid: string;
|
|
92
|
+
} | {
|
|
93
|
+
kind: 'unknown';
|
|
94
|
+
};
|
|
95
|
+
export type KeyPolicyViolation = 'outside_key_window' | 'token_lifetime_exceeded';
|
|
96
|
+
export declare function emptyKeySetState(): KeySetState;
|
|
97
|
+
export declare function parseKeyRecord(jwk: Record<string, unknown>, firstSeen: KeySetClockReading): KeyRecord;
|
|
98
|
+
export declare function isRegistryLive(state: KeySetState): boolean;
|
|
99
|
+
/**
|
|
100
|
+
* Merge one fetched JWKS body (already known to be an object with a `keys` array) into `state`.
|
|
101
|
+
* Pure: returns a new state and the events the caller should report.
|
|
102
|
+
*/
|
|
103
|
+
export declare function mergeFetchedDocument(state: KeySetState, body: Record<string, unknown>, now: KeySetClockReading): MergeResult;
|
|
104
|
+
/**
|
|
105
|
+
* Which keys may verify a token of `family` carrying `kid`. Checks the applied tombstone and the
|
|
106
|
+
* key's purpose; the signature and the window are the caller's next steps, in that order.
|
|
107
|
+
*/
|
|
108
|
+
export declare function selectKeys(state: KeySetState, kid: string | undefined, family: KeyFamily): KeySelection;
|
|
109
|
+
/** The window and lifetime rules for a key whose signature has verified. */
|
|
110
|
+
export declare function checkKeyPolicy(record: KeyRecord, token: {
|
|
111
|
+
iat: unknown;
|
|
112
|
+
exp: unknown;
|
|
113
|
+
}, registryLive: boolean): KeyPolicyViolation | null;
|
|
114
|
+
/**
|
|
115
|
+
* Fold the tombstones of a persisted state into a set this process has already fetched. The
|
|
116
|
+
* fetched keys are newer than disk, but a tombstone is not something a later fetch can restore:
|
|
117
|
+
* dropping the persisted one would silently un-revoke a key until two more fetches agreed again.
|
|
118
|
+
*/
|
|
119
|
+
export declare function withPersistedTombstones(state: KeySetState, persisted: KeySetState): KeySetState;
|
|
120
|
+
/**
|
|
121
|
+
* The part of a cached set that may answer after a refetch FAILED: confirmed keys only. An
|
|
122
|
+
* unconfirmed key is trusted until the next document, and a failing fetch must not stretch that to
|
|
123
|
+
* `maxJwksAgeMs` — otherwise one poisoned response followed by an outage would keep its key, and
|
|
124
|
+
* the registry mode it switched on, for a day.
|
|
125
|
+
*/
|
|
126
|
+
export declare function withConfirmedKeysOnly(state: KeySetState): KeySetState;
|
|
127
|
+
/** Drop every applied and pending tombstone — the documented manual reset. */
|
|
128
|
+
export declare function withoutTombstones(state: KeySetState): KeySetState;
|
|
129
|
+
/**
|
|
130
|
+
* Persisted form. Every timestamp is wall-clock milliseconds: a monotonic reading means nothing
|
|
131
|
+
* to another process.
|
|
132
|
+
*/
|
|
133
|
+
export declare function serializeKeySetState(state: KeySetState): string;
|
|
134
|
+
/**
|
|
135
|
+
* Rebuild a persisted state, or null when the blob is not one. A `fetched_at` in the future marks
|
|
136
|
+
* the set stale; a pending tombstone first seen in the future is dropped.
|
|
137
|
+
*/
|
|
138
|
+
export declare function deserializeKeySetState(raw: string, now: KeySetClockReading): KeySetState | null;
|
|
139
|
+
//# sourceMappingURL=keyset.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keyset.d.ts","sourceRoot":"","sources":["../../../src/jwks/keyset.ts"],"names":[],"mappings":"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"}
|