@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.
- package/lib/ExchangeProcessor.js +2 -1
- package/lib/issue.js +29 -17
- package/lib/oid4/oid4vci.js +6 -5
- package/lib/oid4/oid4vciDraft13.js +8 -6
- package/lib/verify.js +100 -9
- package/package.json +1 -1
- package/schemas/bedrock-vc-workflow.js +54 -1
package/lib/ExchangeProcessor.js
CHANGED
|
@@ -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({
|
|
166
|
+
function _getIssueZcap({
|
|
167
|
+
issuerInstances, zcaps, issueRequestParams, mediaType
|
|
168
|
+
}) {
|
|
166
169
|
const {
|
|
167
170
|
issuerInstanceId,
|
|
168
171
|
oid4vci: {credentialConfigurationId} = {}
|
|
169
|
-
} =
|
|
170
|
-
const
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
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,
|
|
214
|
+
issuerInstances, zcaps, issueRequestParams: params, mediaType
|
|
203
215
|
});
|
|
204
216
|
|
|
205
217
|
// 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
|
@@ -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
|
|
162
|
-
|
|
163
|
-
|
|
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
|
|
167
|
-
|
|
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
|
@@ -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
|
}
|