@reclaimprotocol/client 0.1.27 → 0.1.28
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/README.md +634 -42
- package/lib/api-client.d.ts +31 -0
- package/lib/api-client.js +112 -0
- package/lib/api-consts.d.ts +4 -0
- package/lib/api-consts.js +4 -0
- package/lib/api-errors.d.ts +13 -0
- package/lib/api-errors.js +30 -0
- package/lib/api.d.ts +7 -0
- package/lib/api.js +3 -0
- package/lib/attestor-proof.d.ts +6 -0
- package/lib/attestor-proof.js +45 -18
- package/lib/client.d.ts +1 -6
- package/lib/client.js +12 -33
- package/lib/consts.d.ts +1 -1
- package/lib/consts.js +1 -1
- package/lib/eip191.d.ts +4 -2
- package/lib/eip191.js +17 -1
- package/lib/errors.d.ts +38 -4
- package/lib/errors.js +99 -16
- package/lib/index.d.ts +9 -7
- package/lib/index.js +8 -4
- package/lib/jwks.d.ts +25 -1
- package/lib/jwks.js +69 -4
- package/lib/legacy-proof.d.ts +57 -0
- package/lib/legacy-proof.js +211 -0
- package/lib/proof-hash.d.ts +35 -0
- package/lib/proof-hash.js +216 -0
- package/lib/proof-recipe.d.ts +18 -0
- package/lib/proof-recipe.js +37 -0
- package/lib/proof-trust-constants.d.ts +3 -0
- package/lib/proof-trust-constants.js +43 -0
- package/lib/proof-types.d.ts +62 -0
- package/lib/proof-types.js +1 -0
- package/lib/proof-verification-error.d.ts +13 -0
- package/lib/proof-verification-error.js +11 -0
- package/lib/reclaim.d.ts +27 -5
- package/lib/reclaim.js +140 -14
- package/lib/result.d.ts +58 -6
- package/lib/result.js +252 -61
- package/lib/routes.gen.d.ts +54 -6
- package/lib/routes.gen.js +15 -3
- package/lib/tee-binding.d.ts +82 -0
- package/lib/tee-binding.js +112 -0
- package/lib/types/openapi.gen.d.ts +1343 -206
- package/lib/verification-clients.d.ts +17 -0
- package/lib/verification-clients.js +14 -0
- package/lib/verify-attestor-tee.d.ts +76 -0
- package/lib/verify-attestor-tee.js +243 -0
- package/lib/verify-tee.d.ts +32 -0
- package/lib/verify-tee.js +347 -0
- package/package.json +5 -1
- package/src/api.ts +15 -0
- package/src/attestor-proof.ts +57 -20
- package/src/client.ts +15 -38
- package/src/consts.ts +1 -2
- package/src/eip191.ts +31 -3
- package/src/errors.ts +132 -18
- package/src/index.ts +50 -5
- package/src/jwks.ts +128 -5
- package/src/legacy-proof.ts +367 -0
- package/src/proof-hash.ts +316 -0
- package/src/proof-recipe.ts +75 -0
- package/src/proof-trust-constants.ts +45 -0
- package/src/proof-types.ts +57 -0
- package/src/proof-verification-error.ts +13 -0
- package/src/reclaim.ts +206 -19
- package/src/result.ts +403 -68
- package/src/routes.gen.ts +15 -3
- package/src/tee-binding.ts +192 -0
- package/src/types/openapi.gen.ts +1343 -206
- package/src/verification-clients.ts +22 -0
- package/src/verify-attestor-tee.ts +365 -0
- package/src/verify-tee.ts +507 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stable names for the Verification Clients seeded by Builder.
|
|
3
|
+
*
|
|
4
|
+
* A name identifies a registry record; session creation still selects a
|
|
5
|
+
* client with that deployment's exact registered URL.
|
|
6
|
+
*/
|
|
7
|
+
export declare const KNOWN_VERIFICATION_CLIENTS: {
|
|
8
|
+
readonly builder: "builder";
|
|
9
|
+
readonly portals: "portals";
|
|
10
|
+
readonly verifierApp: "verifier-app";
|
|
11
|
+
readonly inAppSdk: "inapp-sdk";
|
|
12
|
+
readonly reclaimBrowserExtension: "reclaim-browser-extension";
|
|
13
|
+
readonly zkFetch: "zkfetch";
|
|
14
|
+
};
|
|
15
|
+
export type KnownVerificationClientName = typeof KNOWN_VERIFICATION_CLIENTS[keyof typeof KNOWN_VERIFICATION_CLIENTS];
|
|
16
|
+
/** Deployment-specific registered URLs keyed by a known client name. */
|
|
17
|
+
export type KnownVerificationClientUrlMap = Partial<Record<KnownVerificationClientName, string>>;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stable names for the Verification Clients seeded by Builder.
|
|
3
|
+
*
|
|
4
|
+
* A name identifies a registry record; session creation still selects a
|
|
5
|
+
* client with that deployment's exact registered URL.
|
|
6
|
+
*/
|
|
7
|
+
export const KNOWN_VERIFICATION_CLIENTS = {
|
|
8
|
+
builder: 'builder',
|
|
9
|
+
portals: 'portals',
|
|
10
|
+
verifierApp: 'verifier-app',
|
|
11
|
+
inAppSdk: 'inapp-sdk',
|
|
12
|
+
reclaimBrowserExtension: 'reclaim-browser-extension',
|
|
13
|
+
zkFetch: 'zkfetch',
|
|
14
|
+
};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type { LegacyProof as Proof } from './proof-types.ts';
|
|
2
|
+
/**
|
|
3
|
+
* Result of verifying an attestor TEE attestation.
|
|
4
|
+
*/
|
|
5
|
+
export type AttestorTeeVerificationResult = {
|
|
6
|
+
isVerified: boolean;
|
|
7
|
+
error?: string;
|
|
8
|
+
/** sha256 image digest of the attestor container, on success. */
|
|
9
|
+
imageDigest?: string;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Validates a GCP Confidential Space attestation JWT produced by an
|
|
13
|
+
* attestor running in a Confidential Space VM, and asserts that the
|
|
14
|
+
* attestation binds to the given attestor address.
|
|
15
|
+
*
|
|
16
|
+
* The attestor (running inside the TEE) calls the Confidential Space
|
|
17
|
+
* launcher's attestation endpoint with two nonces:
|
|
18
|
+
* - `attestor_public_key:<eth-address>` - binds to the signing key.
|
|
19
|
+
* - `attestor_cert_hash:<sha256-hex>` - binds to the live TLS cert.
|
|
20
|
+
*
|
|
21
|
+
* This function only verifies the public-key nonce. The TLS cert hash
|
|
22
|
+
* binding is informational and not checked here. Callers that need to
|
|
23
|
+
* pin to a specific attestor image should compare the returned
|
|
24
|
+
* `imageDigest` against a known-good value.
|
|
25
|
+
*
|
|
26
|
+
* The JWT signature is verified by walking the x5c certificate chain
|
|
27
|
+
* to a pinned GCP Confidential Space Root CA. No outbound network
|
|
28
|
+
* calls are made.
|
|
29
|
+
*
|
|
30
|
+
* Node-only (uses node:crypto). Mirrors the environment restriction in
|
|
31
|
+
* the existing `verifyTeeAttestation` helper.
|
|
32
|
+
*
|
|
33
|
+
* @param report - the raw JWT string (header.payload.signature).
|
|
34
|
+
* @param expectedAttestorAddress - hex ETH address (0x-prefixed or
|
|
35
|
+
* unprefixed) that the attestation should be bound to.
|
|
36
|
+
*/
|
|
37
|
+
export declare function verifyAttestorTeeAttestation(report: string, expectedAttestorAddress: string): Promise<AttestorTeeVerificationResult>;
|
|
38
|
+
/**
|
|
39
|
+
* Configuration for verifying the attestor's TEE attestation on each
|
|
40
|
+
* witness of the proof.
|
|
41
|
+
*/
|
|
42
|
+
export type AttestorTeeAttestationConfig = {
|
|
43
|
+
/**
|
|
44
|
+
* Optional allowlist of expected attestor container image digests
|
|
45
|
+
* (e.g. `"sha256:4906340f..."`). When provided, the attestation's
|
|
46
|
+
* `submods.container.image_digest` must be in this list.
|
|
47
|
+
*
|
|
48
|
+
* Leave undefined to skip image pinning and rely solely on the JWT
|
|
49
|
+
* chain rooting to the GCP Confidential Space Root CA + nonce
|
|
50
|
+
* binding to the attestor address.
|
|
51
|
+
*/
|
|
52
|
+
expectedImageDigests?: string[];
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Verifies the attestor's TEE attestation for every witness of every
|
|
56
|
+
* provided proof. Throws `AttestorTeeVerificationError` on the first
|
|
57
|
+
* failure.
|
|
58
|
+
*
|
|
59
|
+
* Each witness must carry a `claimAttestation`. For each one, this:
|
|
60
|
+
* 1. Asserts `attestor_address` matches `witness.id`.
|
|
61
|
+
* 2. Asserts `claim_signature` is present in `proof.signatures`.
|
|
62
|
+
* 3. Recovers the signer of `claim_signature` from the claim data and
|
|
63
|
+
* asserts it equals `witness.id` (binds the signature to the
|
|
64
|
+
* attestor that the TEE attestation will cover).
|
|
65
|
+
* 4. Calls `verifyAttestorTeeAttestation` to validate the JWT against
|
|
66
|
+
* the pinned GCP Confidential Space Root CA and the attestor-key
|
|
67
|
+
* nonce.
|
|
68
|
+
* 5. If `expectedImageDigests` is provided, asserts the attestation's
|
|
69
|
+
* container image digest is in the allowlist.
|
|
70
|
+
*
|
|
71
|
+
* Node-only (uses node:crypto), like `verifyTeeAttestation`.
|
|
72
|
+
*
|
|
73
|
+
* @param proofs - The proofs to verify.
|
|
74
|
+
* @param config - Optional config; see {@link AttestorTeeAttestationConfig}.
|
|
75
|
+
*/
|
|
76
|
+
export declare function runAttestorTeeVerification(proofs: Proof[], config?: AttestorTeeAttestationConfig): Promise<void>;
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import { createSignDataForClaim } from '@reclaimprotocol/attestor-core';
|
|
2
|
+
/* eslint-disable @stylistic/max-len */
|
|
3
|
+
import crypto, { X509Certificate } from 'node:crypto';
|
|
4
|
+
import { recoverEip191Address } from "./eip191.js";
|
|
5
|
+
import { ATTESTOR_NONCE_PATTERN, GCP_CONFIDENTIAL_SPACE_ISSUER, GCP_CONFIDENTIAL_SPACE_ROOT_CA, } from "./proof-trust-constants.js";
|
|
6
|
+
import { legacyErrorClass } from "./proof-verification-error.js";
|
|
7
|
+
const AttestorTeeVerificationError = legacyErrorClass('AttestorTeeVerificationError');
|
|
8
|
+
const BROWSER_ENVIRONMENT_ERROR = 'Attestor TEE attestation verification is only supported in non-browser environments. Run verifyAttestorTeeAttestation on your server or API route.';
|
|
9
|
+
function isBrowserEnvironment() {
|
|
10
|
+
if ('window' in globalThis || 'document' in globalThis) {
|
|
11
|
+
return true;
|
|
12
|
+
}
|
|
13
|
+
if (typeof navigator !== 'undefined' && typeof process === 'undefined') {
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
const workerGlobalScope = globalThis.WorkerGlobalScope;
|
|
17
|
+
if (typeof workerGlobalScope === 'function' &&
|
|
18
|
+
globalThis instanceof workerGlobalScope) {
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
function assertNonBrowserEnvironment() {
|
|
24
|
+
if (isBrowserEnvironment()) {
|
|
25
|
+
throw new Error(BROWSER_ENVIRONMENT_ERROR);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
const TOKEN_CLOCK_SKEW_S = 60;
|
|
29
|
+
function decodeBase64Url(input) {
|
|
30
|
+
const normalized = input.replace(/-/g, '+').replace(/_/g, '/');
|
|
31
|
+
const padded = normalized + '='.repeat((4 - (normalized.length % 4)) % 4);
|
|
32
|
+
return Buffer.from(padded, 'base64');
|
|
33
|
+
}
|
|
34
|
+
function normalizeAddress(address) {
|
|
35
|
+
return address.trim().toLowerCase().replace(/^0x/, '');
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Walks the x5c certificate chain (leaf first) and verifies each link
|
|
39
|
+
* up to the pinned GCP Confidential Space Root CA. Returns the leaf
|
|
40
|
+
* certificate's public key on success.
|
|
41
|
+
*/
|
|
42
|
+
function verifyX5cChain(x5cChain) {
|
|
43
|
+
if (!x5cChain || x5cChain.length === 0) {
|
|
44
|
+
throw new Error('Empty x5c certificate chain');
|
|
45
|
+
}
|
|
46
|
+
const certs = x5cChain.map((b64) => new X509Certificate(`-----BEGIN CERTIFICATE-----\n${b64}\n-----END CERTIFICATE-----`));
|
|
47
|
+
const root = new X509Certificate(GCP_CONFIDENTIAL_SPACE_ROOT_CA);
|
|
48
|
+
for (let i = 0; i < certs.length - 1; i++) {
|
|
49
|
+
if (!certs[i].verify(certs[i + 1].publicKey)) {
|
|
50
|
+
throw new Error(`Certificate chain verification failed at level ${i}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
const top = certs[certs.length - 1];
|
|
54
|
+
if (!top.verify(root.publicKey)) {
|
|
55
|
+
throw new Error('Certificate chain does not root to GCP Confidential Space Root CA');
|
|
56
|
+
}
|
|
57
|
+
return certs[0].publicKey;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Validates a GCP Confidential Space attestation JWT produced by an
|
|
61
|
+
* attestor running in a Confidential Space VM, and asserts that the
|
|
62
|
+
* attestation binds to the given attestor address.
|
|
63
|
+
*
|
|
64
|
+
* The attestor (running inside the TEE) calls the Confidential Space
|
|
65
|
+
* launcher's attestation endpoint with two nonces:
|
|
66
|
+
* - `attestor_public_key:<eth-address>` - binds to the signing key.
|
|
67
|
+
* - `attestor_cert_hash:<sha256-hex>` - binds to the live TLS cert.
|
|
68
|
+
*
|
|
69
|
+
* This function only verifies the public-key nonce. The TLS cert hash
|
|
70
|
+
* binding is informational and not checked here. Callers that need to
|
|
71
|
+
* pin to a specific attestor image should compare the returned
|
|
72
|
+
* `imageDigest` against a known-good value.
|
|
73
|
+
*
|
|
74
|
+
* The JWT signature is verified by walking the x5c certificate chain
|
|
75
|
+
* to a pinned GCP Confidential Space Root CA. No outbound network
|
|
76
|
+
* calls are made.
|
|
77
|
+
*
|
|
78
|
+
* Node-only (uses node:crypto). Mirrors the environment restriction in
|
|
79
|
+
* the existing `verifyTeeAttestation` helper.
|
|
80
|
+
*
|
|
81
|
+
* @param report - the raw JWT string (header.payload.signature).
|
|
82
|
+
* @param expectedAttestorAddress - hex ETH address (0x-prefixed or
|
|
83
|
+
* unprefixed) that the attestation should be bound to.
|
|
84
|
+
*/
|
|
85
|
+
export async function verifyAttestorTeeAttestation(report, expectedAttestorAddress) {
|
|
86
|
+
try {
|
|
87
|
+
assertNonBrowserEnvironment();
|
|
88
|
+
if (!report || typeof report !== 'string') {
|
|
89
|
+
throw new Error('attestation report is empty or not a string');
|
|
90
|
+
}
|
|
91
|
+
if (!expectedAttestorAddress || typeof expectedAttestorAddress !== 'string') {
|
|
92
|
+
throw new Error('expectedAttestorAddress is required');
|
|
93
|
+
}
|
|
94
|
+
const parts = report.split('.');
|
|
95
|
+
if (parts.length !== 3) {
|
|
96
|
+
throw new Error('attestation report is not a JWT (expected 3 parts)');
|
|
97
|
+
}
|
|
98
|
+
const [headerB64, payloadB64, signatureB64] = parts;
|
|
99
|
+
const header = JSON.parse(decodeBase64Url(headerB64).toString('utf8'));
|
|
100
|
+
const payload = JSON.parse(decodeBase64Url(payloadB64).toString('utf8'));
|
|
101
|
+
if (header.alg !== 'RS256') {
|
|
102
|
+
throw new Error(`unexpected signing algorithm: ${header.alg}`);
|
|
103
|
+
}
|
|
104
|
+
if (!Array.isArray(header.x5c) || header.x5c.length === 0) {
|
|
105
|
+
throw new Error('attestation report is missing x5c certificate chain');
|
|
106
|
+
}
|
|
107
|
+
if (payload.iss !== GCP_CONFIDENTIAL_SPACE_ISSUER) {
|
|
108
|
+
throw new Error(`unexpected issuer: ${payload.iss}`);
|
|
109
|
+
}
|
|
110
|
+
const now = Math.floor(Date.now() / 1000);
|
|
111
|
+
if (typeof payload.nbf === 'number' && now + TOKEN_CLOCK_SKEW_S < payload.nbf) {
|
|
112
|
+
throw new Error(`attestation not yet valid (nbf=${payload.nbf})`);
|
|
113
|
+
}
|
|
114
|
+
if (typeof payload.exp === 'number' && now - TOKEN_CLOCK_SKEW_S > payload.exp) {
|
|
115
|
+
throw new Error(`attestation expired (exp=${payload.exp})`);
|
|
116
|
+
}
|
|
117
|
+
if (typeof payload.iat === 'number' && payload.iat > now + TOKEN_CLOCK_SKEW_S) {
|
|
118
|
+
throw new Error(`attestation issued in future (iat=${payload.iat})`);
|
|
119
|
+
}
|
|
120
|
+
const publicKey = verifyX5cChain(header.x5c);
|
|
121
|
+
const verifier = crypto.createVerify('RSA-SHA256');
|
|
122
|
+
verifier.update(`${headerB64}.${payloadB64}`);
|
|
123
|
+
if (!verifier.verify(publicKey, new Uint8Array(decodeBase64Url(signatureB64)))) {
|
|
124
|
+
throw new Error('attestation signature verification failed');
|
|
125
|
+
}
|
|
126
|
+
if (!payload.eat_nonce) {
|
|
127
|
+
throw new Error('eat_nonce claim is missing');
|
|
128
|
+
}
|
|
129
|
+
const nonces = Array.isArray(payload.eat_nonce)
|
|
130
|
+
? payload.eat_nonce
|
|
131
|
+
: [payload.eat_nonce];
|
|
132
|
+
let attestedAddress;
|
|
133
|
+
for (const n of nonces) {
|
|
134
|
+
const m = typeof n === 'string' ? n.match(ATTESTOR_NONCE_PATTERN) : null;
|
|
135
|
+
if (m) {
|
|
136
|
+
attestedAddress = m[1];
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if (!attestedAddress) {
|
|
141
|
+
throw new Error(`attestor_public_key nonce not found in eat_nonce: ${JSON.stringify(payload.eat_nonce)}`);
|
|
142
|
+
}
|
|
143
|
+
if (normalizeAddress(attestedAddress) !== normalizeAddress(expectedAttestorAddress)) {
|
|
144
|
+
throw new Error(`attestor address mismatch: attestation binds to 0x${attestedAddress.toLowerCase()}, ` +
|
|
145
|
+
`expected ${expectedAttestorAddress}`);
|
|
146
|
+
}
|
|
147
|
+
const imageDigest = payload.submods?.container?.image_digest
|
|
148
|
+
?? payload.google?.compute_engine?.image_digest;
|
|
149
|
+
return { isVerified: true, imageDigest };
|
|
150
|
+
}
|
|
151
|
+
catch (error) {
|
|
152
|
+
return {
|
|
153
|
+
isVerified: false,
|
|
154
|
+
error: error instanceof Error ? error.message : String(error),
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
function normalizeAttestorAddress(address) {
|
|
159
|
+
return address.trim().toLowerCase();
|
|
160
|
+
}
|
|
161
|
+
function normalizeSignature(sig) {
|
|
162
|
+
return sig.trim().toLowerCase();
|
|
163
|
+
}
|
|
164
|
+
async function verifyAttestorTeeForProof(proof, config) {
|
|
165
|
+
if (!proof.witnesses || proof.witnesses.length === 0) {
|
|
166
|
+
throw new AttestorTeeVerificationError('Proof has no witnesses');
|
|
167
|
+
}
|
|
168
|
+
const expectedDigests = config.expectedImageDigests?.map(d => d.trim());
|
|
169
|
+
const proofSignatures = new Set((proof.signatures || []).map(normalizeSignature));
|
|
170
|
+
const claimSignData = createSignDataForClaim(proof.claimData);
|
|
171
|
+
for (const witness of proof.witnesses) {
|
|
172
|
+
const att = witness.claimAttestation;
|
|
173
|
+
if (!att) {
|
|
174
|
+
throw new AttestorTeeVerificationError(`Witness ${witness.id} is missing claimAttestation`);
|
|
175
|
+
}
|
|
176
|
+
if (normalizeAttestorAddress(att.attestor_address) !== normalizeAttestorAddress(witness.id)) {
|
|
177
|
+
throw new AttestorTeeVerificationError(`claimAttestation.attestor_address ${att.attestor_address} does not match witness id ${witness.id}`);
|
|
178
|
+
}
|
|
179
|
+
if (!proofSignatures.has(normalizeSignature(att.claim_signature))) {
|
|
180
|
+
throw new AttestorTeeVerificationError(`claimAttestation.claim_signature for witness ${witness.id} is not present in proof.signatures`);
|
|
181
|
+
}
|
|
182
|
+
let recoveredSigner;
|
|
183
|
+
try {
|
|
184
|
+
recoveredSigner = recoverEip191Address(claimSignData, att.claim_signature);
|
|
185
|
+
}
|
|
186
|
+
catch (error) {
|
|
187
|
+
throw new AttestorTeeVerificationError(`Failed to recover signer from claimAttestation.claim_signature for witness ${witness.id}`, error);
|
|
188
|
+
}
|
|
189
|
+
if (normalizeAttestorAddress(recoveredSigner) !== normalizeAttestorAddress(witness.id)) {
|
|
190
|
+
throw new AttestorTeeVerificationError(`claim_signature recovers to ${recoveredSigner}, expected attestor ${witness.id}`);
|
|
191
|
+
}
|
|
192
|
+
const result = await verifyAttestorTeeAttestation(att.attestation_report, witness.id);
|
|
193
|
+
if (!result.isVerified) {
|
|
194
|
+
throw new AttestorTeeVerificationError(`Attestor TEE attestation verification failed for witness ${witness.id}: ${result.error}`);
|
|
195
|
+
}
|
|
196
|
+
if (expectedDigests && expectedDigests.length > 0) {
|
|
197
|
+
if (!result.imageDigest) {
|
|
198
|
+
throw new AttestorTeeVerificationError(`Attestor TEE attestation for witness ${witness.id} did not expose an image digest to check against expectedImageDigests`);
|
|
199
|
+
}
|
|
200
|
+
if (!expectedDigests.includes(result.imageDigest)) {
|
|
201
|
+
throw new AttestorTeeVerificationError(`Attestor image digest ${result.imageDigest} for witness ${witness.id} is not in expectedImageDigests`);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Verifies the attestor's TEE attestation for every witness of every
|
|
208
|
+
* provided proof. Throws `AttestorTeeVerificationError` on the first
|
|
209
|
+
* failure.
|
|
210
|
+
*
|
|
211
|
+
* Each witness must carry a `claimAttestation`. For each one, this:
|
|
212
|
+
* 1. Asserts `attestor_address` matches `witness.id`.
|
|
213
|
+
* 2. Asserts `claim_signature` is present in `proof.signatures`.
|
|
214
|
+
* 3. Recovers the signer of `claim_signature` from the claim data and
|
|
215
|
+
* asserts it equals `witness.id` (binds the signature to the
|
|
216
|
+
* attestor that the TEE attestation will cover).
|
|
217
|
+
* 4. Calls `verifyAttestorTeeAttestation` to validate the JWT against
|
|
218
|
+
* the pinned GCP Confidential Space Root CA and the attestor-key
|
|
219
|
+
* nonce.
|
|
220
|
+
* 5. If `expectedImageDigests` is provided, asserts the attestation's
|
|
221
|
+
* container image digest is in the allowlist.
|
|
222
|
+
*
|
|
223
|
+
* Node-only (uses node:crypto), like `verifyTeeAttestation`.
|
|
224
|
+
*
|
|
225
|
+
* @param proofs - The proofs to verify.
|
|
226
|
+
* @param config - Optional config; see {@link AttestorTeeAttestationConfig}.
|
|
227
|
+
*/
|
|
228
|
+
export async function runAttestorTeeVerification(proofs, config = {}) {
|
|
229
|
+
if (!proofs || proofs.length === 0) {
|
|
230
|
+
throw new AttestorTeeVerificationError('No proofs provided for attestor TEE verification');
|
|
231
|
+
}
|
|
232
|
+
try {
|
|
233
|
+
for (const proof of proofs) {
|
|
234
|
+
await verifyAttestorTeeForProof(proof, config);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
catch (error) {
|
|
238
|
+
if (error instanceof AttestorTeeVerificationError) {
|
|
239
|
+
throw error;
|
|
240
|
+
}
|
|
241
|
+
throw new AttestorTeeVerificationError('Attestor TEE attestation verification failed', error);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { LegacyProof as Proof } from './proof-types.ts';
|
|
2
|
+
export interface TeeAttestationConfig {
|
|
3
|
+
appSecret: string;
|
|
4
|
+
}
|
|
5
|
+
export type TeeVerificationResult = {
|
|
6
|
+
isVerified: boolean;
|
|
7
|
+
error?: string;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Validates the hardware TEE attestation included in the proof.
|
|
11
|
+
* Derives the application ID from `appSecret` and verifies the attestation
|
|
12
|
+
* was generated for your application.
|
|
13
|
+
* Returns a result object with `isVerified` and an optional `error` message.
|
|
14
|
+
*
|
|
15
|
+
* This check is stateless and does not protect against replay of a previously
|
|
16
|
+
* valid proof. Callers must enforce session matching and dedup `sessionId` on
|
|
17
|
+
* their server. See the README's "Replay Protection" section.
|
|
18
|
+
*
|
|
19
|
+
* @param proof - The proof containing TEE attestation data
|
|
20
|
+
* @param appSecret - Your application secret (Ethereum private key). Used to
|
|
21
|
+
* derive the application ID and recompute the attestation nonce.
|
|
22
|
+
*/
|
|
23
|
+
export declare function verifyTeeAttestation(proof: Proof, appSecret: string): Promise<TeeVerificationResult>;
|
|
24
|
+
/**
|
|
25
|
+
* Verifies TEE attestation for all proofs.
|
|
26
|
+
* Throws `TeeVerificationError` if any proof is missing TEE data or fails verification.
|
|
27
|
+
*
|
|
28
|
+
* @param proofs - The proofs to verify
|
|
29
|
+
* @param config - TEE attestation configuration containing the app secret
|
|
30
|
+
* @throws {TeeVerificationError} When TEE data is missing or verification fails
|
|
31
|
+
*/
|
|
32
|
+
export declare function runTeeVerification(proofs: Proof[], config: TeeAttestationConfig): Promise<void>;
|