@digitalbazaar/oid4-client 4.3.0 → 5.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.
- package/lib/authorizationRequest.js +461 -0
- package/lib/authorizationResponse.js +337 -0
- package/lib/convert.js +430 -0
- package/lib/index.js +3 -2
- package/lib/oid4vp.js +20 -836
- package/lib/util.js +66 -1
- package/lib/x509.js +61 -0
- package/package.json +8 -8
|
@@ -0,0 +1,461 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) 2023-2025 Digital Bazaar, Inc. All rights reserved.
|
|
3
|
+
*/
|
|
4
|
+
import {
|
|
5
|
+
assert, assertOptional, base64Encode, createNamedError, fetchJSON, selectJwk
|
|
6
|
+
} from './util.js';
|
|
7
|
+
import {decodeJwt, importX509, jwtVerify} from 'jose';
|
|
8
|
+
import {
|
|
9
|
+
hasDomainSubjectAltName, parseCertificateChain, verifyCertificateChain
|
|
10
|
+
} from './x509.js';
|
|
11
|
+
|
|
12
|
+
const REQUIRED_SIGNED_AUTHZ_REQUEST_CLIENT_ID_SCHEMES = new Set([
|
|
13
|
+
'x509_san_dns', 'x509_hash', 'did', 'decentralized_identifier'
|
|
14
|
+
]);
|
|
15
|
+
const SUPPORTED_CLIENT_ID_SCHEMES = new Set([
|
|
16
|
+
'redirect_uri',
|
|
17
|
+
'x509_san_dns', 'x509_hash', 'did', 'decentralized_identifier'
|
|
18
|
+
]);
|
|
19
|
+
|
|
20
|
+
// get an authorization request from a verifier
|
|
21
|
+
export async function get({
|
|
22
|
+
url, getTrustedCertificates, getVerificationKey, agent
|
|
23
|
+
} = {}) {
|
|
24
|
+
try {
|
|
25
|
+
assert(url, 'url', 'string');
|
|
26
|
+
|
|
27
|
+
let authorizationRequest;
|
|
28
|
+
let requestUrl;
|
|
29
|
+
let expectedClientId;
|
|
30
|
+
if(url.startsWith('https://')) {
|
|
31
|
+
// the request must be retrieved via HTTP
|
|
32
|
+
requestUrl = url;
|
|
33
|
+
} else {
|
|
34
|
+
// parse the request from the given URL
|
|
35
|
+
({authorizationRequest} = _parseOID4VPUrl({url}));
|
|
36
|
+
expectedClientId = authorizationRequest.client_id;
|
|
37
|
+
if(authorizationRequest.request_uri) {
|
|
38
|
+
requestUrl = authorizationRequest.request_uri;
|
|
39
|
+
}
|
|
40
|
+
// if whole request is passed by reference, then it MUST be a signed JWT
|
|
41
|
+
if(authorizationRequest.request) {
|
|
42
|
+
authorizationRequest = await _parseJwt({
|
|
43
|
+
jwt: authorizationRequest.request,
|
|
44
|
+
getTrustedCertificates,
|
|
45
|
+
getVerificationKey,
|
|
46
|
+
signatureRequired: true
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// fetch request if necessary...
|
|
52
|
+
let fetched = false;
|
|
53
|
+
let response;
|
|
54
|
+
let jwt;
|
|
55
|
+
if(requestUrl) {
|
|
56
|
+
fetched = true;
|
|
57
|
+
({
|
|
58
|
+
payload: authorizationRequest, response, jwt
|
|
59
|
+
} = await _fetch({
|
|
60
|
+
requestUrl, getTrustedCertificates, getVerificationKey, agent
|
|
61
|
+
}));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ensure authorization request is valid
|
|
65
|
+
validate({authorizationRequest, expectedClientId});
|
|
66
|
+
|
|
67
|
+
return {authorizationRequest, fetched, requestUrl, response, jwt};
|
|
68
|
+
} catch(cause) {
|
|
69
|
+
const message = cause.data?.error_description ?? cause.message;
|
|
70
|
+
throw createNamedError({
|
|
71
|
+
message: `Could not get authorization request: ${message}`,
|
|
72
|
+
name: 'OperationError',
|
|
73
|
+
cause
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function getClientIdScheme({authorizationRequest} = {}) {
|
|
79
|
+
return authorizationRequest.client_id_scheme ??
|
|
80
|
+
authorizationRequest.client_id?.split(':')[0];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function requestsFormat({authorizationRequest, format} = {}) {
|
|
84
|
+
/* e.g. presentation definition requesting an mdoc:
|
|
85
|
+
{
|
|
86
|
+
id: 'mdl-test-age-over-21',
|
|
87
|
+
input_descriptors: [{
|
|
88
|
+
id: 'org.iso.18013.5.1.mDL',
|
|
89
|
+
format: {
|
|
90
|
+
mso_mdoc: {
|
|
91
|
+
alg: ['ES256']
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}]
|
|
95
|
+
}
|
|
96
|
+
*/
|
|
97
|
+
return authorizationRequest.presentation_definition?.input_descriptors?.some(
|
|
98
|
+
e => e?.format?.[format]);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function usesClientIdScheme({authorizationRequest, scheme} = {}) {
|
|
102
|
+
if(Array.isArray(scheme)) {
|
|
103
|
+
return scheme.some(
|
|
104
|
+
scheme => usesClientIdScheme({authorizationRequest, scheme}));
|
|
105
|
+
}
|
|
106
|
+
return authorizationRequest?.client_id_scheme === scheme ||
|
|
107
|
+
authorizationRequest?.client_id?.startsWith(`${scheme}:`);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export async function validate({authorizationRequest, expectedClientId}) {
|
|
111
|
+
// validate payload (expected authorization request)
|
|
112
|
+
const {
|
|
113
|
+
client_id,
|
|
114
|
+
client_id_scheme,
|
|
115
|
+
client_metadata,
|
|
116
|
+
nonce,
|
|
117
|
+
presentation_definition,
|
|
118
|
+
response_mode,
|
|
119
|
+
scope
|
|
120
|
+
} = authorizationRequest;
|
|
121
|
+
assert(client_id, 'client_id', 'string');
|
|
122
|
+
// ensure `client_id` matches expected client ID
|
|
123
|
+
if(expectedClientId !== undefined && client_id !== expectedClientId) {
|
|
124
|
+
throw createNamedError({
|
|
125
|
+
message: '"client_id" in fetched request does not match authorization ' +
|
|
126
|
+
'request URL parameter.',
|
|
127
|
+
name: 'DataError'
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
assert(nonce, 'nonce', 'string');
|
|
131
|
+
assertOptional(client_id_scheme, 'client_id_scheme', 'string');
|
|
132
|
+
assertOptional(client_metadata, 'client_metadata', 'object');
|
|
133
|
+
assertOptional(
|
|
134
|
+
presentation_definition, 'presentation_definition', 'object');
|
|
135
|
+
assertOptional(response_mode, 'response_mode', 'string');
|
|
136
|
+
assertOptional(scope, 'scope', 'string');
|
|
137
|
+
// FIXME: further validate `presentation_definition`
|
|
138
|
+
// FIXME: further validate `client_metadata`
|
|
139
|
+
|
|
140
|
+
// Note: This implementation requires client ID scheme to be one of:
|
|
141
|
+
// `redirect_uri`, `x509_san_dns`, `x509_hash`, `did`, or
|
|
142
|
+
// `decentralized_identifier`
|
|
143
|
+
const scheme = getClientIdScheme({authorizationRequest});
|
|
144
|
+
if(!SUPPORTED_CLIENT_ID_SCHEMES.has(scheme)) {
|
|
145
|
+
const schemes = [...SUPPORTED_CLIENT_ID_SCHEMES].join(', ');
|
|
146
|
+
throw createNamedError({
|
|
147
|
+
message: `Unsupported client ID scheme "${scheme}"; ` +
|
|
148
|
+
`supported schemes are: ${schemes}.'`,
|
|
149
|
+
name: 'NotSupportedError'
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Note: This implementation requires `response_mode` to be `direct_post`
|
|
154
|
+
// or `direct_post.jwt`; no other modes are supported.
|
|
155
|
+
if(!(response_mode === 'direct_post' ||
|
|
156
|
+
response_mode === 'direct_post.jwt')) {
|
|
157
|
+
throw createNamedError({
|
|
158
|
+
message: 'Only "direct_post" and "direct_post.jwt" ' +
|
|
159
|
+
'response modes are supported.',
|
|
160
|
+
name: 'NotSupportedError'
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// `direct_post.jwt` response mode requires encryption; ensure the client
|
|
165
|
+
// meta data has the necessary parameters
|
|
166
|
+
if(response_mode === 'direct_post.jwt') {
|
|
167
|
+
const {
|
|
168
|
+
authorization_encrypted_response_alg = 'ECDH-ES',
|
|
169
|
+
authorization_encrypted_response_enc = 'A256GCM',
|
|
170
|
+
jwks
|
|
171
|
+
} = client_metadata;
|
|
172
|
+
if(authorization_encrypted_response_alg !== 'ECDH-ES') {
|
|
173
|
+
throw createNamedError({
|
|
174
|
+
message: `"${authorization_encrypted_response_alg}" is not ` +
|
|
175
|
+
'supported; only "ECDH-ES" is supported.',
|
|
176
|
+
name: 'NotSupportedError'
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
if(authorization_encrypted_response_enc !== 'A256GCM') {
|
|
180
|
+
throw createNamedError({
|
|
181
|
+
message: `"${authorization_encrypted_response_enc}" is not ` +
|
|
182
|
+
'supported; only "A256GCM" is supported.',
|
|
183
|
+
name: 'NotSupportedError'
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
if(!selectJwk({
|
|
187
|
+
keys: jwks?.keys, alg: 'ECDH-ES', kty: 'EC', crv: 'P-256', use: 'enc'
|
|
188
|
+
})) {
|
|
189
|
+
throw createNamedError({
|
|
190
|
+
message: 'No matching key found for "ECDH-ES" in client meta data ' +
|
|
191
|
+
'JWK key set.',
|
|
192
|
+
name: 'NotFoundError'
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
async function _checkClientIdSchemeRequirements({
|
|
199
|
+
clientIdScheme, authorizationRequest, protectedHeader,
|
|
200
|
+
certificatePublicKey, getTrustedCertificates
|
|
201
|
+
}) {
|
|
202
|
+
// if `x509_san_dns` or `x509_hash`...
|
|
203
|
+
if(clientIdScheme.startsWith('x509_')) {
|
|
204
|
+
// `x5c` MUST be present where the public key is in the leaf cert (which is
|
|
205
|
+
// the first in the chain per RFC 7515:
|
|
206
|
+
// https://datatracker.ietf.org/doc/html/rfc7515#section-4.1.6)
|
|
207
|
+
if(!certificatePublicKey) {
|
|
208
|
+
throw createNamedError({
|
|
209
|
+
message:
|
|
210
|
+
'No "x5c" header with an acceptable public key found; client ID ' +
|
|
211
|
+
'schemes starting with "x509_" must use the "x5c" header ' +
|
|
212
|
+
'to provide an X.509 certificate with the public key for verifying ' +
|
|
213
|
+
'the request.',
|
|
214
|
+
name: 'DataError'
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// ensure trusted certs can be retrieved
|
|
219
|
+
if(typeof getTrustedCertificates !== 'function') {
|
|
220
|
+
throw createNamedError({
|
|
221
|
+
message:
|
|
222
|
+
'No "getTrustedCertificates" function provided; client ID schemes ' +
|
|
223
|
+
'starting with "x509_" require such a function to be provided ' +
|
|
224
|
+
'that will return the certificates that are to be trusted ' +
|
|
225
|
+
'when verifying X.509 certificate chains.',
|
|
226
|
+
name: 'DataError'
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// get trusted certificates for `x5c` and verify chain
|
|
231
|
+
const {x5c} = protectedHeader;
|
|
232
|
+
const chain = parseCertificateChain({x5c});
|
|
233
|
+
const trustedCertificates = await getTrustedCertificates({
|
|
234
|
+
x5c, chain, authorizationRequest
|
|
235
|
+
});
|
|
236
|
+
const verifyResult = await verifyCertificateChain({
|
|
237
|
+
chain, trustedCertificates
|
|
238
|
+
});
|
|
239
|
+
if(!verifyResult.result) {
|
|
240
|
+
throw createNamedError({
|
|
241
|
+
message:
|
|
242
|
+
'Signed authorization request "x5c" certificate chain is invalid: ' +
|
|
243
|
+
verifyResult.resultMessage,
|
|
244
|
+
name: 'DataError'
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
let {client_id: clientId} = authorizationRequest;
|
|
249
|
+
clientId = clientId.startsWith(`${clientIdScheme}:`) ?
|
|
250
|
+
clientId.slice(clientIdScheme.length + 2) : clientId;
|
|
251
|
+
|
|
252
|
+
if(clientIdScheme === 'x509_san_dns') {
|
|
253
|
+
// `x509_san_dns` requires leaf cert to have a dNSName ("domain" type) in
|
|
254
|
+
// a subject alternative name field that matches the client_id
|
|
255
|
+
if(!hasDomainSubjectAltName({certificate: chain[0], name: clientId})) {
|
|
256
|
+
throw createNamedError({
|
|
257
|
+
message:
|
|
258
|
+
`Signed authorization request header "x5c" parameter's leaf ` +
|
|
259
|
+
'certificate does not have a DNS subject alternative name that ' +
|
|
260
|
+
'matches the client ID as required by the used "x509_san_dns" ' +
|
|
261
|
+
'client ID scheme.',
|
|
262
|
+
name: 'DataError'
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
/* Note: The current implementation does not support `redirect_uri` as
|
|
266
|
+
a `response_mode`. If a future revision adds support for this, then
|
|
267
|
+
when `x509_san_dns` is used, the `redirect_uri` value must match the
|
|
268
|
+
FQDN of the `client_id` unless an allow list for overrides is passed
|
|
269
|
+
and it includes the client ID. */
|
|
270
|
+
} else if(clientIdScheme === 'x509_hash') {
|
|
271
|
+
// `x509_hash:<base64url sha256-hash of DER leaf cert>`
|
|
272
|
+
const hash = base64Encode(await _sha256(chain[0].toBER()));
|
|
273
|
+
if(clientId !== hash) {
|
|
274
|
+
throw createNamedError({
|
|
275
|
+
message:
|
|
276
|
+
`The signed authorization request header "x5c" parameter's leaf ` +
|
|
277
|
+
`certificate's SHA-256 hash digest not match the client ID as ` +
|
|
278
|
+
'required by the used "x509_hash" client ID scheme.',
|
|
279
|
+
name: 'DataError'
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
} else if(
|
|
284
|
+
clientIdScheme === 'did' || clientIdScheme === 'decentralized_identifier') {
|
|
285
|
+
// "kid" header must reference a verification method controlled by the
|
|
286
|
+
// DID expressed in client ID; this is checked by default when a proper
|
|
287
|
+
// DID resolver is used in `getVerificationKey` but this check provides
|
|
288
|
+
// a partial additional sanity check
|
|
289
|
+
let {client_id: clientId} = authorizationRequest;
|
|
290
|
+
clientId = clientId.startsWith('decentralized_identifier:did:') ?
|
|
291
|
+
clientId.slice('decentralized_identifier:'.length + 1) : clientId;
|
|
292
|
+
if(!protectedHeader?.kid?.startsWith(clientId + '#')) {
|
|
293
|
+
throw createNamedError({
|
|
294
|
+
message:
|
|
295
|
+
`The signed authorization request header "kid" parameter's value ` +
|
|
296
|
+
'does not reference a verification method controlled by the DID ' +
|
|
297
|
+
'identified in the client ID as required by the used ' +
|
|
298
|
+
'"did"/"decentralized_identifier" client ID scheme.',
|
|
299
|
+
name: 'DataError'
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
async function _fetch({
|
|
306
|
+
requestUrl, getTrustedCertificates, getVerificationKey, agent
|
|
307
|
+
}) {
|
|
308
|
+
// FIXME: every `fetchJSON` call needs to use a block list or other
|
|
309
|
+
// protections to prevent a confused deputy attack where the `requestUrl`
|
|
310
|
+
// accesses a location it should not, e.g., a URL `localhost` is used when
|
|
311
|
+
// it shouldn't be
|
|
312
|
+
const response = await fetchJSON({url: requestUrl, agent});
|
|
313
|
+
|
|
314
|
+
// parse payload from response data...
|
|
315
|
+
const contentType = response.headers.get('content-type');
|
|
316
|
+
const jwt = await response.text();
|
|
317
|
+
|
|
318
|
+
// verify response is a JWT-secured authorization request
|
|
319
|
+
if(!(contentType.includes('application/oauth-authz-req+jwt') &&
|
|
320
|
+
typeof jwt === 'string')) {
|
|
321
|
+
throw createNamedError({
|
|
322
|
+
message: 'Authorization request content-type must be ' +
|
|
323
|
+
'"application/oauth-authz-req+jwt".',
|
|
324
|
+
name: 'DataError'
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// return parsed payload and original response
|
|
329
|
+
const payload = await _parseJwt({
|
|
330
|
+
jwt, getTrustedCertificates, getVerificationKey
|
|
331
|
+
});
|
|
332
|
+
return {payload, response, jwt};
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function _get(sp, name) {
|
|
336
|
+
const value = sp.get(name);
|
|
337
|
+
return value === null ? undefined : value;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function _importPublicKeyFromX5c({x5c}) {
|
|
341
|
+
if(x5c?.[0]) {
|
|
342
|
+
const pem =
|
|
343
|
+
`-----BEGIN CERTIFICATE-----\n${x5c[0]}\n-----END CERTIFICATE-----`;
|
|
344
|
+
return importX509(pem, 'ES256');
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
async function _parseJwt({
|
|
349
|
+
jwt, getTrustedCertificates, getVerificationKey, signatureRequired
|
|
350
|
+
}) {
|
|
351
|
+
// parse unprotected payload and scheme from it
|
|
352
|
+
const payload = decodeJwt(jwt);
|
|
353
|
+
const clientIdScheme = getClientIdScheme({authorizationRequest: payload});
|
|
354
|
+
|
|
355
|
+
// check if a signature on the JWT is required:
|
|
356
|
+
// - `client_metadata.require_signed_request_object` is `true`, OR
|
|
357
|
+
// - `client_id_scheme` requires the JWT to be signed
|
|
358
|
+
signatureRequired = signatureRequired ||
|
|
359
|
+
payload.client_metadata?.require_signed_request_object === true ||
|
|
360
|
+
REQUIRED_SIGNED_AUTHZ_REQUEST_CLIENT_ID_SCHEMES.has(clientIdScheme);
|
|
361
|
+
if(!signatureRequired) {
|
|
362
|
+
// no signature required, just use the decoded payload
|
|
363
|
+
return payload;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// create callback function to handle key lookup; `getVerificationKey` may
|
|
367
|
+
// return a promise
|
|
368
|
+
let certificatePublicKey;
|
|
369
|
+
const getKey = async protectedHeader => {
|
|
370
|
+
// parse any `x5c` to get certificate public key and include that in
|
|
371
|
+
// `getVerificationKey` params
|
|
372
|
+
const {x5c} = protectedHeader;
|
|
373
|
+
certificatePublicKey = await _importPublicKeyFromX5c({x5c});
|
|
374
|
+
if(getVerificationKey) {
|
|
375
|
+
return getVerificationKey({
|
|
376
|
+
protectedHeader, certificatePublicKey,
|
|
377
|
+
clientIdScheme, authorizationRequest: payload
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
if(x5c) {
|
|
381
|
+
return certificatePublicKey;
|
|
382
|
+
}
|
|
383
|
+
_throwKeyNotFound(protectedHeader);
|
|
384
|
+
};
|
|
385
|
+
|
|
386
|
+
// verify the JWT
|
|
387
|
+
const verifyResult = await jwtVerify(jwt, getKey, {alg: 'ES256'});
|
|
388
|
+
const {payload: authorizationRequest, protectedHeader} = verifyResult;
|
|
389
|
+
|
|
390
|
+
// ensure all client ID scheme requirements are met
|
|
391
|
+
await _checkClientIdSchemeRequirements({
|
|
392
|
+
clientIdScheme, authorizationRequest, protectedHeader,
|
|
393
|
+
certificatePublicKey, getTrustedCertificates
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
return verifyResult.payload;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function _parseOID4VPUrl({url}) {
|
|
400
|
+
const {searchParams} = new URL(url);
|
|
401
|
+
const request = _get(searchParams, 'request');
|
|
402
|
+
const request_uri = _get(searchParams, 'request_uri');
|
|
403
|
+
const response_type = _get(searchParams, 'response_type');
|
|
404
|
+
const response_mode = _get(searchParams, 'response_mode');
|
|
405
|
+
const presentation_definition = _get(
|
|
406
|
+
searchParams, 'presentation_definition');
|
|
407
|
+
const client_id = _get(searchParams, 'client_id');
|
|
408
|
+
const client_id_scheme = _get(searchParams, 'client_id_scheme');
|
|
409
|
+
const client_metadata = _get(searchParams, 'client_metadata');
|
|
410
|
+
const nonce = _get(searchParams, 'nonce');
|
|
411
|
+
const response_uri = _get(searchParams, 'response_uri');
|
|
412
|
+
const state = _get(searchParams, 'state');
|
|
413
|
+
if(request && request_uri) {
|
|
414
|
+
const error = createNamedError({
|
|
415
|
+
message: 'Only one of "request" and "request_uri" may be present.',
|
|
416
|
+
name: 'DataError'
|
|
417
|
+
});
|
|
418
|
+
error.url = url;
|
|
419
|
+
throw error;
|
|
420
|
+
}
|
|
421
|
+
if(!(request || request_uri)) {
|
|
422
|
+
if(response_type !== 'vp_token') {
|
|
423
|
+
throw new Error(`Unsupported "response_type", "${response_type}".`);
|
|
424
|
+
}
|
|
425
|
+
if(!(response_mode === 'direct_post' ||
|
|
426
|
+
response_mode === 'direct_post.jwt')) {
|
|
427
|
+
throw new Error(`Unsupported "response_type", "${response_type}".`);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
const authorizationRequest = {
|
|
431
|
+
request,
|
|
432
|
+
request_uri,
|
|
433
|
+
response_type,
|
|
434
|
+
response_mode,
|
|
435
|
+
presentation_definition: presentation_definition &&
|
|
436
|
+
JSON.parse(presentation_definition),
|
|
437
|
+
client_id,
|
|
438
|
+
client_id_scheme,
|
|
439
|
+
client_metadata: client_metadata && JSON.parse(client_metadata),
|
|
440
|
+
response_uri,
|
|
441
|
+
nonce,
|
|
442
|
+
state
|
|
443
|
+
};
|
|
444
|
+
return {authorizationRequest};
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
async function _sha256(data) {
|
|
448
|
+
if(typeof data === 'string') {
|
|
449
|
+
data = new TextEncoder().encode(data);
|
|
450
|
+
}
|
|
451
|
+
const algorithm = {name: 'SHA-256'};
|
|
452
|
+
return new Uint8Array(await crypto.subtle.digest(algorithm, data));
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
function _throwKeyNotFound(protectedHeader) {
|
|
456
|
+
const error = new Error(
|
|
457
|
+
'Could not verify signed authorization request; ' +
|
|
458
|
+
`public key "${protectedHeader.kid}" not found.`);
|
|
459
|
+
error.name = 'NotFoundError';
|
|
460
|
+
throw error;
|
|
461
|
+
}
|