@bedrock/vc-delivery 7.16.1 → 7.17.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/issue.js CHANGED
@@ -162,25 +162,36 @@ async function _evalIssueRequests({
162
162
  }));
163
163
  }
164
164
 
165
- function _getIssueZcap({issuerInstances, zcaps, issueRequest, mediaType}) {
165
+ function _getIssueZcap({
166
+ issuerInstances, zcaps, issueRequestParams, mediaType
167
+ }) {
166
168
  const {
167
169
  issuerInstanceId,
168
170
  oid4vci: {credentialConfigurationId} = {}
169
- } = issueRequest;
170
- const {zcapReferenceIds: {issue: issueRefId}} = issuerInstances.find(
171
- ({id, oid4vci, supportedMediaTypes}) => {
172
- // find by `issuerInstanceId` if given
173
- if(id && id === issuerInstanceId) {
174
- return true;
175
- }
176
- // find by `credentialConfigurationId` if given
177
- if(credentialConfigurationId && oid4vci) {
178
- return oid4vci.supportedConfigurationIds.includes(
179
- credentialConfigurationId);
180
- }
181
- // find by `mediaType` instead
182
- return supportedMediaTypes.includes(mediaType);
183
- });
171
+ } = issueRequestParams;
172
+ const instance = issuerInstances.find(instance => {
173
+ const {id, oid4vci, supportedMediaTypes} = instance;
174
+ // find by `issuerInstanceId` if given
175
+ if(id && id === issuerInstanceId) {
176
+ return true;
177
+ }
178
+ // find by `credentialConfigurationId` if given
179
+ if(credentialConfigurationId && oid4vci) {
180
+ return oid4vci.supportedCredentialConfigurations
181
+ [credentialConfigurationId] !== undefined;
182
+ }
183
+ // find by `mediaType` instead
184
+ return supportedMediaTypes.includes(mediaType);
185
+ });
186
+ if(!instance) {
187
+ throw new BedrockError(
188
+ 'Workflow step "issueRequest" configuration error; ' +
189
+ 'no matching issuer instance found.', {
190
+ name: 'DataError',
191
+ details: {httpStatusCode: 500, public: true}
192
+ });
193
+ }
194
+ const {zcapReferenceIds: {issue: issueRefId}} = instance;
184
195
  return zcaps[issueRefId];
185
196
  }
186
197
 
