@bedrock/vc-delivery 7.16.1 → 7.17.1

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.
@@ -258,7 +258,7 @@ export class ExchangeProcessor {
258
258
 
259
259
  // 5. Set the verification results in
260
260
  // `exchange.variables.results[exchange.step]`.
261
- const {verificationMethod} = verifyResult;
261
+ const {verificationMethod, publicKeyJwk} = verifyResult;
262
262
  exchange.variables.results[exchange.step] = {
263
263
  ...exchange.variables.results[exchange.step],
264
264
  // common use case of DID Authentication; provide `did` for ease
@@ -266,6 +266,7 @@ export class ExchangeProcessor {
266
266
  // receives `did` not verification method nor VP
267
267
  did: verificationMethod?.controller || null,
268
268
  verificationMethod,
269
+ publicKeyJwk,
269
270
  verifiablePresentation,
270
271
  verifyPresentationResults: buildVerifyPresentationResults({verifyResult})
271
272
  };
package/lib/issue.js CHANGED
@@ -129,6 +129,7 @@ export function getIssueRequestsParams({workflow, exchange, step}) {
129
129
  }
130
130
  }
131
131
  const params = {
132
+ issuerInstanceId: r.issuerInstanceId,
132
133
  oid4vci: r.oid4vci,
133
134
  typedTemplate,
134
135
  variables: {
@@ -162,25 +163,36 @@ async function _evalIssueRequests({
162
163
  }));
163
164
  }
164
165
 
165
- function _getIssueZcap({issuerInstances, zcaps, issueRequest, mediaType}) {
166
+ function _getIssueZcap({
167
+ issuerInstances, zcaps, issueRequestParams, mediaType
168
+ }) {
166
169
  const {
167
170
  issuerInstanceId,
168
171
  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
- });
172
+ } = issueRequestParams;
173
+ const instance = issuerInstances.find(instance => {
174
+ const {id, oid4vci, supportedMediaTypes} = instance;
175
+ // find by `issuerInstanceId` if given
176
+ if(id && id === issuerInstanceId) {
177
+ return true;
178
+ }
179
+ // find by `credentialConfigurationId` if given
180
+ if(credentialConfigurationId && oid4vci) {
181
+ return oid4vci.supportedCredentialConfigurations
182
+ [credentialConfigurationId] !== undefined;
183
+ }
184
+ // find by `mediaType` instead
185
+ return supportedMediaTypes.includes(mediaType);
186
+ });
187
+ if(!instance) {
188
+ throw new BedrockError(
189
+ 'Workflow step "issueRequest" configuration error; ' +
190
+ 'no matching issuer instance found.', {
191
+ name: 'DataError',
192
+ details: {httpStatusCode: 500, public: true}
193
+ });
194
+ }
195
+ const {zcapReferenceIds: {issue: issueRefId}} = instance;
184
196
  return zcaps[issueRefId];
185
197
  }
186
198
 
