@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.
Files changed (73) hide show
  1. package/README.md +634 -42
  2. package/lib/api-client.d.ts +31 -0
  3. package/lib/api-client.js +112 -0
  4. package/lib/api-consts.d.ts +4 -0
  5. package/lib/api-consts.js +4 -0
  6. package/lib/api-errors.d.ts +13 -0
  7. package/lib/api-errors.js +30 -0
  8. package/lib/api.d.ts +7 -0
  9. package/lib/api.js +3 -0
  10. package/lib/attestor-proof.d.ts +6 -0
  11. package/lib/attestor-proof.js +45 -18
  12. package/lib/client.d.ts +1 -6
  13. package/lib/client.js +12 -33
  14. package/lib/consts.d.ts +1 -1
  15. package/lib/consts.js +1 -1
  16. package/lib/eip191.d.ts +4 -2
  17. package/lib/eip191.js +17 -1
  18. package/lib/errors.d.ts +38 -4
  19. package/lib/errors.js +99 -16
  20. package/lib/index.d.ts +9 -7
  21. package/lib/index.js +8 -4
  22. package/lib/jwks.d.ts +25 -1
  23. package/lib/jwks.js +69 -4
  24. package/lib/legacy-proof.d.ts +57 -0
  25. package/lib/legacy-proof.js +211 -0
  26. package/lib/proof-hash.d.ts +35 -0
  27. package/lib/proof-hash.js +216 -0
  28. package/lib/proof-recipe.d.ts +18 -0
  29. package/lib/proof-recipe.js +37 -0
  30. package/lib/proof-trust-constants.d.ts +3 -0
  31. package/lib/proof-trust-constants.js +43 -0
  32. package/lib/proof-types.d.ts +62 -0
  33. package/lib/proof-types.js +1 -0
  34. package/lib/proof-verification-error.d.ts +13 -0
  35. package/lib/proof-verification-error.js +11 -0
  36. package/lib/reclaim.d.ts +27 -5
  37. package/lib/reclaim.js +140 -14
  38. package/lib/result.d.ts +58 -6
  39. package/lib/result.js +252 -61
  40. package/lib/routes.gen.d.ts +54 -6
  41. package/lib/routes.gen.js +15 -3
  42. package/lib/tee-binding.d.ts +82 -0
  43. package/lib/tee-binding.js +112 -0
  44. package/lib/types/openapi.gen.d.ts +1343 -206
  45. package/lib/verification-clients.d.ts +17 -0
  46. package/lib/verification-clients.js +14 -0
  47. package/lib/verify-attestor-tee.d.ts +76 -0
  48. package/lib/verify-attestor-tee.js +243 -0
  49. package/lib/verify-tee.d.ts +32 -0
  50. package/lib/verify-tee.js +347 -0
  51. package/package.json +5 -1
  52. package/src/api.ts +15 -0
  53. package/src/attestor-proof.ts +57 -20
  54. package/src/client.ts +15 -38
  55. package/src/consts.ts +1 -2
  56. package/src/eip191.ts +31 -3
  57. package/src/errors.ts +132 -18
  58. package/src/index.ts +50 -5
  59. package/src/jwks.ts +128 -5
  60. package/src/legacy-proof.ts +367 -0
  61. package/src/proof-hash.ts +316 -0
  62. package/src/proof-recipe.ts +75 -0
  63. package/src/proof-trust-constants.ts +45 -0
  64. package/src/proof-types.ts +57 -0
  65. package/src/proof-verification-error.ts +13 -0
  66. package/src/reclaim.ts +206 -19
  67. package/src/result.ts +403 -68
  68. package/src/routes.gen.ts +15 -3
  69. package/src/tee-binding.ts +192 -0
  70. package/src/types/openapi.gen.ts +1343 -206
  71. package/src/verification-clients.ts +22 -0
  72. package/src/verify-attestor-tee.ts +365 -0
  73. package/src/verify-tee.ts +507 -0
