@bedrock/vc-delivery 4.8.0 → 5.0.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 +28 -0
- package/lib/openId.js +4 -4
- package/lib/verify.js +41 -9
- package/package.json +23 -24
- package/schemas/bedrock-vc-workflow.js +141 -3
package/lib/helpers.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import * as bedrock from '@bedrock/core';
|
|
5
5
|
import {decodeId, generateId} from 'bnid';
|
|
6
|
+
import {decodeJwt} from 'jose';
|
|
6
7
|
import {Ed25519Signature2020} from '@digitalbazaar/ed25519-signature-2020';
|
|
7
8
|
import {httpsAgent} from '@bedrock/https-agent';
|
|
8
9
|
import jsonata from 'jsonata';
|
|
@@ -102,3 +103,30 @@ export function decodeLocalId({localId} = {}) {
|
|
|
102
103
|
expectedSize: 16
|
|
103
104
|
}));
|
|
104
105
|
}
|
|
106
|
+
|
|
107
|
+
export async function unenvelopeCredential({envelopedCredential} = {}) {
|
|
108
|
+
let credential;
|
|
109
|
+
const {id} = envelopedCredential;
|
|
110
|
+
if(id?.startsWith('data:application/jwt,')) {
|
|
111
|
+
const format = 'application/jwt';
|
|
112
|
+
const jwt = id.slice('data:application/jwt,'.length);
|
|
113
|
+
const claimset = decodeJwt(jwt);
|
|
114
|
+
// FIXME: perform various field mappings as needed
|
|
115
|
+
console.log('VC-JWT claimset', credential);
|
|
116
|
+
return {credential: claimset.vc, format};
|
|
117
|
+
}
|
|
118
|
+
throw new Error('Not implemented.');
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export async function unenvelopePresentation({envelopedPresentation} = {}) {
|
|
122
|
+
const {id} = envelopedPresentation;
|
|
123
|
+
if(id?.startsWith('data:application/jwt,')) {
|
|
124
|
+
const format = 'application/jwt';
|
|
125
|
+
const jwt = id.slice('data:application/jwt,'.length);
|
|
126
|
+
const claimset = decodeJwt(jwt);
|
|
127
|
+
// FIXME: perform various field mappings as needed
|
|
128
|
+
console.log('VC-JWT claimset', claimset);
|
|
129
|
+
return {presentation: claimset.vp, format};
|
|
130
|
+
}
|
|
131
|
+
throw new Error('Not implemented.');
|
|
132
|
+
}
|
package/lib/openId.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import * as bedrock from '@bedrock/core';
|
|
5
5
|
import * as exchanges from './exchanges.js';
|
|
6
6
|
import {
|
|
7
|
-
compile,
|
|
7
|
+
compile, createValidateMiddleware as validate
|
|
8
8
|
} from '@bedrock/validation';
|
|
9
9
|
import {evaluateTemplate, getWorkflowIssuerInstances} from './helpers.js';
|
|
10
10
|
import {importJWK, SignJWT} from 'jose';
|
|
@@ -13,7 +13,8 @@ import {
|
|
|
13
13
|
openIdBatchCredentialBody,
|
|
14
14
|
openIdCredentialBody,
|
|
15
15
|
openIdTokenBody,
|
|
16
|
-
presentationSubmission as presentationSubmissionSchema
|
|
16
|
+
presentationSubmission as presentationSubmissionSchema,
|
|
17
|
+
verifiablePresentation as verifiablePresentationSchema
|
|
17
18
|
} from '../schemas/bedrock-vc-workflow.js';
|
|
18
19
|
import {verify, verifyDidProofJwt} from './verify.js';
|
|
19
20
|
import {asyncHandler} from '@bedrock/express';
|
|
@@ -84,7 +85,7 @@ export async function createRoutes({
|
|
|
84
85
|
|
|
85
86
|
// create validators for x-www-form-urlencoded parsed data
|
|
86
87
|
const validatePresentation = compile(
|
|
87
|
-
{schema:
|
|
88
|
+
{schema: verifiablePresentationSchema()});
|
|
88
89
|
const validatePresentationSubmission = compile(
|
|
89
90
|
{schema: presentationSubmissionSchema});
|
|
90
91
|
|
|
@@ -924,7 +925,6 @@ async function _processAuthorizationResponse({
|
|
|
924
925
|
const {presentationSchema} = step;
|
|
925
926
|
if(presentationSchema) {
|
|
926
927
|
// validate the received VP
|
|
927
|
-
console.log('run presentation schema');
|
|
928
928
|
const {jsonSchema: schema} = presentationSchema;
|
|
929
929
|
const validate = compile({schema});
|
|
930
930
|
const {valid, error} = validate(presentation);
|
package/lib/verify.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* Copyright (c) 2022-2024 Digital Bazaar, Inc. All rights reserved.
|
|
3
3
|
*/
|
|
4
4
|
import * as bedrock from '@bedrock/core';
|
|
5
|
+
import * as EcdsaMultikey from '@digitalbazaar/ecdsa-multikey';
|
|
5
6
|
import * as Ed25519Multikey from '@digitalbazaar/ed25519-multikey';
|
|
6
7
|
import {importJWK, jwtVerify} from 'jose';
|
|
7
8
|
import {didIo} from '@bedrock/did-io';
|
|
@@ -9,6 +10,10 @@ import {getZcapClient} from './helpers.js';
|
|
|
9
10
|
|
|
10
11
|
const {util: {BedrockError}} = bedrock;
|
|
11
12
|
|
|
13
|
+
// supported JWT algs
|
|
14
|
+
const ECDSA_ALGS = ['ES256', 'ES384'];
|
|
15
|
+
const EDDSA_ALGS = ['Ed25519', 'EdDSA'];
|
|
16
|
+
|
|
12
17
|
export async function createChallenge({workflow} = {}) {
|
|
13
18
|
// create zcap client for creating challenges
|
|
14
19
|
const {zcapClient, zcaps} = await getZcapClient({workflow});
|
|
@@ -134,23 +139,50 @@ export async function verifyDidProofJwt({workflow, exchange, jwt} = {}) {
|
|
|
134
139
|
const audience = exchangeId;
|
|
135
140
|
|
|
136
141
|
let issuer;
|
|
137
|
-
|
|
138
|
-
|
|
142
|
+
// `resolveKey` is passed `protectedHeader`
|
|
143
|
+
const resolveKey = async ({alg, kid}) => {
|
|
144
|
+
const isEcdsa = ECDSA_ALGS.includes(alg);
|
|
145
|
+
const isEddsa = !isEcdsa && EDDSA_ALGS.includes(alg);
|
|
146
|
+
if(!(isEcdsa || isEddsa)) {
|
|
147
|
+
throw new BedrockError(
|
|
148
|
+
`Unsupported JWT "alg": "${alg}".`, {
|
|
149
|
+
name: 'DataError',
|
|
150
|
+
details: {
|
|
151
|
+
httpStatusCode: 400,
|
|
152
|
+
public: true
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const vm = await didIo.get({url: kid});
|
|
139
158
|
// `vm.controller` must be the issuer of the DID JWT; also ensure that
|
|
140
159
|
// the specified controller authorized `vm` for the purpose of
|
|
141
160
|
// authentication
|
|
142
161
|
issuer = vm.controller;
|
|
143
162
|
const didDoc = await didIo.get({url: issuer});
|
|
144
|
-
|
|
163
|
+
let match = didDoc?.authentication?.find?.(
|
|
164
|
+
e => e === vm.id || e.id === vm.id);
|
|
165
|
+
if(typeof match === 'string') {
|
|
166
|
+
match = didDoc?.verificationMethod?.find?.(e => e.id === vm.id);
|
|
167
|
+
}
|
|
168
|
+
if(!(match && Array.isArray(match.controller) ?
|
|
169
|
+
match.controller.includes(vm.controller) :
|
|
170
|
+
match.controller === vm.controller)) {
|
|
145
171
|
throw new BedrockError(
|
|
146
172
|
`Verification method controller "${issuer}" did not authorize ` +
|
|
147
173
|
`verification method "${vm.id}" for the purpose of "authentication".`,
|
|
148
174
|
{name: 'NotAllowedError'});
|
|
149
175
|
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
176
|
+
let jwk;
|
|
177
|
+
if(isEcdsa) {
|
|
178
|
+
const keyPair = await EcdsaMultikey.from(vm);
|
|
179
|
+
jwk = await EcdsaMultikey.toJwk({keyPair});
|
|
180
|
+
jwk.alg = alg;
|
|
181
|
+
} else {
|
|
182
|
+
const keyPair = await Ed25519Multikey.from(vm);
|
|
183
|
+
jwk = await Ed25519Multikey.toJwk({keyPair});
|
|
184
|
+
jwk.alg = 'EdDSA';
|
|
185
|
+
}
|
|
154
186
|
return importJWK(jwk);
|
|
155
187
|
};
|
|
156
188
|
|
|
@@ -186,7 +218,7 @@ export async function verifyDidProofJwt({workflow, exchange, jwt} = {}) {
|
|
|
186
218
|
}
|
|
187
219
|
|
|
188
220
|
// check `iss` claim
|
|
189
|
-
if(!(verifyResult?.payload?.iss === issuer)) {
|
|
221
|
+
if(!(issuer && verifyResult?.payload?.iss === issuer)) {
|
|
190
222
|
throw new BedrockError('DID proof JWT validation failed.', {
|
|
191
223
|
name: 'NotAllowedError',
|
|
192
224
|
details: {
|
|
@@ -200,7 +232,7 @@ export async function verifyDidProofJwt({workflow, exchange, jwt} = {}) {
|
|
|
200
232
|
}
|
|
201
233
|
|
|
202
234
|
// check `nonce` claim
|
|
203
|
-
if(
|
|
235
|
+
if(verifyResult?.payload?.nonce !== exchange.id) {
|
|
204
236
|
throw new BedrockError('DID proof JWT validation failed.', {
|
|
205
237
|
name: 'NotAllowedError',
|
|
206
238
|
details: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bedrock/vc-delivery",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.0.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Bedrock Verifiable Credential Delivery",
|
|
6
6
|
"main": "./lib/index.js",
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"schemas/**/*.js"
|
|
10
10
|
],
|
|
11
11
|
"scripts": {
|
|
12
|
-
"lint": "eslint ."
|
|
12
|
+
"lint": "eslint --ext .cjs,.js ."
|
|
13
13
|
},
|
|
14
14
|
"repository": {
|
|
15
15
|
"type": "git",
|
|
@@ -35,41 +35,40 @@
|
|
|
35
35
|
},
|
|
36
36
|
"homepage": "https://github.com/digitalbazaar/bedrock-vc-delivery",
|
|
37
37
|
"dependencies": {
|
|
38
|
+
"@digitalbazaar/ecdsa-multikey": "^1.7.0",
|
|
38
39
|
"@digitalbazaar/ed25519-multikey": "^1.1.0",
|
|
39
|
-
"@digitalbazaar/ed25519-signature-2020": "^5.
|
|
40
|
-
"@digitalbazaar/ezcap": "^4.
|
|
41
|
-
"@digitalbazaar/oid4-client": "^3.1
|
|
42
|
-
"@digitalbazaar/vc": "^
|
|
40
|
+
"@digitalbazaar/ed25519-signature-2020": "^5.4.0",
|
|
41
|
+
"@digitalbazaar/ezcap": "^4.1.0",
|
|
42
|
+
"@digitalbazaar/oid4-client": "^3.4.1",
|
|
43
|
+
"@digitalbazaar/vc": "^7.0.0",
|
|
43
44
|
"assert-plus": "^1.0.0",
|
|
44
45
|
"bnid": "^3.0.0",
|
|
45
|
-
"body-parser": "^1.20.
|
|
46
|
+
"body-parser": "^1.20.2",
|
|
46
47
|
"cors": "^2.8.5",
|
|
47
|
-
"jose": "^
|
|
48
|
-
"jsonata": "^2.0.
|
|
49
|
-
"klona": "^2.0.
|
|
48
|
+
"jose": "^5.6.3",
|
|
49
|
+
"jsonata": "^2.0.5",
|
|
50
|
+
"klona": "^2.0.6"
|
|
50
51
|
},
|
|
51
52
|
"peerDependencies": {
|
|
52
53
|
"@bedrock/app-identity": "4.0.0",
|
|
53
|
-
"@bedrock/core": "^6.
|
|
54
|
-
"@bedrock/did-io": "^10.
|
|
55
|
-
"@bedrock/express": "^8.
|
|
56
|
-
"@bedrock/https-agent": "^4.
|
|
57
|
-
"@bedrock/mongodb": "^10.
|
|
58
|
-
"@bedrock/oauth2-verifier": "^2.
|
|
59
|
-
"@bedrock/service-agent": "^
|
|
60
|
-
"@bedrock/service-core": "^
|
|
54
|
+
"@bedrock/core": "^6.1.3",
|
|
55
|
+
"@bedrock/did-io": "^10.3.1",
|
|
56
|
+
"@bedrock/express": "^8.3.1",
|
|
57
|
+
"@bedrock/https-agent": "^4.1.0",
|
|
58
|
+
"@bedrock/mongodb": "^10.2.0",
|
|
59
|
+
"@bedrock/oauth2-verifier": "^2.1.0",
|
|
60
|
+
"@bedrock/service-agent": "^9.0.2",
|
|
61
|
+
"@bedrock/service-core": "^10.0.0",
|
|
61
62
|
"@bedrock/validation": "^7.1.0"
|
|
62
63
|
},
|
|
63
64
|
"directories": {
|
|
64
65
|
"lib": "./lib"
|
|
65
66
|
},
|
|
66
67
|
"devDependencies": {
|
|
67
|
-
"eslint": "^8.
|
|
68
|
-
"eslint-config-digitalbazaar": "^5.0
|
|
69
|
-
"eslint-plugin-jsdoc": "^
|
|
70
|
-
"eslint-plugin-unicorn": "^
|
|
71
|
-
"jsdoc": "^4.0.2",
|
|
72
|
-
"jsdoc-to-markdown": "^8.0.0"
|
|
68
|
+
"eslint": "^8.57.0",
|
|
69
|
+
"eslint-config-digitalbazaar": "^5.2.0",
|
|
70
|
+
"eslint-plugin-jsdoc": "^48.11.0",
|
|
71
|
+
"eslint-plugin-unicorn": "^55.0.0"
|
|
73
72
|
},
|
|
74
73
|
"engines": {
|
|
75
74
|
"node": ">=18"
|
|
@@ -4,6 +4,144 @@
|
|
|
4
4
|
import {MAX_ISSUER_INSTANCES} from '../lib/constants.js';
|
|
5
5
|
import {schemas} from '@bedrock/validation';
|
|
6
6
|
|
|
7
|
+
const VC_CONTEXT_1 = 'https://www.w3.org/2018/credentials/v1';
|
|
8
|
+
const VC_CONTEXT_2 = 'https://www.w3.org/ns/credentials/v2';
|
|
9
|
+
|
|
10
|
+
const vcContext = {
|
|
11
|
+
type: 'array',
|
|
12
|
+
minItems: 1,
|
|
13
|
+
// the first context must be the VC context
|
|
14
|
+
items: [{
|
|
15
|
+
oneOf: [{
|
|
16
|
+
const: VC_CONTEXT_1
|
|
17
|
+
}, {
|
|
18
|
+
const: VC_CONTEXT_2
|
|
19
|
+
}]
|
|
20
|
+
}],
|
|
21
|
+
// additional contexts maybe strings or objects
|
|
22
|
+
additionalItems: {
|
|
23
|
+
anyOf: [{type: 'string'}, {type: 'object'}]
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
function idOrObjectWithId() {
|
|
28
|
+
return {
|
|
29
|
+
title: 'identifier or an object with an id',
|
|
30
|
+
anyOf: [
|
|
31
|
+
schemas.identifier(),
|
|
32
|
+
{
|
|
33
|
+
type: 'object',
|
|
34
|
+
required: ['id'],
|
|
35
|
+
additionalProperties: true,
|
|
36
|
+
properties: {id: schemas.identifier()}
|
|
37
|
+
}
|
|
38
|
+
]
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function verifiableCredential() {
|
|
43
|
+
return {
|
|
44
|
+
title: 'Verifiable Credential',
|
|
45
|
+
type: 'object',
|
|
46
|
+
required: [
|
|
47
|
+
'@context',
|
|
48
|
+
'credentialSubject',
|
|
49
|
+
'issuer',
|
|
50
|
+
'type'
|
|
51
|
+
],
|
|
52
|
+
additionalProperties: true,
|
|
53
|
+
properties: {
|
|
54
|
+
'@context': vcContext,
|
|
55
|
+
credentialSubject: {
|
|
56
|
+
anyOf: [
|
|
57
|
+
{type: 'object'},
|
|
58
|
+
{type: 'array', minItems: 1, items: {type: 'object'}}
|
|
59
|
+
]
|
|
60
|
+
},
|
|
61
|
+
id: {
|
|
62
|
+
type: 'string'
|
|
63
|
+
},
|
|
64
|
+
issuer: idOrObjectWithId(),
|
|
65
|
+
type: {
|
|
66
|
+
type: 'array',
|
|
67
|
+
minItems: 1,
|
|
68
|
+
// this first type must be VerifiableCredential
|
|
69
|
+
items: [
|
|
70
|
+
{const: 'VerifiableCredential'},
|
|
71
|
+
],
|
|
72
|
+
// additional types must be strings
|
|
73
|
+
additionalItems: {
|
|
74
|
+
type: 'string'
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
proof: schemas.proof()
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const envelopedVerifiableCredential = {
|
|
83
|
+
title: 'Enveloped Verifiable Credential',
|
|
84
|
+
type: 'object',
|
|
85
|
+
additionalProperties: true,
|
|
86
|
+
properties: {
|
|
87
|
+
'@context': {
|
|
88
|
+
const: VC_CONTEXT_2
|
|
89
|
+
},
|
|
90
|
+
id: {
|
|
91
|
+
type: 'string'
|
|
92
|
+
},
|
|
93
|
+
type: {
|
|
94
|
+
const: 'EnvelopedVerifiableCredential'
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
required: [
|
|
98
|
+
'@context',
|
|
99
|
+
'id',
|
|
100
|
+
'type'
|
|
101
|
+
]
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
export function verifiablePresentation() {
|
|
105
|
+
return {
|
|
106
|
+
title: 'Verifiable Presentation',
|
|
107
|
+
type: 'object',
|
|
108
|
+
required: ['@context', 'type'],
|
|
109
|
+
additionalProperties: true,
|
|
110
|
+
properties: {
|
|
111
|
+
'@context': vcContext,
|
|
112
|
+
id: {
|
|
113
|
+
type: 'string'
|
|
114
|
+
},
|
|
115
|
+
type: {
|
|
116
|
+
type: 'array',
|
|
117
|
+
minItems: 1,
|
|
118
|
+
// this first type must be VerifiablePresentation
|
|
119
|
+
items: [
|
|
120
|
+
{const: 'VerifiablePresentation'},
|
|
121
|
+
],
|
|
122
|
+
// additional types must be strings
|
|
123
|
+
additionalItems: {
|
|
124
|
+
type: 'string'
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
verifiableCredential: {
|
|
128
|
+
anyOf: [
|
|
129
|
+
verifiableCredential(),
|
|
130
|
+
envelopedVerifiableCredential, {
|
|
131
|
+
type: 'array',
|
|
132
|
+
minItems: 1,
|
|
133
|
+
items: {
|
|
134
|
+
anyOf: [verifiableCredential(), envelopedVerifiableCredential]
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
]
|
|
138
|
+
},
|
|
139
|
+
holder: idOrObjectWithId(),
|
|
140
|
+
proof: schemas.proof()
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
7
145
|
const credentialDefinition = {
|
|
8
146
|
title: 'OID4VCI Verifiable Credential Definition',
|
|
9
147
|
type: 'object',
|
|
@@ -19,7 +157,7 @@ const credentialDefinition = {
|
|
|
19
157
|
},
|
|
20
158
|
type: {
|
|
21
159
|
type: 'array',
|
|
22
|
-
minItems:
|
|
160
|
+
minItems: 1,
|
|
23
161
|
item: {
|
|
24
162
|
type: 'string'
|
|
25
163
|
}
|
|
@@ -27,7 +165,7 @@ const credentialDefinition = {
|
|
|
27
165
|
// allow `types` to be flexible for OID4VCI draft 20 implementers
|
|
28
166
|
types: {
|
|
29
167
|
type: 'array',
|
|
30
|
-
minItems:
|
|
168
|
+
minItems: 1,
|
|
31
169
|
item: {
|
|
32
170
|
type: 'string'
|
|
33
171
|
}
|
|
@@ -327,7 +465,7 @@ export function useExchangeBody() {
|
|
|
327
465
|
type: 'object',
|
|
328
466
|
additionalProperties: false,
|
|
329
467
|
properties: {
|
|
330
|
-
verifiablePresentation:
|
|
468
|
+
verifiablePresentation: verifiablePresentation()
|
|
331
469
|
}
|
|
332
470
|
};
|
|
333
471
|
}
|