@reclaimprotocol/client 0.1.27 → 0.1.29
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
package/src/errors.ts
CHANGED
|
@@ -1,30 +1,144 @@
|
|
|
1
|
-
import { Boom } from '@hapi/boom'
|
|
2
1
|
import type { ProblemDetails } from './types/openapi.ts'
|
|
3
2
|
import { PROBLEM_CONTENT_TYPE } from './consts.ts'
|
|
4
|
-
export type { ProblemDetails } from './types/openapi.ts'
|
|
5
3
|
|
|
6
|
-
|
|
4
|
+
type ProblemErrorOutput = {
|
|
5
|
+
statusCode: number
|
|
6
|
+
headers: Record<string, string>
|
|
7
|
+
payload: ProblemDetails & {
|
|
8
|
+
statusCode: number
|
|
9
|
+
error: string
|
|
10
|
+
message: string
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* RFC 9457 error with the fields expected by Boom consumers.
|
|
16
|
+
*
|
|
17
|
+
* `@hapi/boom`'s `isBoom()` recognizes this structural contract. The client
|
|
18
|
+
* doesn't import Boom at runtime, so this error remains browser-safe.
|
|
19
|
+
*/
|
|
20
|
+
export type ProblemError = Error & {
|
|
21
|
+
readonly isBoom: true
|
|
22
|
+
readonly isServer: boolean
|
|
23
|
+
readonly data: ProblemDetails
|
|
24
|
+
readonly output: ProblemErrorOutput
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Returns whether a thrown value is a Builder {@link ProblemError}. */
|
|
28
|
+
export function isProblemError(error: unknown): error is ProblemError {
|
|
29
|
+
if(!(error instanceof Error) || !('isBoom' in error)) {
|
|
30
|
+
return false
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if(
|
|
34
|
+
error.isBoom !== true
|
|
35
|
+
|| !('isServer' in error)
|
|
36
|
+
|| typeof error.isServer !== 'boolean'
|
|
37
|
+
|| !('data' in error)
|
|
38
|
+
|| !('output' in error)
|
|
39
|
+
) {
|
|
40
|
+
return false
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return isProblemDetails(error.data)
|
|
44
|
+
&& isProblemErrorOutput(error.output)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Returns whether a value follows the RFC 9457 problem-details schema. */
|
|
48
|
+
export function isProblemDetails(value: unknown): value is ProblemDetails {
|
|
49
|
+
if(!isRecord(value)) {
|
|
50
|
+
return false
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return isOptionalString(value.type)
|
|
54
|
+
&& isOptionalString(value.title)
|
|
55
|
+
&& isOptionalStatus(value.status)
|
|
56
|
+
&& isOptionalString(value.detail)
|
|
57
|
+
&& isOptionalString(value.instance)
|
|
58
|
+
&& isOptionalString(value.event)
|
|
59
|
+
&& isOptionalString(value.sessionId)
|
|
60
|
+
}
|
|
7
61
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
export async function throwProblemError(res: Response) {
|
|
62
|
+
/** Reads an RFC 9457 response, with a status-only fallback for other bodies. */
|
|
63
|
+
export async function readProblemDetails(res: Response) {
|
|
11
64
|
const contentType = res.headers.get('Content-Type') || ''
|
|
12
|
-
let problem: ProblemDetails
|
|
13
65
|
if(contentType.includes(PROBLEM_CONTENT_TYPE)) {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
66
|
+
try {
|
|
67
|
+
const value: unknown = await res.json()
|
|
68
|
+
if(isProblemDetails(value)) {
|
|
69
|
+
return {
|
|
70
|
+
...value,
|
|
71
|
+
type: value.type || 'about:blank',
|
|
72
|
+
title: value.title || res.statusText,
|
|
73
|
+
status: value.status || res.status,
|
|
74
|
+
} satisfies ProblemDetails
|
|
75
|
+
}
|
|
76
|
+
} catch{
|
|
77
|
+
// Fall back to the HTTP status when the problem body isn't valid JSON.
|
|
20
78
|
}
|
|
21
79
|
}
|
|
22
80
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
81
|
+
return {
|
|
82
|
+
type: 'about:blank',
|
|
83
|
+
status: res.status,
|
|
84
|
+
title: res.statusText,
|
|
85
|
+
} satisfies ProblemDetails
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Throws a browser-safe, Boom-compatible RFC 9457 error. */
|
|
89
|
+
export async function throwProblemError(res: Response): Promise<never> {
|
|
90
|
+
const problem = await readProblemDetails(res)
|
|
91
|
+
const title = problem.title || res.statusText || 'Request failed'
|
|
92
|
+
const fields = {
|
|
93
|
+
isBoom: true,
|
|
94
|
+
isServer: res.status >= 500,
|
|
95
|
+
data: problem,
|
|
96
|
+
output: {
|
|
26
97
|
statusCode: res.status,
|
|
27
|
-
|
|
98
|
+
headers: { 'content-type': PROBLEM_CONTENT_TYPE },
|
|
99
|
+
payload: {
|
|
100
|
+
...problem,
|
|
101
|
+
statusCode: res.status,
|
|
102
|
+
error: title,
|
|
103
|
+
message: problem.detail || title,
|
|
104
|
+
},
|
|
28
105
|
},
|
|
29
|
-
|
|
106
|
+
} satisfies Omit<ProblemError, keyof Error>
|
|
107
|
+
const error = Object.assign(new Error(title), fields)
|
|
108
|
+
error.name = 'ProblemError'
|
|
109
|
+
throw error
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
113
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function isProblemErrorOutput(value: unknown): value is ProblemErrorOutput {
|
|
117
|
+
if(!isRecord(value) || !isRecord(value.payload)) {
|
|
118
|
+
return false
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return typeof value.statusCode === 'number'
|
|
122
|
+
&& isStringRecord(value.headers)
|
|
123
|
+
&& isProblemDetails(value.payload)
|
|
124
|
+
&& typeof value.payload.statusCode === 'number'
|
|
125
|
+
&& typeof value.payload.error === 'string'
|
|
126
|
+
&& typeof value.payload.message === 'string'
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function isStringRecord(value: unknown): value is Record<string, string> {
|
|
130
|
+
return isRecord(value)
|
|
131
|
+
&& Object.values(value).every(item => typeof item === 'string')
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function isOptionalString(value: unknown) {
|
|
135
|
+
return value === undefined || typeof value === 'string'
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function isOptionalStatus(value: unknown) {
|
|
139
|
+
return value === undefined
|
|
140
|
+
|| typeof value === 'number'
|
|
141
|
+
&& Number.isInteger(value)
|
|
142
|
+
&& value >= 100
|
|
143
|
+
&& value <= 599
|
|
30
144
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
|
-
export * from './
|
|
2
|
-
export
|
|
3
|
-
export { throwProblemError } from './errors.ts'
|
|
4
|
-
export type { ProblemDetails, ProblemError } from './errors.ts'
|
|
5
|
-
export { DEFAULT_BASE_URL, PROBLEM_CONTENT_TYPE, USER_AGENT } from './consts.ts'
|
|
1
|
+
export * from './api.ts'
|
|
2
|
+
export { USER_AGENT } from './consts.ts'
|
|
6
3
|
export {
|
|
7
4
|
addressFromPublicKey,
|
|
8
5
|
deriveFromPrivateKey,
|
|
@@ -51,6 +48,7 @@ export {
|
|
|
51
48
|
export {
|
|
52
49
|
createReclaim,
|
|
53
50
|
type CreateSessionInput,
|
|
51
|
+
type CreateSessionTeeAttestation,
|
|
54
52
|
type Reclaim,
|
|
55
53
|
type ReclaimConfig,
|
|
56
54
|
type ReceiveOptions,
|
|
@@ -61,12 +59,59 @@ export {
|
|
|
61
59
|
} from './reclaim.ts'
|
|
62
60
|
export {
|
|
63
61
|
fetchJwks,
|
|
62
|
+
fetchJwksUrl,
|
|
63
|
+
fetchReclaimJwks,
|
|
64
64
|
type Jwk,
|
|
65
65
|
jwksFromPrivateKey,
|
|
66
66
|
jwkToUncompressedPublicKey,
|
|
67
|
+
type ReclaimJwks,
|
|
67
68
|
selectKey,
|
|
69
|
+
selectVerificationResultKey,
|
|
70
|
+
trustedAttestorAddresses,
|
|
71
|
+
validateAttestorAddresses,
|
|
68
72
|
} from './jwks.ts'
|
|
69
73
|
export {
|
|
70
74
|
type NestedProofOutcome,
|
|
71
75
|
verifyNestedProof,
|
|
72
76
|
} from './attestor-proof.ts'
|
|
77
|
+
export {
|
|
78
|
+
type LegacyProof,
|
|
79
|
+
type LegacyProofOutcome,
|
|
80
|
+
type LegacyVerifyProofOptions,
|
|
81
|
+
type ProofHashRequirement,
|
|
82
|
+
proofHashes,
|
|
83
|
+
verifyProof,
|
|
84
|
+
} from './legacy-proof.ts'
|
|
85
|
+
export {
|
|
86
|
+
type BuilderProofRecipe,
|
|
87
|
+
proofHashesFromBuilderRecipe,
|
|
88
|
+
} from './proof-recipe.ts'
|
|
89
|
+
export {
|
|
90
|
+
getHashFromProof,
|
|
91
|
+
hashProofClaimParams,
|
|
92
|
+
hashRequestSpec,
|
|
93
|
+
type HttpProofParams,
|
|
94
|
+
type ProofRequestSpec,
|
|
95
|
+
} from './proof-hash.ts'
|
|
96
|
+
export {
|
|
97
|
+
builderTeeBindingPayload,
|
|
98
|
+
BUILDER_TEE_BINDING_DOMAIN,
|
|
99
|
+
createBuilderTeeBinding,
|
|
100
|
+
createLegacyTeeNonceContext,
|
|
101
|
+
deriveLegacySignatureTeeNonce,
|
|
102
|
+
deriveLegacyTeeNonce,
|
|
103
|
+
LEGACY_TEE_ATTESTATION_VERSION,
|
|
104
|
+
LEGACY_TEE_NONCE_DOMAIN,
|
|
105
|
+
legacyApplicationIdFromPrivateKey,
|
|
106
|
+
signBuilderTeeBinding,
|
|
107
|
+
type BuilderTeeBinding,
|
|
108
|
+
type BuilderTeeBindingPayload,
|
|
109
|
+
type LegacyTeeNonceContext,
|
|
110
|
+
type LegacyTeeNonceData,
|
|
111
|
+
type LegacyTeeNonceMode,
|
|
112
|
+
} from './tee-binding.ts'
|
|
113
|
+
export {
|
|
114
|
+
KNOWN_VERIFICATION_CLIENTS,
|
|
115
|
+
type KnownVerificationClientName,
|
|
116
|
+
type KnownVerificationClientUrlMap,
|
|
117
|
+
} from './verification-clients.ts'
|
package/src/jwks.ts
CHANGED
|
@@ -14,6 +14,21 @@ export interface Jwk {
|
|
|
14
14
|
kid?: string
|
|
15
15
|
alg?: string
|
|
16
16
|
use?: string
|
|
17
|
+
key_ops?: string[]
|
|
18
|
+
/** Restricts how Builder consumers may use this key. */
|
|
19
|
+
'x-reclaim-purpose'?: 'verification-result' | 'attestor-proof'
|
|
20
|
+
/** Verification Client that owns a result-signing key. */
|
|
21
|
+
'x-reclaim-verification-client-id'?: string
|
|
22
|
+
/** Verification Clients that share a built-in result-signing key. */
|
|
23
|
+
'x-reclaim-verification-client-ids'?: string[]
|
|
24
|
+
/** Attestor proofs use EIP-191 rather than JOSE ES256K. */
|
|
25
|
+
'x-reclaim-signature-scheme'?: 'EIP-191'
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface ReclaimJwks {
|
|
29
|
+
keys: Jwk[]
|
|
30
|
+
/** Trusted EIP-191 attestor signer addresses (public-key hashes). */
|
|
31
|
+
'x-reclaim-attestors'?: string[]
|
|
17
32
|
}
|
|
18
33
|
|
|
19
34
|
/**
|
|
@@ -21,7 +36,15 @@ export interface Jwk {
|
|
|
21
36
|
* result-signing key). `kid` is the key's ETH address; `x`/`y` are the
|
|
22
37
|
* uncompressed public-key coordinates. Empty set when no key is configured.
|
|
23
38
|
*/
|
|
24
|
-
export function jwksFromPrivateKey(
|
|
39
|
+
export function jwksFromPrivateKey(
|
|
40
|
+
privateKeyHex?: string,
|
|
41
|
+
metadata: Pick<
|
|
42
|
+
Jwk,
|
|
43
|
+
'x-reclaim-purpose'
|
|
44
|
+
| 'x-reclaim-verification-client-id'
|
|
45
|
+
| 'x-reclaim-verification-client-ids'
|
|
46
|
+
> = {},
|
|
47
|
+
): { keys: Jwk[] } {
|
|
25
48
|
if(!privateKeyHex) {
|
|
26
49
|
return { keys: [] }
|
|
27
50
|
}
|
|
@@ -38,6 +61,8 @@ export function jwksFromPrivateKey(privateKeyHex?: string): { keys: Jwk[] } {
|
|
|
38
61
|
crv: 'secp256k1',
|
|
39
62
|
alg: 'ES256K',
|
|
40
63
|
use: 'sig',
|
|
64
|
+
key_ops: ['verify'],
|
|
65
|
+
...metadata,
|
|
41
66
|
kid: addressFromUncompressedPubkey(pub),
|
|
42
67
|
x: b64url(pub.subarray(1, 33)),
|
|
43
68
|
y: b64url(pub.subarray(33, 65)),
|
|
@@ -52,13 +77,35 @@ export async function fetchJwks(
|
|
|
52
77
|
fetchImpl: typeof fetch = fetch,
|
|
53
78
|
): Promise<Jwk[]> {
|
|
54
79
|
const base = iss.replace(/\/+$/, '')
|
|
55
|
-
|
|
80
|
+
return fetchJwksUrl(`${base}/.well-known/jwks.json`, fetchImpl)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Fetch a JWK set from an explicitly trusted registry URL. */
|
|
84
|
+
export async function fetchJwksUrl(
|
|
85
|
+
url: string,
|
|
86
|
+
fetchImpl: typeof fetch = fetch,
|
|
87
|
+
): Promise<Jwk[]> {
|
|
88
|
+
const jwks = await fetchReclaimJwks(url, fetchImpl)
|
|
89
|
+
return jwks.keys
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Fetch Builder's JWKS plus its namespaced attestor trust registry. */
|
|
93
|
+
export async function fetchReclaimJwks(
|
|
94
|
+
url: string,
|
|
95
|
+
fetchImpl: typeof fetch = fetch,
|
|
96
|
+
): Promise<ReclaimJwks> {
|
|
97
|
+
const res = await fetchImpl(url)
|
|
56
98
|
if(!res.ok) {
|
|
57
|
-
throw new Error(`JWKS fetch failed for ${
|
|
99
|
+
throw new Error(`JWKS fetch failed for ${url}: HTTP ${res.status}`)
|
|
58
100
|
}
|
|
59
101
|
|
|
60
|
-
const body = (await res.json()) as
|
|
61
|
-
return
|
|
102
|
+
const body = (await res.json()) as Partial<ReclaimJwks>
|
|
103
|
+
return {
|
|
104
|
+
keys: body.keys ?? [],
|
|
105
|
+
'x-reclaim-attestors': validateAttestorAddresses(
|
|
106
|
+
body['x-reclaim-attestors'] ?? [],
|
|
107
|
+
),
|
|
108
|
+
}
|
|
62
109
|
}
|
|
63
110
|
|
|
64
111
|
/** Find the key matching `kid` (case-insensitive — kids are ETH addresses). */
|
|
@@ -67,6 +114,59 @@ export function selectKey(keys: Jwk[], kid: string): Jwk | undefined {
|
|
|
67
114
|
return keys.find((k) => (k.kid ?? '').toLowerCase() === want)
|
|
68
115
|
}
|
|
69
116
|
|
|
117
|
+
/** Select a result key and enforce its Verification Client ownership. */
|
|
118
|
+
export function selectVerificationResultKey(
|
|
119
|
+
keys: Jwk[],
|
|
120
|
+
kid: string,
|
|
121
|
+
verificationClientId: string,
|
|
122
|
+
): Jwk | undefined {
|
|
123
|
+
return keys.find((key) => (
|
|
124
|
+
(key.kid ?? '').toLowerCase() === kid.toLowerCase()
|
|
125
|
+
&& key['x-reclaim-purpose'] !== 'attestor-proof'
|
|
126
|
+
&& belongsToVerificationClient(key, verificationClientId)
|
|
127
|
+
))
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** Return ETH addresses from keys explicitly registered for attestor proofs. */
|
|
131
|
+
export function trustedAttestorAddresses(keys: Jwk[]): string[] {
|
|
132
|
+
return keys
|
|
133
|
+
.filter((key) => key['x-reclaim-purpose'] === 'attestor-proof')
|
|
134
|
+
.map((key) => {
|
|
135
|
+
if(
|
|
136
|
+
key.kty !== 'EC'
|
|
137
|
+
|| key.crv !== 'secp256k1'
|
|
138
|
+
|| key.use !== 'sig'
|
|
139
|
+
|| key['x-reclaim-signature-scheme'] !== 'EIP-191'
|
|
140
|
+
|| (key.key_ops && !key.key_ops.includes('verify'))
|
|
141
|
+
) {
|
|
142
|
+
throw new Error('invalid attestor JWK metadata')
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const address = addressFromUncompressedPubkey(
|
|
146
|
+
hexPublicKeyBytes(jwkToUncompressedPublicKey(key)),
|
|
147
|
+
).toLowerCase()
|
|
148
|
+
if((key.kid ?? '').toLowerCase() !== address) {
|
|
149
|
+
throw new Error(
|
|
150
|
+
'attestor JWK kid does not match its ETH public key',
|
|
151
|
+
)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return address
|
|
155
|
+
})
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** Validate and normalize the JWKS address-only trust extension. */
|
|
159
|
+
export function validateAttestorAddresses(addresses: string[]): string[] {
|
|
160
|
+
return [...new Set(addresses.map((address) => {
|
|
161
|
+
const normalized = address.trim().toLowerCase()
|
|
162
|
+
if(!/^0x[0-9a-f]{40}$/.test(normalized)) {
|
|
163
|
+
throw new Error('invalid attestor address in JWKS trust registry')
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return normalized
|
|
167
|
+
}))]
|
|
168
|
+
}
|
|
169
|
+
|
|
70
170
|
/**
|
|
71
171
|
* Reconstruct the uncompressed secp256k1 public key (`0x04` + X + Y) from an
|
|
72
172
|
* EC JWK's base64url `x`/`y` coordinates, for signature verification.
|
|
@@ -84,3 +184,26 @@ export function jwkToUncompressedPublicKey(jwk: Jwk): string {
|
|
|
84
184
|
out.set(y, 33)
|
|
85
185
|
return '0x' + bytesToHex(out)
|
|
86
186
|
}
|
|
187
|
+
|
|
188
|
+
function belongsToVerificationClient(
|
|
189
|
+
key: Jwk,
|
|
190
|
+
verificationClientId: string,
|
|
191
|
+
): boolean {
|
|
192
|
+
const one = key['x-reclaim-verification-client-id']
|
|
193
|
+
const many = key['x-reclaim-verification-client-ids']
|
|
194
|
+
if(one) {
|
|
195
|
+
return one === verificationClientId
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if(many) {
|
|
199
|
+
return many.includes(verificationClientId)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Untagged self-hosted JWKS remain compatible because their URL is itself
|
|
203
|
+
// selected from the registered Verification Client record.
|
|
204
|
+
return key['x-reclaim-purpose'] !== 'verification-result'
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function hexPublicKeyBytes(value: string) {
|
|
208
|
+
return Uint8Array.from(Buffer.from(value.slice(2), 'hex'))
|
|
209
|
+
}
|