@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 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, schemas, createValidateMiddleware as validate
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: schemas.verifiablePresentation()});
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
- const resolveKey = async protectedHeader => {
138
- const vm = await didIo.get({url: protectedHeader.kid});
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
- if(!(didDoc?.authentication?.some(e => e === vm.id || e.id === vm.id))) {
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
- // FIXME: support other key types
151
- const keyPair = await Ed25519Multikey.from(vm);
152
- const jwk = await Ed25519Multikey.toJwk({keyPair});
153
- jwk.alg = 'EdDSA';
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(!(verifyResult?.payload?.nonce === exchange.id)) {
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": "4.8.0",
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.2.0",
40
- "@digitalbazaar/ezcap": "^4.0.0",
41
- "@digitalbazaar/oid4-client": "^3.1.0",
42
- "@digitalbazaar/vc": "^6.0.1",
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.1",
46
+ "body-parser": "^1.20.2",
46
47
  "cors": "^2.8.5",
47
- "jose": "^4.10.4",
48
- "jsonata": "^2.0.3",
49
- "klona": "^2.0.5"
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.0.1",
54
- "@bedrock/did-io": "^10.0.0",
55
- "@bedrock/express": "^8.0.0",
56
- "@bedrock/https-agent": "^4.0.0",
57
- "@bedrock/mongodb": "^10.0.0",
58
- "@bedrock/oauth2-verifier": "^2.0.0",
59
- "@bedrock/service-agent": "^8.0.0",
60
- "@bedrock/service-core": "^9.0.0",
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.41.0",
68
- "eslint-config-digitalbazaar": "^5.0.1",
69
- "eslint-plugin-jsdoc": "^46.3.0",
70
- "eslint-plugin-unicorn": "^47.0.0",
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: 2,
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: 2,
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: schemas.verifiablePresentation()
468
+ verifiablePresentation: verifiablePresentation()
331
469
  }
332
470
  };
333
471
  }