@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 +28 -17
- package/lib/oid4/oid4vci.js +6 -5
- package/lib/oid4/oid4vciDraft13.js +8 -6
- package/lib/verify.js +83 -9
- package/package.json +1 -1
- package/schemas/bedrock-vc-workflow.js +54 -1
package/lib/issue.js
CHANGED
|
@@ -162,25 +162,36 @@ async function _evalIssueRequests({
|
|
|
162
162
|
}));
|
|
163
163
|
}
|
|
164
164
|
|
|
165
|
-
function _getIssueZcap({
|
|
165
|
+
function _getIssueZcap({
|
|
166
|
+
issuerInstances, zcaps, issueRequestParams, mediaType
|
|
167
|
+
}) {
|
|
166
168
|
const {
|
|
167
169
|
issuerInstanceId,
|
|
168
170
|
oid4vci: {credentialConfigurationId} = {}
|
|
169
|
-
} =
|
|
170
|
-
const
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
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,
|
|
213
|
+
issuerInstances, zcaps, issueRequestParams: params, mediaType
|
|
203
214
|
});
|
|
204
215
|
|
|
205
216
|
// specify URL to `/credentials/issue` to handle case that capability
|
package/lib/oid4/oid4vci.js
CHANGED
|
@@ -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
|
|
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 =
|
|
463
|
-
if(
|
|
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
|
|
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
|
-
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
|
162
|
-
|
|
163
|
-
|
|
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
|
|
167
|
-
|
|
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
|
@@ -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
|
-
'^.*$':
|
|
421
|
+
'^.*$': {
|
|
422
|
+
oneOf: [credentialConfiguration(), mdocCredentialConfiguration()]
|
|
423
|
+
}
|
|
371
424
|
}
|
|
372
425
|
}
|
|
373
426
|
}
|