@bedrock/vc-delivery 7.19.1 → 7.19.2

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.
@@ -2,12 +2,11 @@
2
2
  * Copyright (c) 2022-2026 Digital Bazaar, Inc. All rights reserved.
3
3
  */
4
4
  import * as bedrock from '@bedrock/core';
5
- import {AsymmetricKey, KmsClient} from '@digitalbazaar/webkms-client';
6
- import {exportJWK, generateKeyPair, importJWK} from 'jose';
5
+ import {exportJWK, generateKeyPair} from 'jose';
7
6
  import {oid4vp, signJWT} from '@digitalbazaar/oid4-client';
7
+ import {createDcApiRequest} from './mdoc.js';
8
8
  import {getClientBaseUrl} from './clientProfiles.js';
9
- import {getZcapClient} from '../helpers.js';
10
- import {httpsAgent} from '@bedrock/https-agent';
9
+ import {getRequestSigningParameters} from './requestSigning.js';
11
10
  import {randomUUID} from 'node:crypto';
12
11
 
13
12
  const {util: {BedrockError}} = bedrock;
@@ -112,6 +111,13 @@ export async function create({
112
111
  export async function encode({
113
112
  workflow, clientProfile, authorizationRequest
114
113
  } = {}) {
114
+ if(authorizationRequest.response_mode === 'dc_api') {
115
+ // create mdoc device request for DC API Annex C
116
+ return createDcApiRequest({
117
+ workflow, clientProfile, authorizationRequest
118
+ });
119
+ }
120
+
115
121
  // if required, construct authz request as signed JWT
116
122
  if(authorizationRequest.client_metadata.require_signed_request_object) {
117
123
  return _createJwt({workflow, clientProfile, authorizationRequest});
@@ -234,64 +240,15 @@ async function _createClientMetaData({
234
240
 
235
241
  async function _createJwt({workflow, clientProfile, authorizationRequest}) {
236
242
  try {
237
- // create zcap client
238
- const {zcapClient, zcaps} = await getZcapClient({workflow});
243
+ // get signing params
244
+ const {signer, kid, alg} = await getRequestSigningParameters({
245
+ workflow, clientProfile
246
+ });
239
247
 
240
- // get any `x5c` and either `privateKeyJwk` or the zcap to use to sign the
241
- // authz request via the client profile
248
+ // get any `x5c` to include
242
249
  const {
243
- authorizationRequestSigningParameters: {privateKeyJwk, x5c} = {},
244
- zcapReferenceIds: {signAuthorizationRequest: refId} = {}
250
+ authorizationRequestSigningParameters: {x5c} = {},
245
251
  } = clientProfile;
246
- if(privateKeyJwk === undefined && refId === undefined) {
247
- throw new BedrockError(
248
- 'The OID4VP client profile does not specify the private key or ' +
249
- 'capability in the workflow configuration to use to sign ' +
250
- 'authorization requests.', {
251
- name: 'DataError',
252
- details: {httpStatusCode: 500, public: true}
253
- });
254
- }
255
-
256
- // code assumed ES256 (ECDSA P-256 + SHA-256) support only
257
- const alg = 'ES256';
258
- let kid;
259
- let signer;
260
- if(privateKeyJwk) {
261
- kid = privateKeyJwk.kid;
262
-
263
- // import `privateKeyJwk`
264
- const privateKey = await importJWK(privateKeyJwk, alg);
265
-
266
- // create signer API
267
- const algorithm = {name: 'ECDSA', hash: {name: 'SHA-256'}};
268
- signer = {
269
- async sign({data}) {
270
- return new Uint8Array(
271
- await crypto.subtle.sign(algorithm, privateKey, data));
272
- }
273
- };
274
- } else {
275
- const capability = zcaps[refId];
276
- if(capability === undefined) {
277
- throw new BedrockError(
278
- 'The capability specified by the OID4VP client profile for signing ' +
279
- 'authorization requests was not found in the workflow ' +
280
- 'configuration.', {
281
- name: 'DataError',
282
- details: {httpStatusCode: 500, public: true}
283
- });
284
- }
285
-
286
- // create a WebKMS `signer` interface
287
- const {invocationSigner} = zcapClient;
288
- const kmsClient = new KmsClient({httpsAgent});
289
- signer = await AsymmetricKey.fromCapability({
290
- capability, invocationSigner, kmsClient
291
- });
292
- const keyDescription = await signer.getKeyDescription();
293
- kid = keyDescription.id;
294
- }
295
252
 
296
253
  // create the JWT payload and header to be signed
297
254
  const payload = {...authorizationRequest};
@@ -80,7 +80,7 @@ export async function parse({
80
80
  function _getDecryptParameters({
81
81
  exchange, clientProfileId, authorizationRequest, mdocOptions
82
82
  }) {
83
- // get private key agreement keys in JWT format
83
+ // get private key agreement keys in JWK format
84
84
  const {keyAgreementKeyPairs} = exchange.secrets?.oid4vp?.clientProfiles
85
85
  ?.[clientProfileId ?? 'default'] ?? {};
86
86
  const keys = keyAgreementKeyPairs.map(({privateKeyJwk}) => privateKeyJwk);
package/lib/oid4/http.js CHANGED
@@ -405,18 +405,24 @@ async function _handleOid4vpAuthzRequest(req, res) {
405
405
  // returned in response; presently any wallet metadata is ignored
406
406
  const {
407
407
  authorizationRequest,
408
- clientProfile
408
+ clientProfile,
409
+ exchange
409
410
  } = await oid4vp.getAuthorizationRequest({req, clientProfileId});
410
411
  const {config: workflow} = req.serviceObject;
411
412
  result = await oid4vp.encodeAuthorizationRequest({
412
- workflow, clientProfile, authorizationRequest,
413
+ workflow, exchange,
414
+ clientProfileId, clientProfile, authorizationRequest,
413
415
  requestMethod: req.method.toLowerCase()
414
416
  });
415
- res.set('content-type', 'application/oauth-authz-req+jwt');
417
+ if(authorizationRequest.response_mode === 'dc_api') {
418
+ res.json(result);
419
+ } else {
420
+ res.set('content-type', 'application/oauth-authz-req+jwt');
421
+ res.send(result);
422
+ }
416
423
  } catch(error) {
417
424
  return _sendOID4Error({res, error});
418
425
  }
419
- res.send(result);
420
426
  }
421
427
 
422
428
  async function _handleOid4vpAuthzResponse(req, res) {
@@ -0,0 +1,112 @@
1
+ /*
2
+ * Copyright (c) 2025-2026 Digital Bazaar, Inc.
3
+ */
4
+ import {CoseKey, IsoMdocDcApi} from '@owf/mdoc';
5
+ import {getRequestSigningParameters} from './requestSigning.js';
6
+ import {selectJwk} from '@digitalbazaar/oid4-client';
7
+
8
+ const TEXT_ENCODER = new TextEncoder();
9
+
10
+ export async function createDcApiRequest({
11
+ workflow, clientProfile, authorizationRequest
12
+ } = {}) {
13
+ return _createAnnexCDcApiRequest({
14
+ workflow, clientProfile, authorizationRequest
15
+ });
16
+ }
17
+
18
+ async function _createAnnexCDcApiRequest({
19
+ workflow, clientProfile, authorizationRequest
20
+ }) {
21
+ // get recipient public key from authz request
22
+ const keys = authorizationRequest.client_metadata.jwks?.keys ?? [];
23
+ // these parameters are only ones supported Annex C for encryption key pair
24
+ const recipientPublicJwk = selectJwk({
25
+ keys, alg: 'ECDH-ES', kty: 'EC', crv: 'P-256'
26
+ });
27
+ const recipientPublicKey = CoseKey.fromJwk(recipientPublicJwk);
28
+
29
+ // get signing params
30
+ const {signer} = await getRequestSigningParameters({
31
+ workflow, clientProfile
32
+ });
33
+
34
+ // get `x5c` to include in certificate chain
35
+ const {
36
+ authorizationRequestSigningParameters: {x5c} = {},
37
+ } = clientProfile;
38
+ const certificateChain = x5c.map(b64 => Buffer.from(b64, 'base64'));
39
+
40
+ // create Annex C DC API mdoc device request
41
+ const {dcql_query: dcqlQuery} = authorizationRequest;
42
+ const {request} = await IsoMdocDcApi.createRequest({
43
+ version: '1.1',
44
+ nonce: TEXT_ENCODER.encode(authorizationRequest.nonce),
45
+ docRequests: _createDocRequests({dcqlQuery}),
46
+ recipientPublicKey,
47
+ readerAuth: {
48
+ signingKey: CoseKey.fromJwk({
49
+ kid: signer.id,
50
+ alg: 'ES256',
51
+ kty: 'EC',
52
+ crv: 'P-256'
53
+ }),
54
+ certificateChain,
55
+ origin: authorizationRequest.expected_origins[0]
56
+ }
57
+ }, _createMdocContext({signer}));
58
+
59
+ return {
60
+ meta: {authorizationRequest},
61
+ request: {
62
+ protocol: 'org-iso-mdoc',
63
+ data: request
64
+ }
65
+ };
66
+ }
67
+
68
+ function _createDocRequests({dcqlQuery} = {}) {
69
+ // process all `mso_mdoc` queries as a separate doc request
70
+ const credentials = dcqlQuery?.credentials
71
+ ?.filter(c => c?.format === 'mso_mdoc') ?? [];
72
+ return credentials.map(c => {
73
+ const docType = c.meta?.doctype_value;
74
+ const namespaces = new Map();
75
+ const claims = c.claims ?? [];
76
+ for(const claim of claims) {
77
+ const path = claim.path;
78
+ if(!(Array.isArray(path) && path.length >= 2)) {
79
+ continue;
80
+ }
81
+ const [namespace, field] = path;
82
+ let fields = namespaces.get(namespace);
83
+ if(!fields) {
84
+ namespaces.set(namespace, fields = new Map());
85
+ }
86
+ fields.set(field, true);
87
+ }
88
+ return {docType, namespaces};
89
+ });
90
+ }
91
+
92
+ function _createMdocContext({signer}) {
93
+ const crypto = globalThis.crypto;
94
+ return {
95
+ crypto: {
96
+ async digest({digestAlgorithm, bytes}) {
97
+ const digest = await crypto.subtle.digest(digestAlgorithm, bytes);
98
+ return new Uint8Array(digest);
99
+ },
100
+ random(length) {
101
+ return crypto.getRandomValues(new Uint8Array(length));
102
+ }
103
+ },
104
+ cose: {
105
+ sign1: {
106
+ async sign({toBeSigned}) {
107
+ return signer.sign({data: toBeSigned});
108
+ }
109
+ }
110
+ }
111
+ };
112
+ }
@@ -107,15 +107,21 @@ export async function getOID4VPProtocols({workflow, exchange, step}) {
107
107
  } = await _getOrCreateStepAuthorizationRequest({
108
108
  workflow, exchange, clientProfileId, clientProfile, step
109
109
  });
110
- const {client_id, request_uri_method} = authorizationRequest;
111
- const searchParams = new URLSearchParams({
112
- client_id,
113
- request_uri: `${clientBaseUrl}/authorization/request`
114
- });
115
- if(request_uri_method && version !== 'OID4VP-draft18') {
116
- searchParams.set('request_uri_method', request_uri_method);
110
+ if(authorizationRequest.response_mode === 'dc_api') {
111
+ // use `request_uri` only
112
+ protocols[name] = `${clientBaseUrl}/authorization/request`;
113
+ } else {
114
+ // build OID4VP-style URL
115
+ const {client_id, request_uri_method} = authorizationRequest;
116
+ const searchParams = new URLSearchParams({
117
+ client_id,
118
+ request_uri: `${clientBaseUrl}/authorization/request`
119
+ });
120
+ if(request_uri_method && version !== 'OID4VP-draft18') {
121
+ searchParams.set('request_uri_method', request_uri_method);
122
+ }
123
+ protocols[name] = `${scheme}://?${searchParams}`;
117
124
  }
118
- protocols[name] = `${scheme}://?${searchParams}`;
119
125
  }
120
126
  return protocols;
121
127
  }
@@ -0,0 +1,84 @@
1
+ /*!
2
+ * Copyright (c) 2022-2026 Digital Bazaar, Inc. All rights reserved.
3
+ */
4
+ import * as bedrock from '@bedrock/core';
5
+ import {AsymmetricKey, KmsClient} from '@digitalbazaar/webkms-client';
6
+ import {getZcapClient} from '../helpers.js';
7
+ import {httpsAgent} from '@bedrock/https-agent';
8
+ import {importJWK} from 'jose';
9
+
10
+ const {util: {BedrockError}} = bedrock;
11
+
12
+ export async function getRequestSigningParameters({
13
+ workflow, clientProfile
14
+ } = {}) {
15
+ try {
16
+ // create zcap client
17
+ const {zcapClient, zcaps} = await getZcapClient({workflow});
18
+
19
+ // get any `x5c` and either `privateKeyJwk` or the zcap to use to sign the
20
+ // authz request via the client profile
21
+ const {
22
+ authorizationRequestSigningParameters: {privateKeyJwk} = {},
23
+ zcapReferenceIds: {signAuthorizationRequest: refId} = {}
24
+ } = clientProfile;
25
+ if(privateKeyJwk === undefined && refId === undefined) {
26
+ throw new BedrockError(
27
+ 'The OID4VP client profile does not specify the private key or ' +
28
+ 'capability in the workflow configuration to use to sign ' +
29
+ 'authorization requests.', {
30
+ name: 'DataError',
31
+ details: {httpStatusCode: 500, public: true}
32
+ });
33
+ }
34
+
35
+ // code assumed ES256 (ECDSA P-256 + SHA-256) support only
36
+ const alg = 'ES256';
37
+ let kid;
38
+ let signer;
39
+ if(privateKeyJwk) {
40
+ kid = privateKeyJwk.kid;
41
+
42
+ // import `privateKeyJwk`
43
+ const privateKey = await importJWK(privateKeyJwk, alg);
44
+
45
+ // create signer API
46
+ const algorithm = {name: 'ECDSA', hash: {name: 'SHA-256'}};
47
+ signer = {
48
+ async sign({data}) {
49
+ return new Uint8Array(
50
+ await crypto.subtle.sign(algorithm, privateKey, data));
51
+ }
52
+ };
53
+ } else {
54
+ const capability = zcaps[refId];
55
+ if(capability === undefined) {
56
+ throw new BedrockError(
57
+ 'The capability specified by the OID4VP client profile for signing ' +
58
+ 'authorization requests was not found in the workflow ' +
59
+ 'configuration.', {
60
+ name: 'DataError',
61
+ details: {httpStatusCode: 500, public: true}
62
+ });
63
+ }
64
+
65
+ // create a WebKMS `signer` interface
66
+ const {invocationSigner} = zcapClient;
67
+ const kmsClient = new KmsClient({httpsAgent});
68
+ signer = await AsymmetricKey.fromCapability({
69
+ capability, invocationSigner, kmsClient
70
+ });
71
+ const keyDescription = await signer.getKeyDescription();
72
+ kid = keyDescription.id;
73
+ }
74
+
75
+ return {signer, privateKeyJwk, kid, alg};
76
+ } catch(cause) {
77
+ throw new BedrockError(
78
+ `Could not get request signing parameters: ${cause.message}`, {
79
+ name: cause instanceof BedrockError ? cause.name : 'OperationError',
80
+ cause,
81
+ details: {httpStatusCode: 500, public: true}
82
+ });
83
+ }
84
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bedrock/vc-delivery",
3
- "version": "7.19.1",
3
+ "version": "7.19.2",
4
4
  "type": "module",
5
5
  "description": "Bedrock Verifiable Credential Delivery",
6
6
  "main": "./lib/index.js",