@idpartners/client-attestation-sdk 0.0.0-stage → 1.0.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.
Files changed (47) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +142 -2
  3. package/dist/attester.d.ts +93 -0
  4. package/dist/attester.js +139 -0
  5. package/dist/builders.d.ts +92 -0
  6. package/dist/builders.js +284 -0
  7. package/dist/credential.d.ts +22 -0
  8. package/dist/credential.js +44 -0
  9. package/dist/errors.d.ts +29 -0
  10. package/dist/errors.js +28 -0
  11. package/dist/evidence.d.ts +55 -0
  12. package/dist/evidence.js +168 -0
  13. package/dist/http.d.ts +1 -0
  14. package/dist/http.js +183 -0
  15. package/dist/index.d.ts +26 -0
  16. package/dist/index.js +20 -0
  17. package/dist/issuance.d.ts +61 -0
  18. package/dist/issuance.js +117 -0
  19. package/dist/logging.d.ts +12 -0
  20. package/dist/logging.js +77 -0
  21. package/dist/runtime.d.ts +1 -0
  22. package/dist/runtime.js +64 -0
  23. package/dist/signer.d.ts +32 -0
  24. package/dist/signer.js +121 -0
  25. package/dist/signing-key-pair.d.ts +44 -0
  26. package/dist/signing-key-pair.js +130 -0
  27. package/dist/svid-source.d.ts +41 -0
  28. package/dist/svid-source.js +77 -0
  29. package/dist/token-source.d.ts +146 -0
  30. package/dist/token-source.js +510 -0
  31. package/dist/validator/config.d.ts +61 -0
  32. package/dist/validator/config.js +61 -0
  33. package/dist/validator/errors.d.ts +20 -0
  34. package/dist/validator/errors.js +18 -0
  35. package/dist/validator/index.d.ts +19 -0
  36. package/dist/validator/index.js +12 -0
  37. package/dist/validator/jwks.d.ts +2 -0
  38. package/dist/validator/jwks.js +150 -0
  39. package/dist/validator/resource.d.ts +37 -0
  40. package/dist/validator/resource.js +98 -0
  41. package/dist/validator/result.d.ts +17 -0
  42. package/dist/validator/result.js +28 -0
  43. package/dist/validator/strict.d.ts +1 -0
  44. package/dist/validator/strict.js +202 -0
  45. package/dist/validator/validator.d.ts +41 -0
  46. package/dist/validator/validator.js +240 -0
  47. package/package.json +56 -4
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Builders for the attestation, PoP and DPoP JWTs (draft-ietf-oauth-attestation-based-client-auth).
3
+ */
4
+ import { type JWK } from "jose";
5
+ import { SigningKeyPair } from "./signing-key-pair.js";
6
+ import { type JwsSigner } from "./signer.js";
7
+ export declare const ATTESTATION_TYP = "oauth-client-attestation+jwt";
8
+ export declare const POP_TYP = "oauth-client-attestation-pop+jwt";
9
+ export declare const DPOP_TYP = "dpop+jwt";
10
+ /**
11
+ * Builds a Client Attestation JWT — the credential a Client Attester issues to name a client
12
+ * (`sub`) and bind its instance key via `cnf.jwk`. Attester side; sign with the attester's key.
13
+ */
14
+ export declare class ClientAttestationBuilder {
15
+ #private;
16
+ constructor(attesterKey: SigningKeyPair | JwsSigner, issuer: string);
17
+ /** The client being attested — becomes the attestation `sub` (= `client_id`). */
18
+ clientId(clientId: string): this;
19
+ /** Binds the client instance key as `cnf.jwk`. Must be a public JWK. */
20
+ confirmationJwk(publicInstanceJwk: JWK): this;
21
+ /** Convenience: binds the public half of the given instance key as `cnf.jwk`. */
22
+ confirmationKey(instanceKey: SigningKeyPair): this;
23
+ issuedAt(epochSeconds: number): this;
24
+ /** Sets an absolute `exp`. */
25
+ expiresAt(epochSeconds: number): this;
26
+ /** Sets `exp` to `iat + seconds`. */
27
+ expiresIn(seconds: number): this;
28
+ /** The optional RFC 9396 `authorization_details` entitlement the attester asserts. */
29
+ authorizationDetails(details: unknown[]): this;
30
+ /** Optional attester-asserted `workload` attributes. */
31
+ workload(workload: Record<string, unknown>): this;
32
+ /**
33
+ * Names the client's OPERATOR — the human or organisation accountable for running this client,
34
+ * as distinct from the client itself (`sub`) and from the principal it acts for.
35
+ *
36
+ * Deliberately a top-level claim rather than a member of `workload`: `workload` describes the
37
+ * running software, whereas the operator is an accountability statement about who answers for
38
+ * it. It is per-client and static, so it does not affect how far an attestation can be cached.
39
+ *
40
+ * The shape mirrors RFC 8693 `may_act` (`{ sub: ..., iss: ... }`) on purpose. An AS that wants
41
+ * the operator to become the party authorised to act for the subject can lift this straight
42
+ * into `may_act`; `iss` names the OP the operator's `sub` is meaningful in, so the identifier is
43
+ * not ambiguous across directories.
44
+ *
45
+ * This is an ASSERTION by the attester, not proof the operator authorised anything. A relying
46
+ * AS should treat it as it treats the rest of the attestation: trusted exactly as far as the
47
+ * attester is.
48
+ */
49
+ operator(sub: string, issuer?: string | null): this;
50
+ build(): Promise<string>;
51
+ }
52
+ /**
53
+ * Builds a Client Attestation PoP JWT proving possession of the instance key. Client side of
54
+ * `attest_jwt_client_auth`; mint a fresh one per token request.
55
+ */
56
+ export declare class PopBuilder {
57
+ #private;
58
+ constructor(instanceKey: SigningKeyPair);
59
+ /** Sets the optional `iss`; when present the verifier requires it to equal the attestation `sub`. */
60
+ clientId(clientId: string | null): this;
61
+ /** The AS identifier this PoP is bound to (`aud`) — required. */
62
+ audience(audience: string): this;
63
+ challenge(challenge: string | null): this;
64
+ jwtId(jwtId: string): this;
65
+ issuedAt(epochSeconds: number): this;
66
+ build(): Promise<string>;
67
+ }
68
+ /**
69
+ * Builds a DPoP proof JWT (RFC 9449) for attestation combined mode
70
+ * (PoP method `dpop_combined`): the embedded `jwk` header MUST be the attestation's `cnf` key.
71
+ */
72
+ export declare class DpopProofBuilder {
73
+ #private;
74
+ constructor(instanceKey: SigningKeyPair);
75
+ /** The request's HTTP method (`htm`, upper-cased when built); defaults to `POST`. */
76
+ method(htm: string | null | undefined): this;
77
+ /**
78
+ * The target URL (`htu`) — required. Normalised when built: query and fragment are dropped, and
79
+ * scheme and host lower-cased (RFC 9449 section 4.2).
80
+ */
81
+ uri(htu: string): this;
82
+ /** The server challenge, carried in DPoP `nonce`. */
83
+ nonce(nonce: string | null): this;
84
+ /**
85
+ * The access token this proof accompanies at a resource: sets `ath` = base64url(SHA-256(token)).
86
+ * Leave unset for a proof sent to the token endpoint.
87
+ */
88
+ accessToken(accessToken: string | null): this;
89
+ jwtId(jwtId: string): this;
90
+ issuedAt(epochSeconds: number): this;
91
+ build(): Promise<string>;
92
+ }
@@ -0,0 +1,284 @@
1
+ /**
2
+ * Builders for the attestation, PoP and DPoP JWTs (draft-ietf-oauth-attestation-based-client-auth).
3
+ */
4
+ import { base64url } from "jose";
5
+ import { SigningKeyPair, requireText, signCompact } from "./signing-key-pair.js";
6
+ import { utf8Encode } from "./runtime.js";
7
+ import { signExternal } from "./signer.js";
8
+ export const ATTESTATION_TYP = "oauth-client-attestation+jwt";
9
+ export const POP_TYP = "oauth-client-attestation-pop+jwt";
10
+ export const DPOP_TYP = "dpop+jwt";
11
+ function nowSeconds() {
12
+ return Math.floor(Date.now() / 1000);
13
+ }
14
+ /**
15
+ * Builds a Client Attestation JWT — the credential a Client Attester issues to name a client
16
+ * (`sub`) and bind its instance key via `cnf.jwk`. Attester side; sign with the attester's key.
17
+ */
18
+ export class ClientAttestationBuilder {
19
+ #attesterKey;
20
+ #issuer;
21
+ #clientId = null;
22
+ #cnfJwk = null;
23
+ #issuedAt = null;
24
+ #expiresAt = null;
25
+ #ttl = null;
26
+ #authorizationDetails = null;
27
+ #workload = null;
28
+ #operator = null;
29
+ constructor(attesterKey, issuer) {
30
+ this.#attesterKey = attesterKey;
31
+ this.#issuer = requireText(issuer, "issuer");
32
+ }
33
+ /** The client being attested — becomes the attestation `sub` (= `client_id`). */
34
+ clientId(clientId) {
35
+ this.#clientId = clientId;
36
+ return this;
37
+ }
38
+ /** Binds the client instance key as `cnf.jwk`. Must be a public JWK. */
39
+ confirmationJwk(publicInstanceJwk) {
40
+ this.#cnfJwk = publicInstanceJwk;
41
+ return this;
42
+ }
43
+ /** Convenience: binds the public half of the given instance key as `cnf.jwk`. */
44
+ confirmationKey(instanceKey) {
45
+ return this.confirmationJwk(instanceKey.publicJwk());
46
+ }
47
+ issuedAt(epochSeconds) {
48
+ this.#issuedAt = epochSeconds;
49
+ return this;
50
+ }
51
+ /** Sets an absolute `exp`. */
52
+ expiresAt(epochSeconds) {
53
+ this.#expiresAt = epochSeconds;
54
+ return this;
55
+ }
56
+ /** Sets `exp` to `iat + seconds`. */
57
+ expiresIn(seconds) {
58
+ this.#ttl = seconds;
59
+ return this;
60
+ }
61
+ /** The optional RFC 9396 `authorization_details` entitlement the attester asserts. */
62
+ authorizationDetails(details) {
63
+ this.#authorizationDetails = details;
64
+ return this;
65
+ }
66
+ /** Optional attester-asserted `workload` attributes. */
67
+ workload(workload) {
68
+ this.#workload = workload;
69
+ return this;
70
+ }
71
+ /**
72
+ * Names the client's OPERATOR — the human or organisation accountable for running this client,
73
+ * as distinct from the client itself (`sub`) and from the principal it acts for.
74
+ *
75
+ * Deliberately a top-level claim rather than a member of `workload`: `workload` describes the
76
+ * running software, whereas the operator is an accountability statement about who answers for
77
+ * it. It is per-client and static, so it does not affect how far an attestation can be cached.
78
+ *
79
+ * The shape mirrors RFC 8693 `may_act` (`{ sub: ..., iss: ... }`) on purpose. An AS that wants
80
+ * the operator to become the party authorised to act for the subject can lift this straight
81
+ * into `may_act`; `iss` names the OP the operator's `sub` is meaningful in, so the identifier is
82
+ * not ambiguous across directories.
83
+ *
84
+ * This is an ASSERTION by the attester, not proof the operator authorised anything. A relying
85
+ * AS should treat it as it treats the rest of the attestation: trusted exactly as far as the
86
+ * attester is.
87
+ */
88
+ operator(sub, issuer) {
89
+ const operator = { sub: requireText(sub, "operator sub") };
90
+ if (issuer) {
91
+ operator.iss = issuer;
92
+ }
93
+ this.#operator = operator;
94
+ return this;
95
+ }
96
+ async build() {
97
+ const sub = requireText(this.#clientId, "client_id");
98
+ if (this.#cnfJwk === null) {
99
+ throw new Error("confirmation key (cnf.jwk) is required");
100
+ }
101
+ const iat = this.#issuedAt !== null ? this.#issuedAt : nowSeconds();
102
+ const exp = this.#resolveExpiry(iat);
103
+ const claims = {
104
+ iss: this.#issuer,
105
+ sub,
106
+ iat,
107
+ exp,
108
+ cnf: { jwk: this.#cnfJwk },
109
+ };
110
+ if (this.#authorizationDetails && this.#authorizationDetails.length > 0) {
111
+ claims.authorization_details = this.#authorizationDetails;
112
+ }
113
+ if (this.#workload && Object.keys(this.#workload).length > 0) {
114
+ claims.workload = this.#workload;
115
+ }
116
+ if (this.#operator) {
117
+ claims.operator = this.#operator;
118
+ }
119
+ if (this.#attesterKey instanceof SigningKeyPair) {
120
+ return signCompact(claims, this.#attesterKey, ATTESTATION_TYP, false);
121
+ }
122
+ return signExternal(claims, this.#attesterKey, ATTESTATION_TYP);
123
+ }
124
+ #resolveExpiry(iat) {
125
+ if (this.#expiresAt !== null) {
126
+ return this.#expiresAt;
127
+ }
128
+ if (this.#ttl !== null) {
129
+ return iat + this.#ttl;
130
+ }
131
+ throw new Error("expiry is required: call expiresAt(...) or expiresIn(...)");
132
+ }
133
+ }
134
+ /**
135
+ * Builds a Client Attestation PoP JWT proving possession of the instance key. Client side of
136
+ * `attest_jwt_client_auth`; mint a fresh one per token request.
137
+ */
138
+ export class PopBuilder {
139
+ #instanceKey;
140
+ #clientId = null;
141
+ #audience = null;
142
+ #challenge = null;
143
+ #jwtId = null;
144
+ #issuedAt = null;
145
+ constructor(instanceKey) {
146
+ this.#instanceKey = instanceKey;
147
+ }
148
+ /** Sets the optional `iss`; when present the verifier requires it to equal the attestation `sub`. */
149
+ clientId(clientId) {
150
+ this.#clientId = clientId;
151
+ return this;
152
+ }
153
+ /** The AS identifier this PoP is bound to (`aud`) — required. */
154
+ audience(audience) {
155
+ this.#audience = audience;
156
+ return this;
157
+ }
158
+ challenge(challenge) {
159
+ this.#challenge = challenge;
160
+ return this;
161
+ }
162
+ jwtId(jwtId) {
163
+ this.#jwtId = jwtId;
164
+ return this;
165
+ }
166
+ issuedAt(epochSeconds) {
167
+ this.#issuedAt = epochSeconds;
168
+ return this;
169
+ }
170
+ async build() {
171
+ if (!this.#audience) {
172
+ throw new Error("audience (aud) is required");
173
+ }
174
+ const claims = {
175
+ aud: this.#audience,
176
+ jti: this.#jwtId ?? crypto.randomUUID(),
177
+ iat: this.#issuedAt !== null ? this.#issuedAt : nowSeconds(),
178
+ };
179
+ if (this.#clientId) {
180
+ claims.iss = this.#clientId;
181
+ }
182
+ if (this.#challenge) {
183
+ claims.challenge = this.#challenge;
184
+ }
185
+ return signCompact(claims, this.#instanceKey, POP_TYP, false);
186
+ }
187
+ }
188
+ /**
189
+ * Builds a DPoP proof JWT (RFC 9449) for attestation combined mode
190
+ * (PoP method `dpop_combined`): the embedded `jwk` header MUST be the attestation's `cnf` key.
191
+ */
192
+ export class DpopProofBuilder {
193
+ #instanceKey;
194
+ #htm = "POST";
195
+ #htu = null;
196
+ #nonce = null;
197
+ #accessToken = null;
198
+ #jwtId = null;
199
+ #issuedAt = null;
200
+ constructor(instanceKey) {
201
+ this.#instanceKey = instanceKey;
202
+ }
203
+ /** The request's HTTP method (`htm`, upper-cased when built); defaults to `POST`. */
204
+ method(htm) {
205
+ if (htm) {
206
+ this.#htm = htm;
207
+ }
208
+ return this;
209
+ }
210
+ /**
211
+ * The target URL (`htu`) — required. Normalised when built: query and fragment are dropped, and
212
+ * scheme and host lower-cased (RFC 9449 section 4.2).
213
+ */
214
+ uri(htu) {
215
+ this.#htu = htu;
216
+ return this;
217
+ }
218
+ /** The server challenge, carried in DPoP `nonce`. */
219
+ nonce(nonce) {
220
+ this.#nonce = nonce;
221
+ return this;
222
+ }
223
+ /**
224
+ * The access token this proof accompanies at a resource: sets `ath` = base64url(SHA-256(token)).
225
+ * Leave unset for a proof sent to the token endpoint.
226
+ */
227
+ accessToken(accessToken) {
228
+ this.#accessToken = accessToken || null;
229
+ return this;
230
+ }
231
+ jwtId(jwtId) {
232
+ this.#jwtId = jwtId;
233
+ return this;
234
+ }
235
+ issuedAt(epochSeconds) {
236
+ this.#issuedAt = epochSeconds;
237
+ return this;
238
+ }
239
+ async build() {
240
+ if (!this.#htu) {
241
+ throw new Error("uri (htu) is required");
242
+ }
243
+ const claims = {
244
+ htm: this.#htm.toUpperCase(),
245
+ htu: normalizeHtu(this.#htu),
246
+ jti: this.#jwtId ?? crypto.randomUUID(),
247
+ iat: this.#issuedAt !== null ? this.#issuedAt : nowSeconds(),
248
+ };
249
+ if (this.#nonce) {
250
+ claims.nonce = this.#nonce;
251
+ }
252
+ if (this.#accessToken) {
253
+ claims.ath = await accessTokenHash(this.#accessToken);
254
+ }
255
+ return signCompact(claims, this.#instanceKey, DPOP_TYP, true);
256
+ }
257
+ }
258
+ /**
259
+ * RFC 9449 `ath`: base64url(SHA-256(ASCII access token)).
260
+ *
261
+ * @internal
262
+ */
263
+ export async function accessTokenHash(accessToken) {
264
+ const digest = await crypto.subtle.digest("SHA-256", utf8Encode(accessToken));
265
+ return base64url.encode(new Uint8Array(digest));
266
+ }
267
+ /**
268
+ * The DPoP `htu` for a URL: scheme, host, port and path, with no query and no fragment. Scheme and host
269
+ * are lower-cased and a default port dropped; a URL with no path keeps none (no slash is invented).
270
+ *
271
+ * @internal
272
+ */
273
+ export function normalizeHtu(url) {
274
+ const bare = url.split(/[?#]/, 1)[0];
275
+ let parsed;
276
+ try {
277
+ parsed = new URL(bare);
278
+ }
279
+ catch {
280
+ return bare;
281
+ }
282
+ const hasPath = /^[a-z][a-z0-9+.-]*:\/\/[^/]*\//i.test(bare);
283
+ return `${parsed.protocol}//${parsed.host}${hasPath ? parsed.pathname : ""}`;
284
+ }
@@ -0,0 +1,22 @@
1
+ import { SigningKeyPair } from "./signing-key-pair.js";
2
+ export declare const ATTESTATION_HEADER = "OAuth-Client-Attestation";
3
+ export declare const POP_HEADER = "OAuth-Client-Attestation-PoP";
4
+ export declare const DPOP_HEADER = "DPoP";
5
+ /**
6
+ * The Attester-issued attestation JWT plus the instance key it is bound to. Produces the
7
+ * token-request headers — PoP-JWT mode or DPoP combined mode — minting a fresh proof each call.
8
+ */
9
+ export declare class ClientAttestationCredential {
10
+ #private;
11
+ constructor(attestationJwt: string, instanceKey: SigningKeyPair);
12
+ /**
13
+ * Headers for dedicated PoP-JWT mode (PoP method `attestation_pop_jwt`):
14
+ * `OAuth-Client-Attestation` + `OAuth-Client-Attestation-PoP`.
15
+ */
16
+ popHeaders(clientId: string | null, audience: string, challenge?: string | null): Promise<Record<string, string>>;
17
+ /**
18
+ * Headers for DPoP combined mode (PoP method `dpop_combined`):
19
+ * `OAuth-Client-Attestation` + `DPoP` (and no PoP header).
20
+ */
21
+ dpopHeaders(method: string, uri: string, challenge?: string | null): Promise<Record<string, string>>;
22
+ }
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Assembles the request headers a client sends at the token endpoint.
3
+ */
4
+ import { DpopProofBuilder, PopBuilder } from "./builders.js";
5
+ import { requireText } from "./signing-key-pair.js";
6
+ export const ATTESTATION_HEADER = "OAuth-Client-Attestation";
7
+ export const POP_HEADER = "OAuth-Client-Attestation-PoP";
8
+ export const DPOP_HEADER = "DPoP";
9
+ /**
10
+ * The Attester-issued attestation JWT plus the instance key it is bound to. Produces the
11
+ * token-request headers — PoP-JWT mode or DPoP combined mode — minting a fresh proof each call.
12
+ */
13
+ export class ClientAttestationCredential {
14
+ #attestationJwt;
15
+ #instanceKey;
16
+ constructor(attestationJwt, instanceKey) {
17
+ this.#attestationJwt = requireText(attestationJwt, "attestation_jwt");
18
+ this.#instanceKey = instanceKey;
19
+ }
20
+ /**
21
+ * Headers for dedicated PoP-JWT mode (PoP method `attestation_pop_jwt`):
22
+ * `OAuth-Client-Attestation` + `OAuth-Client-Attestation-PoP`.
23
+ */
24
+ async popHeaders(clientId, audience, challenge) {
25
+ const pop = await new PopBuilder(this.#instanceKey)
26
+ .clientId(clientId)
27
+ .audience(audience)
28
+ .challenge(challenge ?? null)
29
+ .build();
30
+ return { [ATTESTATION_HEADER]: this.#attestationJwt, [POP_HEADER]: pop };
31
+ }
32
+ /**
33
+ * Headers for DPoP combined mode (PoP method `dpop_combined`):
34
+ * `OAuth-Client-Attestation` + `DPoP` (and no PoP header).
35
+ */
36
+ async dpopHeaders(method, uri, challenge) {
37
+ const dpop = await new DpopProofBuilder(this.#instanceKey)
38
+ .method(method)
39
+ .uri(uri)
40
+ .nonce(challenge ?? null)
41
+ .build();
42
+ return { [ATTESTATION_HEADER]: this.#attestationJwt, [DPOP_HEADER]: dpop };
43
+ }
44
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * The SDK's error types. {@link ClientAttestationError} is the base for every failure of the
3
+ * one-call flow; {@link TransportError} is the subclass for anything that went wrong on the wire.
4
+ */
5
+ /** A step in the flow failed. `status` and `body` are set for HTTP failures. */
6
+ export declare class ClientAttestationError extends Error {
7
+ readonly status?: number;
8
+ readonly body?: string;
9
+ constructor(message: string, opts?: {
10
+ status?: number;
11
+ body?: string;
12
+ cause?: unknown;
13
+ });
14
+ }
15
+ /** Why an outbound HTTP call failed. */
16
+ export type TransportFailure = "network" | "timeout" | "redirect" | "too_large" | "status" | "json";
17
+ /**
18
+ * An outbound HTTP call failed: unreachable, timed out, redirected, oversized, non-2xx, or malformed
19
+ * JSON (see {@link kind}). `status` is set when a response was received; `body` holds at most the first
20
+ * 512 characters of it.
21
+ */
22
+ export declare class TransportError extends ClientAttestationError {
23
+ readonly kind: TransportFailure;
24
+ constructor(message: string, kind: TransportFailure, opts?: {
25
+ status?: number;
26
+ body?: string;
27
+ cause?: unknown;
28
+ });
29
+ }
package/dist/errors.js ADDED
@@ -0,0 +1,28 @@
1
+ /**
2
+ * The SDK's error types. {@link ClientAttestationError} is the base for every failure of the
3
+ * one-call flow; {@link TransportError} is the subclass for anything that went wrong on the wire.
4
+ */
5
+ /** A step in the flow failed. `status` and `body` are set for HTTP failures. */
6
+ export class ClientAttestationError extends Error {
7
+ status;
8
+ body;
9
+ constructor(message, opts) {
10
+ super(message, opts?.cause === undefined ? undefined : { cause: opts.cause });
11
+ this.name = "ClientAttestationError";
12
+ this.status = opts?.status;
13
+ this.body = opts?.body;
14
+ }
15
+ }
16
+ /**
17
+ * An outbound HTTP call failed: unreachable, timed out, redirected, oversized, non-2xx, or malformed
18
+ * JSON (see {@link kind}). `status` is set when a response was received; `body` holds at most the first
19
+ * 512 characters of it.
20
+ */
21
+ export class TransportError extends ClientAttestationError {
22
+ kind;
23
+ constructor(message, kind, opts) {
24
+ super(message, opts);
25
+ this.name = "TransportError";
26
+ this.kind = kind;
27
+ }
28
+ }
@@ -0,0 +1,55 @@
1
+ import { type SvidSource } from "./svid-source.js";
2
+ /** Environment variable holding the evidence token itself (static). */
3
+ export declare const ENV_SVID = "CLIENT_ATTESTATION_SVID";
4
+ /** Environment variable holding a path to a projected token file. */
5
+ export declare const ENV_TOKEN_FILE = "CLIENT_ATTESTATION_TOKEN_FILE";
6
+ /** SPIRE Workload API socket (standard SPIFFE var). */
7
+ export declare const ENV_SPIFFE_SOCKET = "SPIFFE_ENDPOINT_SOCKET";
8
+ /** Default projected-token path used by the Kubernetes/GKE serviceAccountToken volume convention. */
9
+ export declare const DEFAULT_TOKEN_FILE = "/var/run/secrets/tokens/attester-token";
10
+ /** Default SPIRE agent socket path. */
11
+ export declare const DEFAULT_SPIFFE_SOCKET = "/tmp/spire-agent/public/api.sock";
12
+ /** No platform identity could be detected and none was supplied. */
13
+ export declare class EvidenceNotFound extends Error {
14
+ constructor(message: string);
15
+ }
16
+ /**
17
+ * Reads the evidence token from a file on every fetch.
18
+ *
19
+ * Kubernetes and GKE mount a short-lived, auto-rotated service-account token into the pod as a file
20
+ * (a projected `serviceAccountToken` volume). Reading it fresh each time picks up the rotation. The
21
+ * file's audience must already be the attester's evidence audience — set it when you declare the volume.
22
+ *
23
+ * Needs a filesystem: `node:fs/promises` is loaded on the first `fetch`, which throws a
24
+ * `ClientAttestationError` on a runtime without it.
25
+ */
26
+ export declare class FileTokenSvidSource implements SvidSource {
27
+ private readonly path;
28
+ constructor(path: string);
29
+ fetch(_audience: string): Promise<string>;
30
+ }
31
+ /**
32
+ * Fetches a Google-signed ID token for the runtime service account from the GCP metadata server.
33
+ *
34
+ * Works on any Google runtime that exposes the metadata server (GCE, GKE, Cloud Run, Vertex AI Agent
35
+ * Engine). The token's `aud` is the attester's evidence audience; `sub` is the service account. No
36
+ * google-auth dependency — a plain metadata call.
37
+ */
38
+ export declare class GoogleMetadataSvidSource implements SvidSource {
39
+ private readonly timeoutMs;
40
+ private readonly identityUrl;
41
+ /**
42
+ * @param timeoutMs per-request timeout.
43
+ * @param identityUrl the identity endpoint; internal, for tests - defaults to the real metadata server.
44
+ */
45
+ constructor(timeoutMs?: number, identityUrl?: string);
46
+ fetch(audience: string): Promise<string>;
47
+ }
48
+ /**
49
+ * Return the first evidence source that applies to this runtime, or throw with guidance.
50
+ *
51
+ * Order: explicit env token, projected token file, SPIRE socket, GCP metadata server. A source named
52
+ * by an environment variable ({@link ENV_TOKEN_FILE}, {@link ENV_SPIFFE_SOCKET}) whose path does not
53
+ * exist throws {@link EvidenceNotFound} naming the variable; it never falls through to the next source.
54
+ */
55
+ export declare function autoDetectEvidence(): Promise<SvidSource>;