@@ -199,7 +211,7 @@ async function _issue({workflow, exchange, issueRequests, mediaType} = {}) {
199
211
 
200
212
  // get the zcap to use for the issue request
201
213
  const capability = _getIssueZcap({
202
- issuerInstances, zcaps, issueRequest, mediaType
214
+ issuerInstances, zcaps, issueRequestParams: params, mediaType
203
215
  });
204
216
 
205
217
  // 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
@@ -142,7 +142,10 @@ export async function verify({
142
142
  return {
143
143
  verified,
144
144
  challengeUses,
145
+ // return original VM result and w/both common key formats
145
146
  verificationMethod,
147
+ multikey: await _toMultikey({vm: verificationMethod}),
148
+ publicKeyJwk: await _toJwk(verificationMethod),
146
149
  credentialResults,
147
150
  presentationResult
148
151
  };
@@ -157,16 +160,36 @@ export async function verifyCredentialRequestProof({
157
160
 
158
161
  let _did;
159
162
  const dids = [];
163
+ const verifyResults = {};
160
164
  if(diVp) {
161
- const {did} = await verifyDidProofDiVp({workflow, exchange, diVp});
162
- dids.push(did);
163
- _did = did;
165
+ const result = await verifyDidProofDiVp({workflow, exchange, diVp});
166
+ verifyResults.di_vp = verifyResults.di_vp ?? [];
167
+ verifyResults.di_vp.push({
168
+ did: result.did,
169
+ // return original VM result and w/both common key formats
170
+ verificationMethod: result.verifyResult.verificationMethod,
171
+ multikey: await _toMultikey({vm: result.verifyResult.verificationMethod}),
172
+ publicKeyJwk: await _toJwk(result.verifyResult.verificationMethod)
173
+ });
174
+ dids.push(result.did);
175
+ _did = result.did;
164
176
  }
165
177
  if(jwt) {
166
- const {did} = await verifyDidProofJwt({workflow, exchange, jwt});
167
- dids.push(did);
178
+ const result = await verifyDidProofJwt({workflow, exchange, jwt});
179
+ verifyResults.jwt = verifyResults.jwt ?? [];
180
+ const multikey = await _toMultikey({
181
+ jwk: result.verifyResult.jwk, controller: result.did
182
+ });
183
+ verifyResults.jwt.push({
184
+ did: result.did,
185
+ // return original VM result and w/both common key formats
186
+ verificationMethod: multikey,
187
+ multikey,
188
+ publicKeyJwk: result.verifyResult.jwk
189
+ });
190
+ dids.push(result.did);
168
191
  if(_did === undefined) {
169
- _did = did;
192
+ _did = result.did;
170
193
  }
171
194
  }
172
195
 
@@ -175,7 +198,7 @@ export async function verifyCredentialRequestProof({
175
198
  throw new Error('every DID must be the same');
176
199
  }
177
200
 
178
- return _did;
201
+ return {did: _did, credentialRequest, verifyResults};
179
202
  }
180
203
 
181
204
  export async function verifyDidProofDiVp({workflow, exchange, diVp} = {}) {
@@ -191,6 +214,7 @@ export async function verifyDidProofDiVp({workflow, exchange, diVp} = {}) {
191
214
  expectedChallenge: exchange.id,
192
215
  expectedDomain: exchangeId
193
216
  });
217
+ // FIXME: ensure cryptosuite used matches a supported `di_vp` proof type
194
218
  const did = verifyResult.verificationMethod.controller;
195
219
  return {verified: true, did, verifyResult};
196
220
  }
@@ -208,6 +232,7 @@ export async function verifyDidProofJwt({workflow, exchange, jwt} = {}) {
208
232
 
209
233
  let issuer;
210
234
  // `resolveKey` is passed `protectedHeader`
235
+ let jwk;
211
236
  const resolveKey = async ({alg, kid}) => {
212
237
  const isEcdsa = ECDSA_ALGS.includes(alg);
213
238
  const isEddsa = !isEcdsa && EDDSA_ALGS.includes(alg);
@@ -258,7 +283,6 @@ export async function verifyDidProofJwt({workflow, exchange, jwt} = {}) {
258
283
  }
259
284
  });
260
285
  }
261
- let jwk;
262
286
  if(isEcdsa) {
263
287
  const keyPair = await EcdsaMultikey.from(vm);
264
288
  jwk = await EcdsaMultikey.toJwk({keyPair});
@@ -272,6 +296,7 @@ export async function verifyDidProofJwt({workflow, exchange, jwt} = {}) {
272
296
  };
273
297
 
274
298
  // FIXME: indicate supported signatures
299
+ // FIXME: ensure algorithm used matches a supported `jwt` algs
275
300
  // const allowedAlgorithms = [];
276
301
 
277
302
  // use `jose` lib (for now) to verify JWT and return `payload`;
@@ -285,7 +310,7 @@ export async function verifyDidProofJwt({workflow, exchange, jwt} = {}) {
285
310
  audience,
286
311
  clockTolerance: maxClockSkew
287
312
  });
288
- verifyResult = {payload, protectedHeader};
313
+ verifyResult = {payload, protectedHeader, jwk};
289
314
  } catch(e) {
290
315
  const details = {
291
316
  httpStatusCode: 403,
@@ -332,3 +357,69 @@ export async function verifyDidProofJwt({workflow, exchange, jwt} = {}) {
332
357
 
333
358
  return {verified: true, did: issuer, verifyResult};
334
359
  }
360
+
361
+ function _getJwkAlgFromMultibase(mb) {
362
+ if(mb.startsWith('zDna')) {
363
+ return 'ES256';
364
+ }
365
+ if(mb.startsWith('z82L')) {
366
+ return 'ES384';
367
+ }
368
+ if(mb.startsWith('z6Mk')) {
369
+ return 'EdDSA';
370
+ }
371
+ }
372
+
373
+ // tries to convert to JWK and returns undefined if it cannot
374
+ async function _toJwk(vm) {
375
+ if(vm?.type === 'JsonWebKey') {
376
+ return vm.publicKeyJwk;
377
+ }
378
+ if(!vm?.publicKeyMultibase) {
379
+ return;
380
+ }
381
+ let jwk;
382
+ const alg = _getJwkAlgFromMultibase(vm.publicKeyMultibase);
383
+ if(alg === 'EdDSA') {
384
+ const keyPair = await Ed25519Multikey.from(vm);
385
+ jwk = await Ed25519Multikey.toJwk({keyPair});
386
+ jwk.alg = 'EdDSA';
387
+ } else if(alg?.startsWith('ES')) {
388
+ const keyPair = await EcdsaMultikey.from(vm);
389
+ jwk = await EcdsaMultikey.toJwk({keyPair});
390
+ jwk.alg = alg;
391
+ }
392
+ return jwk;
393
+ }
394
+
395
+ // tries to convert to multikey and returns undefined if it cannot
396
+ async function _toMultikey({vm, jwk, controller}) {
397
+ if(vm?.type === 'JsonWebKey') {
398
+ jwk = vm.publicKeyJwk;
399
+ }
400
+
401
+ let keyPair;
402
+ if(jwk) {
403
+ if(jwk.crv === 'Ed25519' || jwk.alg === 'EdDSA') {
404
+ keyPair = await Ed25519Multikey.fromJwk({jwk, controller});
405
+ }
406
+ if(jwk.crv?.startsWith('P-') || jwk.alg?.startsWith('ES')) {
407
+ keyPair = await EcdsaMultikey.fromJwk({jwk, controller});
408
+ }
409
+ } else if(vm?.publicKeyMultibase) {
410
+ if(vm.type === 'Multikey') {
411
+ // return VM as-is
412
+ return vm;
413
+ }
414
+ const alg = _getJwkAlgFromMultibase(vm.publicKeyMultibase);
415
+ if(alg === 'EdDSA') {
416
+ keyPair = await Ed25519Multikey.from(vm);
417
+ } else if(alg.startsWith('ES')) {
418
+ keyPair = await EcdsaMultikey.from(vm);
419
+ }
420
+ }
421
+
422
+ if(keyPair) {
423
+ return keyPair.export();
424
+ }
425
+ }
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.1",
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
  }