@@ -0,0 +1,75 @@
1
+ import type { components } from './types/openapi.gen.ts'
2
+ import {
3
+ hashRequirementsFromSpecs,
4
+ type ProofRequestSpec,
5
+ } from './proof-hash.ts'
6
+ import type { LegacyProof } from './proof-types.ts'
7
+
8
+ type RequestSelection = components['schemas']['RequestSelection']
9
+ type RequestSelectionTemplate =
10
+ components['schemas']['RequestSelectionTemplate']
11
+
12
+ /** Resolved Builder provider recipe returned for a verification session. */
13
+ export interface BuilderProofRecipe {
14
+ providerId: string
15
+ resolvedVersion: string
16
+ initialUrl: string
17
+ requests: RequestSelection[]
18
+ allowedJsRequests?: RequestSelectionTemplate[]
19
+ }
20
+
21
+ /**
22
+ * Derive the exact hash requirements consumed by legacy `verifyProof` from a
23
+ * resolved Builder recipe. Builder's fixed requests are required once each.
24
+ */
25
+ export function proofHashesFromBuilderRecipe(
26
+ recipe: BuilderProofRecipe,
27
+ proofs: LegacyProof[],
28
+ ) {
29
+ return hashRequirementsFromSpecs(
30
+ recipe.requests.map((request) => toRequestSpec(
31
+ request,
32
+ { required: true, multiple: false },
33
+ )),
34
+ (recipe.allowedJsRequests ?? []).map(
35
+ (request) => toRequestSpec(request, {
36
+ required: request.required,
37
+ multiple: request.multiple,
38
+ templateParams: request.templateParams,
39
+ templateParamsMode: request.templateParamsMode,
40
+ }),
41
+ ),
42
+ proofs,
43
+ )
44
+ }
45
+
46
+ function toRequestSpec(
47
+ request: RequestSelection | RequestSelectionTemplate,
48
+ flags: Pick<
49
+ ProofRequestSpec,
50
+ 'required' | 'multiple' | 'templateParams' | 'templateParamsMode'
51
+ >,
52
+ ): ProofRequestSpec {
53
+ return {
54
+ url: request.url,
55
+ urlType: request.url.includes('{{') ? 'TEMPLATE' : 'CONSTANT',
56
+ method: request.method,
57
+ bodySniff: {
58
+ enabled: request.requestBodyTemplate !== undefined,
59
+ template: request.requestBodyTemplate ?? '',
60
+ },
61
+ responseMatches: (request.responseMatches ?? []).map((match) => ({
62
+ type: match.type,
63
+ value: match.value,
64
+ invert: undefined,
65
+ isOptional: match.isOptional,
66
+ })),
67
+ responseRedactions: (request.responseRedactions ?? []).map((redaction) => ({
68
+ ...(redaction.hash ? { hash: redaction.hash } : {}),
69
+ jsonPath: redaction.jsonPath ?? '',
70
+ regex: redaction.regex ?? '',
71
+ xPath: redaction.xPath ?? '',
72
+ })),
73
+ ...flags,
74
+ }
75
+ }
@@ -0,0 +1,45 @@
1
+ /* eslint-disable @stylistic/max-len */
2
+ // GCP Confidential Space Root CA — pinned. Rotates infrequently
3
+ // (current cert valid 2024-2034). Used to verify attestor TEE attestation JWTs.
4
+ export const GCP_CONFIDENTIAL_SPACE_ROOT_CA = `-----BEGIN CERTIFICATE-----
5
+ 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
37
+ uyT5CnJulxSC5CT1
38
+ -----END CERTIFICATE-----`
39
+
40
+ // Matches an `attestor_public_key:0x<40-hex>` entry in a GCP Confidential
41
+ // Space attestation JWT's `eat_nonce` claim.
42
+ export const ATTESTOR_NONCE_PATTERN = /^attestor_public_key:0x([0-9a-fA-F]{40})$/
43
+
44
+ // Expected `iss` claim on GCP Confidential Space attestation JWTs.
45
+ export const GCP_CONFIDENTIAL_SPACE_ISSUER = 'https://confidentialcomputing.googleapis.com'
@@ -0,0 +1,57 @@
1
+ /** Exact legacy proof structures carried inside Builder result envelopes. */
2
+ export interface LegacyProof {
3
+ identifier: string
4
+ claimData: LegacyClaimData
5
+ signatures: string[]
6
+ witnesses: LegacyWitness[]
7
+ extractedParameterValues: unknown
8
+ publicData?: unknown
9
+ taskId?: number
10
+ teeAttestation?: TeeAttestation | string
11
+ }
12
+
13
+ export interface LegacyClaimData {
14
+ provider: string
15
+ parameters: string
16
+ owner: string
17
+ timestampS: number
18
+ context: string
19
+ identifier: string
20
+ epoch: number
21
+ }
22
+
23
+ export interface LegacyWitness {
24
+ id: string
25
+ url: string
26
+ claimAttestation?: AttestorClaimAttestation
27
+ }
28
+
29
+ export interface AttestorClaimAttestation {
30
+ attestor_address: string
31
+ claim_signature: string
32
+ attestation_report: string
33
+ }
34
+
35
+ export interface TeeAttestation {
36
+ proof_version?: 'v2' | 'v3'
37
+ proofVersion?: 'v2' | 'v3'
38
+ tee_provider: string
39
+ tee_technology: string
40
+ nonce: string
41
+ timestamp: string
42
+ workload: { container_name?: string, image_digest: string }
43
+ verifier: { container_name?: string, image_digest: string }
44
+ attestation: { token: string }
45
+ error?: { code: string, message: string }
46
+ }
47
+
48
+ export interface TrustedData {
49
+ context: Record<string, unknown>
50
+ extractedParameters: Record<string, string>
51
+ }
52
+
53
+ export interface HashRequirement {
54
+ value: string | string[]
55
+ required?: boolean
56
+ multiple?: boolean
57
+ }
@@ -0,0 +1,13 @@
1
+ export function legacyErrorClass(name: string) {
2
+ return class extends Error {
3
+ readonly innerError?: unknown
4
+
5
+ constructor(message?: string, innerError?: unknown) {
6
+ super(innerError
7
+ ? `${message ?? ''} caused by ${String(innerError)}`
8
+ : message)
9
+ this.name = name
10
+ this.innerError = innerError
11
+ }
12
+ }
13
+ }
package/src/reclaim.ts CHANGED
@@ -28,6 +28,10 @@ import {
28
28
  type DecryptionKeyInput,
29
29
  loadDecryptionKey,
30
30
  } from './enckeys.ts'