@@ -199,7 +210,7 @@ async function _issue({workflow, exchange, issueRequests, mediaType} = {}) {
199
210
 
200
211
  // get the zcap to use for the issue request
201
212
  const capability = _getIssueZcap({
202
- issuerInstances, zcaps, issueRequest, mediaType
213
+ issuerInstances, zcaps, issueRequestParams: params, mediaType
203
214
  });
204
215
 
205
216
  // specify URL to `/credentials/issue` to handle case that capability
@@ -451,7 +451,7 @@ export async function processCredentialRequests({req, res, isBatchRequest}) {
451
451
  }
452
452
 
453
453
  // verify every DID proof and get resulting DIDs
454
- const results = await Promise.all(
454
+ const credentialRequestProofResults = await Promise.all(
455
455
  credentialRequests.map(async credentialRequest => {
456
456
  return verifyCredentialRequestProof({
457
457
  credentialRequest, workflow, exchange
@@ -459,17 +459,18 @@ export async function processCredentialRequests({req, res, isBatchRequest}) {
459
459
  }));
460
460
  // require `did` to be the same for every proof
461
461
  // FIXME: determine if this needs to be more flexible
462
- const did = results[0];
463
- if(results.some(d => did !== d)) {
462
+ const did = credentialRequestProofResults[0].did;
463
+ if(credentialRequestProofResults.some(({did: d}) => did !== d)) {
464
464
  // FIXME: improve error
465
465
  throw new Error('every DID must be the same');
466
466
  }
467
- // store did results in variables associated with current step
467
+ // store results in variables associated with current step
468
468
  exchange.variables.results[exchange.step] = {
469
469
  ...exchange.variables.results[exchange.step],
470
470
  // common use case of DID Authentication; provide `did` for ease
471
471
  // of use in templates
472
- did
472
+ did,
473
+ credentialRequestProofResults
473
474
  };
474
475
  }
475
476
  },
@@ -25,12 +25,14 @@ export function createSupportedCredentialConfigurations({
25
25
 
26
26
  // for every expected credential request and supported format, generate a
27
27
  // supported credential configuration
28
- for(const credentialRequest of expectedCredentialRequests) {
29
- const configurations = _createCredentialConfigurations({
30
- credentialRequest, supportedFormats, step
31
- });
32
- for(const {id, configuration} of configurations) {
33
- supported.set(id, configuration);
28
+ if(expectedCredentialRequests) {
29
+ for(const credentialRequest of expectedCredentialRequests) {
30
+ const configurations = _createCredentialConfigurations({
31
+ credentialRequest, supportedFormats, step
32
+ });
33
+ for(const {id, configuration} of configurations) {
34
+ supported.set(id, configuration);
35
+ }
34
36
  }
35
37
  }
36
38
 
package/lib/verify.js CHANGED
@@ -157,16 +157,35 @@ export async function verifyCredentialRequestProof({
157
157
 
158
158
  let _did;
159
159
  const dids = [];
160
+ const verifyResults = {};
160
161
  if(diVp) {
161
- const {did} = await verifyDidProofDiVp({workflow, exchange, diVp});
162
- dids.push(did);
163
- _did = did;
162
+ const result = await verifyDidProofDiVp({workflow, exchange, diVp});
163
+ verifyResults.di_vp = verifyResults.di_vp ?? [];
164
+ // return results w/both common key formats
165
+ verifyResults.di_vp.push({
166
+ did: result.did,
167
+ verificationMethod: await _toMultikey({
168
+ vm: result.verifyResult.verificationMethod
169
+ }),
170
+ publicKeyJwk: await _toJwk(result.verifyResult.verificationMethod)
171
+ });
172
+ dids.push(result.did);
173
+ _did = result.did;
164
174
  }
165
175
  if(jwt) {
166
- const {did} = await verifyDidProofJwt({workflow, exchange, jwt});
167
- dids.push(did);
176
+ const result = await verifyDidProofJwt({workflow, exchange, jwt});
177
+ verifyResults.jwt = verifyResults.jwt ?? [];
178
+ // return results w/both common key formats
179
+ verifyResults.jwt.push({
180
+ did: result.did,
181
+ verificationMethod: await _toMultikey({
182
+ jwk: result.verifyResult.jwk, controller: result.did
183
+ }),
184
+ publicKeyJwk: result.verifyResult.jwk
185
+ });
186
+ dids.push(result.did);
168
187
  if(_did === undefined) {
169
- _did = did;
188
+ _did = result.did;
170
189
  }
171
190
  }
172
191
 
@@ -175,7 +194,7 @@ export async function verifyCredentialRequestProof({
175
194
  throw new Error('every DID must be the same');
176
195
  }
177
196
 
178
- return _did;
197
+ return {did: _did, credentialRequest, verifyResults};
179
198
  }
180
199
 
181
200
  export async function verifyDidProofDiVp({workflow, exchange, diVp} = {}) {
@@ -191,6 +210,7 @@ export async function verifyDidProofDiVp({workflow, exchange, diVp} = {}) {
191
210
  expectedChallenge: exchange.id,
192
211
  expectedDomain: exchangeId
193
212
  });
213
+ // FIXME: ensure cryptosuite used matches a supported `di_vp` proof type
194
214
  const did = verifyResult.verificationMethod.controller;
195
215
  return {verified: true, did, verifyResult};
196
216
  }
@@ -208,6 +228,7 @@ export async function verifyDidProofJwt({workflow, exchange, jwt} = {}) {
208
228
 
209
229
  let issuer;
210
230
  // `resolveKey` is passed `protectedHeader`
231
+ let jwk;
211
232
  const resolveKey = async ({alg, kid}) => {
212
233
  const isEcdsa = ECDSA_ALGS.includes(alg);
213
234
  const isEddsa = !isEcdsa && EDDSA_ALGS.includes(alg);
@@ -258,7 +279,6 @@ export async function verifyDidProofJwt({workflow, exchange, jwt} = {}) {
258
279
  }
259
280
  });
260
281
  }
261
- let jwk;
262
282
  if(isEcdsa) {
263
283
  const keyPair = await EcdsaMultikey.from(vm);
264
284
  jwk = await EcdsaMultikey.toJwk({keyPair});
@@ -272,6 +292,7 @@ export async function verifyDidProofJwt({workflow, exchange, jwt} = {}) {
272
292
  };
273
293
 
274
294
  // FIXME: indicate supported signatures
295
+ // FIXME: ensure algorithm used matches a supported `jwt` algs
275
296
  // const allowedAlgorithms = [];
276
297
 
277
298
  // use `jose` lib (for now) to verify JWT and return `payload`;
@@ -285,7 +306,7 @@ export async function verifyDidProofJwt({workflow, exchange, jwt} = {}) {
285
306
  audience,
286
307
  clockTolerance: maxClockSkew
287
308
  });
288
- verifyResult = {payload, protectedHeader};
309
+ verifyResult = {payload, protectedHeader, jwk};
289
310
  } catch(e) {
290
311
  const details = {
291
312
  httpStatusCode: 403,
@@ -332,3 +353,56 @@ export async function verifyDidProofJwt({workflow, exchange, jwt} = {}) {
332
353
 
333
354
  return {verified: true, did: issuer, verifyResult};
334
355
  }
356
+
357
+ function _getJwkAlgFromMultibase(mb) {
358
+ if(mb.startsWith('zDna')) {
359
+ return 'ES256';
360
+ }
361
+ if(mb.startsWith('z82L')) {
362
+ return 'ES384';
363
+ }
364
+ if(mb.startsWith('z6Mk')) {
365
+ return 'EdDSA';
366
+ }
367
+ }
368
+
369
+ async function _toJwk(vm) {
370
+ let jwk;
371
+ const alg = _getJwkAlgFromMultibase(vm.publicKeyMultibase);
372
+ if(alg === 'EdDSA') {
373
+ const keyPair = await Ed25519Multikey.from(vm);
374
+ jwk = await Ed25519Multikey.toJwk({keyPair});
375
+ jwk.alg = 'EdDSA';
376
+ } else if(alg?.startsWith('ES')) {
377
+ const keyPair = await EcdsaMultikey.from(vm);
378
+ jwk = await EcdsaMultikey.toJwk({keyPair});
379
+ jwk.alg = alg;
380
+ }
381
+ return jwk;
382
+ }
383
+
384
+ async function _toMultikey({vm, jwk, controller}) {
385
+ let keyPair;
386
+ if(jwk) {
387
+ if(jwk.alg === 'EdDSA') {
388
+ keyPair = await Ed25519Multikey.fromJwk({jwk, controller});
389
+ }
390
+ if(jwk.alg.startsWith('ES')) {
391
+ keyPair = await EcdsaMultikey.fromJwk({jwk, controller});
392
+ }
393
+ } else if(vm) {
394
+ if(vm.type === 'Multikey') {
395
+ return vm;
396
+ }
397
+ const alg = _getJwkAlgFromMultibase(vm.publicKeyMultibase);
398
+ if(alg === 'EdDSA') {
399
+ keyPair = await Ed25519Multikey.from(vm);
400
+ } else if(alg.startsWith('ES')) {
401
+ keyPair = await EcdsaMultikey.from(vm);
402
+ }
403
+ }
404
+
405
+ if(keyPair) {
406
+ return keyPair.export();
407
+ }
408
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bedrock/vc-delivery",
3
- "version": "7.16.1",
3
+ "version": "7.17.0",
4
4
  "type": "module",
5
5
  "description": "Bedrock Verifiable Credential Delivery",
6
6
  "main": "./lib/index.js",
@@ -232,6 +232,57 @@ function credentialConfiguration() {
232
232
  };
233
233
  }
234
234
 
235
+ function mdocCredentialConfiguration() {
236
+ return {
237
+ title: 'OID4VCI Credential Configuration for mdocs',
238
+ type: 'object',
239
+ required: [
240
+ 'cryptographic_binding_methods_supported',
241
+ 'credential_signing_alg_values_supported',
242
+ 'doctype',
243
+ 'format',
244
+ 'proof_types_supported'
245
+ ],
246
+ additionalProperties: false,
247
+ properties: {
248
+ cryptographic_binding_methods_supported: {
249
+ type: 'array',
250
+ minItems: 1,
251
+ maxItems: 1,
252
+ items: {
253
+ type: 'string',
254
+ enum: ['cose_key']
255
+ }
256
+ },
257
+ credential_signing_alg_values_supported: {
258
+ // schema could be improved to ensure array is const: [-7, -9]; but
259
+ // other values might be acceptable in the future
260
+ type: 'array',
261
+ minItems: 2,
262
+ maxItems: 2,
263
+ items: {
264
+ type: 'number',
265
+ enum: [-7, -9]
266
+ }
267
+ },
268
+ doctype: {
269
+ const: 'org.iso.18013.5.1.mDL'
270
+ },
271
+ format: {
272
+ type: 'string',
273
+ enum: ['mso_mdoc']
274
+ },
275
+ proof_types_supported: {
276
+ type: 'object',
277
+ properties: {
278
+ di_vp: supportedProofTypeConfiguration,
279
+ jwt: supportedProofTypeConfiguration
280
+ }
281
+ }
282
+ }
283
+ };
284
+ }
285
+
235
286
  const openIdExchangeOptions = {
236
287
  title: 'OpenID Exchange options',
237
288
  type: 'object',
@@ -367,7 +418,9 @@ const issuerInstance = {
367
418
  type: 'object',
368
419
  additionalProperties: false,
369
420
  patternProperties: {
370
- '^.*$': credentialConfiguration()
421
+ '^.*$': {
422
+ oneOf: [credentialConfiguration(), mdocCredentialConfiguration()]
423
+ }
371
424
  }
372
425
  }
373
426
  }