@bedrock/vc-delivery 7.14.2 → 7.15.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/helpers.js CHANGED
@@ -304,7 +304,7 @@ export function createVerifyOptions({
304
304
 
305
305
  // update `checks` with anything additional from `verifyPresentationOptions`
306
306
  const checkSet = new Set(checks);
307
- if(verifyPresentationOptions.checks) {
307
+ if(verifyPresentationOptions?.checks) {
308
308
  Object.entries(verifyPresentationOptions.checks)
309
309
  .forEach(([check, enabled]) => enabled && checkSet.add(check));
310
310
  }
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * Copyright (c) 2022-2026 Digital Bazaar, Inc. All rights reserved.
2
+ * Copyright (c) 2022-2026 Digital Bazaar, Inc.
3
3
  */
4
4
  import * as bedrock from '@bedrock/core';
5
5
  import * as draft13 from './oid4vciDraft13.js';
@@ -14,7 +14,7 @@ import {checkAccessToken} from '@bedrock/oauth2-verifier';
14
14
  import {compile} from '@bedrock/validation';
15
15
  import {ExchangeProcessor} from '../ExchangeProcessor.js';
16
16
  import {getStepAuthorizationRequest} from './oid4vp.js';
17
- import {verifyDidProofJwt} from '../verify.js';
17
+ import {verifyCredentialRequestProof} from '../verify.js';
18
18
 
19
19
  const {util: {BedrockError}} = bedrock;
20
20
 
@@ -430,23 +430,32 @@ export async function processCredentialRequests({req, res, isBatchRequest}) {
430
430
  }
431
431
 
432
432
  // check to see if step requires a DID proof
433
- if(step.jwtDidProofRequest) {
434
- // handle OID4VCI specialized JWT DID Proof request...
433
+ if(step.divpDidProofRequest || step.jwtDidProofRequest) {
434
+ // handle OID4VCI specialized DI VP / JWT DID Proof request...
435
435
 
436
436
  // `proof` must be in every credential request; if any request is
437
437
  // missing `proof` then request a DID proof
438
- if(credentialRequests.some(cr => !cr.proofs?.jwt)) {
438
+ const acceptableProofTypes = new Set();
439
+ if(step.divpDidProofRequest) {
440
+ acceptableProofTypes.add('di_vp');
441
+ }
442
+ if(step.jwtDidProofRequest) {
443
+ acceptableProofTypes.add('jwt');
444
+ }
445
+ const hasAcceptableProofType =
446
+ cr => Object.keys(cr.proofs ?? {}).some(
447
+ k => acceptableProofTypes.has(k));
448
+ if(credentialRequests.some(cr => !hasAcceptableProofType(cr))) {
439
449
  didProofRequired = true;
440
450
  return _requestDidProof({res, exchangeRecord});
441
451
  }
442
452
 
443
453
  // verify every DID proof and get resulting DIDs
444
454
  const results = await Promise.all(
445
- credentialRequests.map(async cr => {
446
- // FIXME: do not support more than one proof at this time
447
- const {proofs: {jwt: [jwt]}} = cr;
448
- const {did} = await verifyDidProofJwt({workflow, exchange, jwt});
449
- return did;
455
+ credentialRequests.map(async credentialRequest => {
456
+ return verifyCredentialRequestProof({
457
+ credentialRequest, workflow, exchange
458
+ });
450
459
  }));
451
460
  // require `did` to be the same for every proof
452
461
  // FIXME: determine if this needs to be more flexible
@@ -497,7 +506,7 @@ export async function processCredentialRequests({req, res, isBatchRequest}) {
497
506
  }
498
507
 
499
508
  // otherwise, input is required if:
500
- // 1. a `jwtDidProofRequest` is required and hasn't been provided
509
+ // 1. a `divp|jwtDidProofRequest` is required and hasn't been provided
501
510
  // 2. OID4VP is enabled and no OID4VP result has been stored yet
502
511
  return didProofRequired || (step.openId && !exchange.variables
503
512
  .results[exchange.step]?.openId?.authorizationRequest);
@@ -617,7 +626,7 @@ function _createSupportedCredentialRequests({
617
626
  if(isDraft13) {
618
627
  supportedCredentialRequests =
619
628
  draft13.createSupportedCredentialRequests({
620
- workflow, exchange, issueRequestsParams
629
+ workflow, exchange, issueRequestsParams, step
621
630
  });
622
631
  } else {
623
632
  supportedCredentialRequests = [];
@@ -712,7 +721,7 @@ function _getSupportedCredentialConfigurations({workflow, exchange, step}) {
712
721
 
713
722
  // no explicit IDs; create legacy supported credential configurations
714
723
  return draft13.createSupportedCredentialConfigurations({
715
- exchange, issuerInstances
724
+ exchange, issuerInstances, step
716
725
  });
717
726
  }
718
727
 
@@ -8,7 +8,7 @@ import {randomUUID as uuid} from 'node:crypto';
8
8
  const {util: {BedrockError}} = bedrock;
9
9
 
10
10
  export function createSupportedCredentialConfigurations({
11
- exchange, issuerInstances
11
+ exchange, issuerInstances, step
12
12
  } = {}) {
13
13
  // get legacy `expectedCredentialRequests`
14
14
  const {
@@ -27,7 +27,7 @@ export function createSupportedCredentialConfigurations({
27
27
  // supported credential configuration
28
28
  for(const credentialRequest of expectedCredentialRequests) {
29
29
  const configurations = _createCredentialConfigurations({
30
- credentialRequest, supportedFormats
30
+ credentialRequest, supportedFormats, step
31
31
  });
32
32
  for(const {id, configuration} of configurations) {
33
33
  supported.set(id, configuration);
@@ -38,11 +38,11 @@ export function createSupportedCredentialConfigurations({
38
38
  }
39
39
 
40
40
  export function createSupportedCredentialRequests({
41
- workflow, exchange, issueRequestsParams
41
+ workflow, exchange, issueRequestsParams, step
42
42
  } = {}) {
43
43
  const issuerInstances = getWorkflowIssuerInstances({workflow});
44
44
  const supported = createSupportedCredentialConfigurations({
45
- exchange, issuerInstances
45
+ exchange, issuerInstances, step
46
46
  });
47
47
 
48
48
  // for each `issueRequest` params, create a duplicate for each supported
@@ -130,12 +130,15 @@ function _matchCredentialRequests({
130
130
  type: 'openid_credential',
131
131
  credential_configuration_id
132
132
  };
133
- // only proof type supported for draft 13 is `jwt` per JSON schema that
134
- // has already run
135
133
  if(cr.proof) {
136
- newRequest.proofs = {
137
- jwt: [cr.proof.jwt]
138
- };
134
+ newRequest.proofs = {};
135
+ for(const [key, value] of Object.entries(cr.proof)) {
136
+ if(key === 'proof_type') {
137
+ newRequest.proof_type = value;
138
+ continue;
139
+ }
140
+ newRequest.proofs[key] = [value];
141
+ }
139
142
  }
140
143
  return newRequest;
141
144
  }
@@ -149,7 +152,7 @@ function _matchCredentialRequests({
149
152
  }
150
153
 
151
154
  function _createCredentialConfigurations({
152
- credentialRequest, supportedFormats
155
+ credentialRequest, supportedFormats, step
153
156
  }) {
154
157
  const configurations = [];
155
158
 
@@ -164,17 +167,23 @@ function _createCredentialConfigurations({
164
167
  format, credential_definition
165
168
  });
166
169
  const configuration = {format, credential_definition};
167
- // FIXME: if `jwtDidProofRequest` exists in (any) step in the exchange,
168
- // then must include:
169
- /*
170
- "proof_types_supported": {
171
- "jwt": {
172
- "proof_signing_alg_values_supported": [
173
- "ES256"
174
- ]
170
+ if(step.divpDidProofRequest) {
171
+ configuration.proof_types_supported = {
172
+ di_vp: {
173
+ proof_signing_alg_values_supported: [
174
+ 'ecdsa-rdfc-2019', 'eddsa-rdfc-2022'
175
+ ]
176
+ }
177
+ };
178
+ }
179
+ if(step.jwtDidProofRequest) {
180
+ if(!configuration.proof_types_supported) {
181
+ configuration.proof_types_supported = {};
175
182
  }
183
+ configuration.proof_types_supported.jwt = {
184
+ proof_signing_alg_values_supported: ['ES256']
185
+ };
176
186
  }
177
- */
178
187
  configurations.push({id, configuration});
179
188
  }
180
189
 
package/lib/verify.js CHANGED
@@ -148,6 +148,53 @@ export async function verify({
148
148
  };
149
149
  }
150
150
 
151
+ export async function verifyCredentialRequestProof({
152
+ credentialRequest, workflow, exchange
153
+ } = {}) {
154
+ // FIXME: do not support more than one proof of each type at this time
155
+ const jwt = credentialRequest.proofs.jwt?.[0];
156
+ const diVp = credentialRequest.proofs.di_vp?.[0];
157
+
158
+ let _did;
159
+ const dids = [];
160
+ if(diVp) {
161
+ const {did} = await verifyDidProofDiVp({workflow, exchange, diVp});
162
+ dids.push(did);
163
+ _did = did;
164
+ }
165
+ if(jwt) {
166
+ const {did} = await verifyDidProofJwt({workflow, exchange, jwt});
167
+ dids.push(did);
168
+ if(_did === undefined) {
169
+ _did = did;
170
+ }
171
+ }
172
+
173
+ if(dids.some(d => d !== _did)) {
174
+ // FIXME: improve error
175
+ throw new Error('every DID must be the same');
176
+ }
177
+
178
+ return _did;
179
+ }
180
+
181
+ export async function verifyDidProofDiVp({workflow, exchange, diVp} = {}) {
182
+ // domain is always the `exchangeId` and cannot be configured; this
183
+ // prevents attacks where access tokens could otherwise be generated
184
+ // if the AS keys were compromised; the `exchangeId` must also be known
185
+ const exchangeId = `${workflow.id}/exchanges/${exchange.id}`;
186
+ const verifyResult = await verify({
187
+ workflow,
188
+ presentation: diVp,
189
+ // challenge is always local exchange ID; which is what is returned from
190
+ // nonce endpoint; VCALM exchanges are short-lived and are capability URLs
191
+ expectedChallenge: exchange.id,
192
+ expectedDomain: exchangeId
193
+ });
194
+ const did = verifyResult.verificationMethod.controller;
195
+ return {verified: true, did, verifyResult};
196
+ }
197
+
151
198
  export async function verifyDidProofJwt({workflow, exchange, jwt} = {}) {
152
199
  // optional oauth2 options
153
200
  const {oauth2} = exchange.openId;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bedrock/vc-delivery",
3
- "version": "7.14.2",
3
+ "version": "7.15.0",
4
4
  "type": "module",
5
5
  "description": "Bedrock Verifiable Credential Delivery",
6
6
  "main": "./lib/index.js",
@@ -40,7 +40,7 @@
40
40
  "@digitalbazaar/ed25519-signature-2020": "^5.4.0",
41
41
  "@digitalbazaar/ezcap": "^4.1.0",
42
42
  "@digitalbazaar/http-client": "^4.2.0",
43
- "@digitalbazaar/oid4-client": "^5.11.0",
43
+ "@digitalbazaar/oid4-client": "^5.12.1",
44
44
  "@digitalbazaar/vc": "^7.2.0",
45
45
  "@digitalbazaar/webkms-client": "^14.2.0",
46
46
  "assert-plus": "^1.0.0",
@@ -195,6 +195,20 @@ const credentialDefinition = {
195
195
  }
196
196
  };
197
197
 
198
+ const supportedProofTypeConfiguration = {
199
+ title: 'OID4VCI Supported Proof Type Configuration',
200
+ type: 'object',
201
+ required: ['proof_signing_alg_values_supported'],
202
+ additionalProperties: false,
203
+ properties: {
204
+ proof_signing_alg_values_supported: {
205
+ type: 'array',
206
+ minItems: 1,
207
+ items: {type: 'string'}
208
+ }
209
+ }
210
+ };
211
+
198
212
  function credentialConfiguration() {
199
213
  return {
200
214
  title: 'OID4VCI Credential Configuration',
@@ -209,14 +223,9 @@ function credentialConfiguration() {
209
223
  },
210
224
  proof_types_supported: {
211
225
  type: 'object',
212
- required: ['proof_signing_al_values_supported'],
213
- additionalProperties: false,
214
226
  properties: {
215
- proof_signing_alg_values_supported: {
216
- type: 'array',
217
- minItems: 1,
218
- items: {type: 'string'}
219
- }
227
+ di_vp: supportedProofTypeConfiguration,
228
+ jwt: supportedProofTypeConfiguration
220
229
  }
221
230
  }
222
231
  }
@@ -584,6 +593,31 @@ function computedStep() {
584
593
  }
585
594
  }
586
595
  },
596
+ divpDidProofRequest: {
597
+ type: 'object',
598
+ additionalProperties: false,
599
+ properties: {
600
+ acceptedMethods: {
601
+ title: 'Accepted DID Methods',
602
+ type: 'array',
603
+ minItems: 1,
604
+ items: {
605
+ title: 'Accepted DID Method',
606
+ type: 'object',
607
+ additionalProperties: false,
608
+ properties: {
609
+ method: {type: 'string'}
610
+ }
611
+ }
612
+ },
613
+ allowedCryptosuites: {
614
+ title: 'Allowed DI Cryptosuites',
615
+ type: 'array',
616
+ minItems: 1,
617
+ items: {type: 'string'}
618
+ }
619
+ }
620
+ },
587
621
  nextStep: {type: 'string'},
588
622
  // required to support OID4VP
589
623
  openId: {
@@ -717,13 +751,17 @@ function openIdCredentialRequestDraft13() {
717
751
  title: 'DID Authn Proof JWT',
718
752
  type: 'object',
719
753
  additionalProperties: false,
720
- required: ['proof_type', 'jwt'],
754
+ anyOf: [
755
+ {required: ['proof_type', 'jwt']},
756
+ {required: ['proof_type', 'di_vp']}
757
+ ],
721
758
  properties: {
722
759
  proof_type: {
723
760
  type: 'string',
724
- enum: ['jwt']
761
+ enum: ['jwt', 'di_vp']
725
762
  },
726
- jwt: {type: 'string'}
763
+ jwt: {type: 'string'},
764
+ di_vp: verifiablePresentation()
727
765
  }
728
766
  }
729
767
  }
@@ -735,21 +773,15 @@ function openIdCredentialRequestVersion1() {
735
773
  title: 'OID4VCI-1.0 Credential Request',
736
774
  type: 'object',
737
775
  additionalProperties: false,
738
- oneOf: [
739
- // FIXME: only support `credential_identifier`;
740
- // `credential_configuration_id` is for scope-identified credentials,
741
- // which is not supported
742
- {required: ['credential_identifier']}//,
743
- //{required: ['credential_configuration_id']}
744
- ],
776
+ // `credential_configuration_id` is for scope-identified credentials,
777
+ // which is not supported
778
+ required: ['credential_identifier'],
745
779
  properties: {
746
780
  credential_identifier: {type: 'string'},
747
- // FIXME: remove me
748
- //credential_configuration_id: {type: 'string'},
749
781
  proofs: {
750
782
  type: 'object',
751
783
  additionalProperties: false,
752
- oneOf: [
784
+ anyOf: [
753
785
  {required: ['jwt']},
754
786
  {required: ['di_vp']}
755
787
  ],