31
+ import { isProblemError } from './errors.ts'
32
+ import type { BuilderProofRecipe } from './proof-recipe.ts'
33
+ import { proofHashesFromBuilderRecipe } from './proof-recipe.ts'
34
+ import type { LegacyProof } from './proof-types.ts'
31
35
  import {
32
36
  type CallbackBody,
33
37
  decryptCallback,
@@ -36,6 +40,10 @@ import {
36
40
  type VerifyResultFullOptions,
37
41
  type VerifyResultFullOutcome,
38
42
  } from './result.ts'
43
+ import {
44
+ createBuilderTeeBinding,
45
+ type LegacyTeeNonceMode,
46
+ } from './tee-binding.ts'
39
47
 
40
48
  type CallbackSubscription = components['schemas']['CallbackSubscription']
41
49
  type VerificationSession = components['schemas']['VerificationSession']
@@ -44,11 +52,19 @@ type VerificationEventRecord = components['schemas']['VerificationEventRecord']
44
52
  type OrgKeypair = components['schemas']['OrgKeypair']
45
53
  type OrgKeypairRequest = components['schemas']['OrgKeypairRequest']
46
54
 
47
- /** The three terminal events a subscription can receive. */
55
+ /** Terminal lifecycle events selected by default for callback subscriptions. */
48
56
  export const TERMINAL_EVENTS = [
49
57
  'verification_success',
50
58
  'verification_rejected',
51
59
  'verification_error',
60
+ 'verification_cancelled',
61
+ 'session_expired',
62
+ ] as const
63
+
64
+ const SIGNED_RESULT_EVENTS = [
65
+ 'verification_success',
66
+ 'verification_rejected',
67
+ 'verification_error',
52
68
  ] as const
53
69
 
54
70
  export interface RegisterCallbackInput {
@@ -61,8 +77,25 @@ export interface CreateSessionInput {
61
77
  * session) and one combined result is delivered; all are required. */
62
78
  providers: { providerId: string, version?: string }[]
63
79
  context: Record<string, unknown>
64
- /** URL with a `{sessionId}` placeholder the end-user opens. */
80
+ /** Exact registered Verification Client base URL. It may contain a
81
+ * `{sessionId}` placeholder; Builder otherwise adds the session id as a
82
+ * query parameter. Open the returned `verificationUrl`, not this base
83
+ * URL. */
65
84
  verificationClientUrl: string
85
+ /** Optional legacy-compatible TEE session binding. The app secret is the
86
+ * organization's verification private key and never leaves this process.
87
+ * Session creation returns only after Builder stores the signed binding. */
88
+ teeAttestation?: CreateSessionTeeAttestation
89
+ }
90
+
91
+ /** Options intentionally mirror legacy `verifyProof`'s `teeAttestation`
92
+ * vocabulary. `timestamp` is decimal Unix milliseconds; omit it to use now. */
93
+ export interface CreateSessionTeeAttestation {
94
+ appSecret: string
95
+ timestamp?: string
96
+ /** `hash` is the default. Use `signature` only for an older Verification
97
+ * Client that expects the 65-byte EIP-191 nonce. */
98
+ nonceMode?: LegacyTeeNonceMode
66
99
  }
67
100
 
68
101
  /** Input for `keypair.set`: the org's eth public key as uncompressed hex
@@ -105,6 +138,14 @@ export interface Reclaim {
105
138
  input: Omit<CreateSessionInput, 'verificationClientUrl'> &
106
139
  Partial<Pick<CreateSessionInput, 'verificationClientUrl'>>,
107
140
  ): Promise<VerificationSession>
141
+ /** Add the immutable legacy-compatible TEE context after session
142
+ * creation. Normally use
143
+ * `sessions.create({ teeAttestation: { appSecret } })`; this method
144
+ * supports retrying or explicitly orchestrating the two-step flow. */
145
+ bindTeeNonce(
146
+ sessionId: string,
147
+ input: CreateSessionTeeAttestation,
148
+ ): Promise<VerificationSession>
108
149
  get(sessionId: string): Promise<VerificationSession>
109
150
  listEvents(sessionId: string): Promise<VerificationEventRecord[]>
110
151
  }
@@ -112,7 +153,7 @@ export interface Reclaim {
112
153
  /** Register a callback subscription (one-time setup). Delivery is scoped
113
154
  * to the token's org; results are ECIES-encrypted to the org's eth key
114
155
  * when `canEncryptResult` is enabled (see `keypair.set`), else plaintext.
115
- * `events` defaults to all three terminal events. */
156
+ * `events` defaults to all terminal lifecycle events. */
116
157
  register(
117
158
  input: Partial<RegisterCallbackInput> &
118
159
  Pick<RegisterCallbackInput, 'callbackUrl'>,
@@ -135,7 +176,7 @@ export interface Reclaim {
135
176
  * the delivery is encrypted. */
136
177
  decrypt(body: CallbackBody, credential?: DecryptionKeyInput): Promise<Jws>
137
178
  /** Verify a decrypted JWS: JWKS discovery, ES256K signature,
138
- * `reclaimSessionId`/`aud`/`exp`, nested attestor proofs. */
179
+ * `reclaimSessionId`/`aud`, any legacy `exp`, and exact legacy proofs. */
139
180
  verify(
140
181
  jws: Jws,
141
182
  opts?: VerifyResultFullOptions,
@@ -190,15 +231,25 @@ export function createReclaim(config: ReclaimConfig): Reclaim {
190
231
  api: client.call.bind(client),
191
232
  sessions: {
192
233
  async create(input) {
193
- const body: CreateSessionInput = {
194
- ...input,
234
+ const { teeAttestation, ...request } = input
235
+ const body:
236
+ components['schemas']['CreateVerificationSessionRequest'] = {
237
+ ...request,
195
238
  verificationClientUrl:
196
- input.verificationClientUrl ?? `${client.baseUrl}/v/{sessionId}`,
239
+ request.verificationClientUrl ?? `${client.baseUrl}/v/{sessionId}`,
197
240
  }
198
241
  const { data } = await client.call('CreateVerificationSession', {
199
242
  body,
200
243
  })
201
- return data
244
+ return teeAttestation
245
+ ? bindTeeNonce(data, teeAttestation)
246
+ : data
247
+ },
248
+ async bindTeeNonce(sessionId, input) {
249
+ const { data } = await client.call('GetVerificationSession', {
250
+ params: { sessionId },
251
+ })
252
+ return bindTeeNonce(data, input)
202
253
  },
203
254
  async get(sessionId) {
204
255
  const { data } = await client.call('GetVerificationSession', {
@@ -269,7 +320,10 @@ export function createReclaim(config: ReclaimConfig): Reclaim {
269
320
  return decryptCallback(resolveKey(credential), body)
270
321
  },
271
322
  verify(jws, opts) {
272
- return verifyResultFull(jws, { fetchImpl: config.fetch, ...opts })
323
+ return verifyResultFull(jws, {
324
+ fetchImpl: config.fetch,
325
+ ...withRegisteredIssuer(opts),
326
+ })
273
327
  },
274
328
  async receive(body, opts) {
275
329
  // A delivery envelope tagged with a non-terminal event carries a
@@ -283,7 +337,7 @@ export function createReclaim(config: ReclaimConfig): Reclaim {
283
337
  const env = body
284
338
  if(
285
339
  env.event
286
- && !(TERMINAL_EVENTS as readonly string[]).includes(env.event)
340
+ && !(SIGNED_RESULT_EVENTS as readonly string[]).includes(env.event)
287
341
  ) {
288
342
  const decoded = parseEventData(env.data)
289
343
  return {
@@ -299,12 +353,151 @@ export function createReclaim(config: ReclaimConfig): Reclaim {
299
353
  const jws = await decryptCallback(key, body)
300
354
  const result = await verifyResultFull(jws, {
301
355
  fetchImpl: config.fetch,
302
- ...verifyOpts,
356
+ ...withRegisteredIssuer(verifyOpts),
303
357
  })
304
358
  return { kind: 'result', result }
305
359
  },
306
360
  },
307
361
  }
362
+
363
+ async function bindTeeNonce(
364
+ session: VerificationSession,
365
+ input: CreateSessionTeeAttestation,
366
+ ) {
367
+ const binding = createBuilderTeeBinding({
368
+ organizationId: session.orgId,
369
+ organizationVerificationPrivateKey: input.appSecret,
370
+ sessionId: session.id,
371
+ timestamp: input.timestamp ?? Date.now().toString(),
372
+ nonceMode: input.nonceMode,
373
+ })
374
+ const { data } = await client.call('BindVerificationSessionTeeNonce', {
375
+ params: { sessionId: session.id },
376
+ body: {
377
+ attestationNonce: binding.attestationNonce,
378
+ attestationNonceData: binding.attestationNonceData,
379
+ signature: binding.signature,
380
+ },
381
+ })
382
+ return data
383
+ }
384
+
385
+ async function resolvedSession(id: string) {
386
+ const { data } = await client.call(
387
+ 'GetVerificationRecipe',
388
+ { params: { sessionId: id } },
389
+ )
390
+ return { recipes: data.recipes satisfies BuilderProofRecipe[] }
391
+ }
392
+
393
+ async function resolvedProvider(
394
+ providerId: string,
395
+ providerVersion: string | undefined,
396
+ _allowedTags: string[] | undefined,
397
+ proofs: LegacyProof[],
398
+ ) {
399
+ let exactVersion = providerVersion
400
+ if(!exactVersion) {
401
+ const { data: provider } = await client.call('GetProvider', {
402
+ params: { providerId },
403
+ })
404
+ if(!provider.latestVersion) {
405
+ throw new Error(`Provider ${providerId} has no live version`)
406
+ }
407
+
408
+ exactVersion = canonicalVersion(provider.latestVersion)
409
+ }
410
+
411
+ const { data: version } = await client.call('GetProviderVersion', {
412
+ params: { providerId, version: exactVersion },
413
+ })
414
+ return [{
415
+ hashes: proofHashesFromBuilderRecipe({
416
+ providerId,
417
+ resolvedVersion: exactVersion,
418
+ initialUrl: version.initialUrl,
419
+ requests: version.requests,
420
+ allowedJsRequests: version.allowedJsRequests,
421
+ }, proofs),
422
+ }]
423
+ }
424
+
425
+ function withRegisteredIssuer(opts: VerifyResultFullOptions = {}) {
426
+ const sessionId = opts.sessionId ?? opts.expectedReclaimSessionId
427
+ const requestedProofValidation = opts.proofValidation
428
+ ?? opts.proofVerification
429
+ const resolvedOpts: VerifyResultFullOptions = {
430
+ ...opts,
431
+ proofValidation: {
432
+ ...requestedProofValidation,
433
+ resolveProvider: requestedProofValidation?.resolveProvider
434
+ ?? resolvedProvider,
435
+ },
436
+ ...(sessionId && !opts.resolveSession && !opts.proofRecipes
437
+ ? { resolveSession: resolvedSession }
438
+ : {}),
439
+ }
440
+ if(
441
+ opts.signerPublicKey
442
+ || opts.issuer
443
+ || opts.trustedIssuer
444
+ || opts.issuers?.length
445
+ || opts.trustedIssuers?.length
446
+ || opts.resolveIssuers
447
+ || opts.issuerResolver
448
+ || opts.issuersByVerificationClientId
449
+ || opts.jwksUrl
450
+ || opts.jwksUrlsByVerificationClientId
451
+ || opts.resolveJwksUrl
452
+ ) {
453
+ return resolvedOpts
454
+ }
455
+
456
+ return {
457
+ ...resolvedOpts,
458
+ resolveIssuers: async(verificationClientId: string) => {
459
+ const session = await registeredClient(verificationClientId)
460
+ if(!session) {
461
+ return []
462
+ }
463
+
464
+ return session.verificationClientIssuer
465
+ ? [session.verificationClientIssuer]
466
+ : []
467
+ },
468
+ resolveJwksUrl: async(verificationClientId: string) => {
469
+ const session = await registeredClient(verificationClientId)
470
+ return session?.verificationClientJwksUrl
471
+ },
472
+ }
473
+
474
+ async function registeredClient(verificationClientId: string) {
475
+ if(!sessionId) {
476
+ return undefined
477
+ }
478
+
479
+ const { data: session } = await client.call(
480
+ 'GetVerificationSession',
481
+ { params: { sessionId } },
482
+ )
483
+ if(session.verificationClientId !== verificationClientId) {
484
+ return undefined
485
+ }
486
+
487
+ return session
488
+ }
489
+ }
490
+ }
491
+
492
+ function canonicalVersion(
493
+ version: components['schemas']['ProviderVersionRef'],
494
+ ) {
495
+ const base = `${version.major}.${version.minor}.${version.patch}`
496
+ return version.branch
497
+ ? `${base}-${version.branch}${version.buildNumber === undefined
498
+ ? ''
499
+ : `.${version.buildNumber}`}`
500
+ : base
308
501
  }
309
502
 
310
503
  /** Non-terminal `data` is a base64-encoded JSON `{ event, eventData }`. */
@@ -323,13 +516,7 @@ function parseEventData(data: string | undefined): Record<string, unknown> {
323
516
  }
324
517
  }
325
518
 
326
- /** True for a `404` thrown by {@link ReclaimClient.call} — a Boom error whose
327
- * `output.statusCode` is 404 (see ./errors.ts). */
519
+ /** True for a `404` thrown by {@link ReclaimClient.call}. */
328
520
  function isNotFound(err: unknown): boolean {
329
- if(typeof err !== 'object' || err === null) {
330
- return false
331
- }
332
-
333
- const boom = err as { output?: { statusCode?: number }, statusCode?: number }
334
- return boom.output?.statusCode === 404 || boom.statusCode === 404
521
+ return isProblemError(err) && err.output.statusCode === 404
335
522
  }