@bedrock/vc-verifier 6.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.
@@ -0,0 +1,392 @@
1
+ /*!
2
+ * Copyright (c) 2020-2022 Digital Bazaar, Inc. All rights reserved.
3
+ */
4
+ import * as bedrock from '@bedrock/core';
5
+ import * as helpers from './helpers.js';
6
+ import {agent} from '@bedrock/https-agent';
7
+ import {createRequire} from 'module';
8
+ import {documentLoader as brDocLoader} from '@bedrock/jsonld-document-loader';
9
+ import {mockData} from './mock.data.js';
10
+ const require = createRequire(import.meta.url);
11
+ const {CapabilityAgent} = require('@digitalbazaar/webkms-client');
12
+ const didKeyDriver = require('@digitalbazaar/did-method-key').driver();
13
+ const {Ed25519Signature2020} = require('@digitalbazaar/ed25519-signature-2020');
14
+ const {httpClient} = require('@digitalbazaar/http-client');
15
+ const vc = require('@digitalbazaar/vc');
16
+
17
+ const {util: {clone}} = bedrock;
18
+
19
+ const {baseUrl} = mockData;
20
+ const serviceType = 'vc-verifier';
21
+
22
+ // NOTE: using embedded context in mockCredential:
23
+ // https://www.w3.org/2018/credentials/examples/v1
24
+ const mockCredential = require('./mock-credential');
25
+
26
+ describe('verify APIs', () => {
27
+ let capabilityAgent;
28
+ let verifierConfig;
29
+ let verifierId;
30
+ let rootZcap;
31
+ const zcaps = {};
32
+ beforeEach(async () => {
33
+ const secret = '53ad64ce-8e1d-11ec-bb12-10bf48838a41';
34
+ const handle = 'test';
35
+ capabilityAgent = await CapabilityAgent.fromSecret({secret, handle});
36
+
37
+ // create keystore for capability agent
38
+ const keystoreAgent = await helpers.createKeystoreAgent(
39
+ {capabilityAgent});
40
+
41
+ // create EDV for storage (creating hmac and kak in the process)
42
+ const {
43
+ edvConfig,
44
+ hmac,
45
+ keyAgreementKey
46
+ } = await helpers.createEdv({capabilityAgent, keystoreAgent});
47
+
48
+ // get service agent to delegate to
49
+ const serviceAgentUrl =
50
+ `${baseUrl}/service-agents/${encodeURIComponent(serviceType)}`;
51
+ const {data: serviceAgent} = await httpClient.get(serviceAgentUrl, {
52
+ agent
53
+ });
54
+
55
+ // delegate edv, hmac, and key agreement key zcaps to service agent
56
+ const {id: edvId} = edvConfig;
57
+ zcaps.edv = await helpers.delegate({
58
+ controller: serviceAgent.id,
59
+ delegator: capabilityAgent,
60
+ invocationTarget: edvId
61
+ });
62
+ const {keystoreId} = keystoreAgent;
63
+ zcaps.hmac = await helpers.delegate({
64
+ capability: `urn:zcap:root:${encodeURIComponent(keystoreId)}`,
65
+ controller: serviceAgent.id,
66
+ invocationTarget: hmac.id,
67
+ delegator: capabilityAgent
68
+ });
69
+ zcaps.keyAgreementKey = await helpers.delegate({
70
+ capability: `urn:zcap:root:${encodeURIComponent(keystoreId)}`,
71
+ controller: serviceAgent.id,
72
+ invocationTarget: keyAgreementKey.kmsId,
73
+ delegator: capabilityAgent
74
+ });
75
+
76
+ // create verifier instance
77
+ verifierConfig = await helpers.createConfig({capabilityAgent, zcaps});
78
+ verifierId = verifierConfig.id;
79
+ rootZcap = `urn:zcap:root:${encodeURIComponent(verifierId)}`;
80
+ });
81
+ describe('/challenges', () => {
82
+ it('create a challenge', async () => {
83
+ let err;
84
+ let result;
85
+ try {
86
+ result = await helpers.createChallenge({capabilityAgent, verifierId});
87
+ } catch(e) {
88
+ err = e;
89
+ }
90
+ assertNoError(err);
91
+ should.exist(result.data);
92
+ result.status.should.equal(200);
93
+ result.data.should.have.keys(['challenge']);
94
+ result.data.challenge.should.be.a('string');
95
+ });
96
+ });
97
+ describe('/credentials/verify', () => {
98
+ it('verifies a valid credential', async () => {
99
+ const verifiableCredential = clone(mockCredential);
100
+ let error;
101
+ let result;
102
+ try {
103
+ const zcapClient = helpers.createZcapClient({capabilityAgent});
104
+ result = await zcapClient.write({
105
+ url: `${verifierId}/credentials/verify`,
106
+ capability: rootZcap,
107
+ json: {
108
+ options: {
109
+ checks: ['proof'],
110
+ },
111
+ verifiableCredential
112
+ }
113
+ });
114
+ } catch(e) {
115
+ error = e;
116
+ }
117
+ assertNoError(error);
118
+ should.exist(result.data.verified);
119
+ result.data.verified.should.be.a('boolean');
120
+ result.data.verified.should.equal(true);
121
+ const {checks} = result.data;
122
+ checks.should.be.an('array');
123
+ checks.should.have.length(1);
124
+ const [check] = checks;
125
+ check.should.be.a('string');
126
+ check.should.equal('proof');
127
+ should.exist(result.data.results);
128
+ result.data.results.should.be.an('array');
129
+ result.data.results.should.have.length(1);
130
+ const [r] = result.data.results;
131
+ r.verified.should.be.a('boolean');
132
+ r.verified.should.equal(true);
133
+ });
134
+ it('does not verify an invalid credential', async () => {
135
+ const badCredential = clone(mockCredential);
136
+ // change the degree name
137
+ badCredential.credentialSubject.degree.name =
138
+ 'Bachelor of Science in Nursing';
139
+
140
+ let error;
141
+ let result;
142
+ try {
143
+ const zcapClient = helpers.createZcapClient({capabilityAgent});
144
+ result = await zcapClient.write({
145
+ url: `${verifierId}/credentials/verify`,
146
+ capability: rootZcap,
147
+ json: {
148
+ options: {
149
+ checks: ['proof'],
150
+ },
151
+ verifiableCredential: badCredential
152
+ }
153
+ });
154
+ } catch(e) {
155
+ error = e;
156
+ }
157
+ should.exist(error);
158
+ should.not.exist(result);
159
+ should.exist(error.data);
160
+ error.data.should.be.an('object');
161
+ error.data.verified.should.be.a('boolean');
162
+ error.data.verified.should.equal(false);
163
+ error.data.error.name.should.equal('VerificationError');
164
+ error.data.error.errors[0].message.should.equal('Invalid signature.');
165
+ });
166
+ });
167
+
168
+ describe('/presentations/verify', () => {
169
+ it('verifies a valid presentation', async () => {
170
+ // get signing key
171
+ const {methodFor} = await didKeyDriver.generate();
172
+ const signingKey = methodFor({purpose: 'assertionMethod'});
173
+ const suite = new Ed25519Signature2020({key: signingKey});
174
+
175
+ const verifiableCredential = clone(mockCredential);
176
+ const presentation = vc.createPresentation({
177
+ holder: 'did:test:foo',
178
+ id: 'urn:uuid:3e793029-d699-4096-8e74-5ebd956c3137',
179
+ verifiableCredential
180
+ });
181
+
182
+ // get challenge from verifier
183
+ const {data: {challenge}} = await helpers.createChallenge(
184
+ {capabilityAgent, verifierId});
185
+
186
+ await vc.signPresentation({
187
+ presentation,
188
+ suite,
189
+ challenge,
190
+ documentLoader: brDocLoader
191
+ });
192
+
193
+ let error;
194
+ let result;
195
+ try {
196
+ const zcapClient = helpers.createZcapClient({capabilityAgent});
197
+ result = await zcapClient.write({
198
+ url: `${verifierId}/presentations/verify`,
199
+ capability: rootZcap,
200
+ json: {
201
+ options: {
202
+ challenge,
203
+ checks: ['proof'],
204
+ },
205
+ verifiablePresentation: presentation
206
+ }
207
+ });
208
+ } catch(e) {
209
+ error = e;
210
+ }
211
+ assertNoError(error);
212
+ should.exist(result.data.checks);
213
+ const {checks} = result.data;
214
+ checks.should.be.an('array');
215
+ checks.should.have.length(1);
216
+ checks[0].should.be.a('string');
217
+ checks[0].should.equal('proof');
218
+ should.exist(result.data.verified);
219
+ result.data.verified.should.be.a('boolean');
220
+ result.data.verified.should.equal(true);
221
+ should.exist(result.data.presentationResult);
222
+ result.data.presentationResult.should.be.an('object');
223
+ should.exist(result.data.presentationResult.verified);
224
+ result.data.presentationResult.verified.should.be.a('boolean');
225
+ result.data.presentationResult.verified.should.equal(true);
226
+ should.exist(result.data.credentialResults);
227
+ const {data: {credentialResults}} = result;
228
+ credentialResults.should.be.an('array');
229
+ credentialResults.should.have.length(1);
230
+ const [credentialResult] = credentialResults;
231
+ should.exist(credentialResult.verified);
232
+ credentialResult.verified.should.be.a('boolean');
233
+ credentialResult.verified.should.equal(true);
234
+ });
235
+ it('returns an error if bad challenge is specified', async () => {
236
+ // get signing key
237
+ const {methodFor} = await didKeyDriver.generate();
238
+ const signingKey = methodFor({purpose: 'assertionMethod'});
239
+ const suite = new Ed25519Signature2020({key: signingKey});
240
+
241
+ const verifiableCredential = clone(mockCredential);
242
+ const presentation = vc.createPresentation({
243
+ holder: 'foo',
244
+ id: 'urn:uuid:3e793029-d699-4096-8e74-5ebd956c3137',
245
+ verifiableCredential
246
+ });
247
+
248
+ // expired / bad challenge
249
+ const challenge = 'z1A9b6RjuUzVWC3VcvsFX5fPb';
250
+
251
+ await vc.signPresentation({
252
+ presentation, suite, challenge, documentLoader: brDocLoader
253
+ });
254
+
255
+ let error;
256
+ let result;
257
+ try {
258
+ const zcapClient = helpers.createZcapClient({capabilityAgent});
259
+ result = await zcapClient.write({
260
+ url: `${verifierId}/presentations/verify`,
261
+ capability: rootZcap,
262
+ json: {
263
+ options: {
264
+ challenge,
265
+ checks: ['proof'],
266
+ },
267
+ verifiablePresentation: presentation
268
+ }
269
+ });
270
+ } catch(e) {
271
+ error = e;
272
+ }
273
+ should.exist(error);
274
+ should.exist(error.data);
275
+ should.not.exist(result);
276
+ error.data.should.be.an('object');
277
+ error.data.verified.should.be.a('boolean');
278
+ error.data.verified.should.equal(false);
279
+ error.data.error.message.should.equal('Invalid or expired challenge.');
280
+ error.data.error.name.should.equal('DataError');
281
+ });
282
+ it('returns an error if challenge is not specified', async () => {
283
+ // get signing key
284
+ const {methodFor} = await didKeyDriver.generate();
285
+ const signingKey = methodFor({purpose: 'assertionMethod'});
286
+ const suite = new Ed25519Signature2020({key: signingKey});
287
+
288
+ const verifiableCredential = clone(mockCredential);
289
+ const presentation = vc.createPresentation({
290
+ holder: 'foo',
291
+ id: 'urn:uuid:3e793029-d699-4096-8e74-5ebd956c3137',
292
+ verifiableCredential
293
+ });
294
+
295
+ // get challenge from verifier
296
+ const {data: {challenge}} = await helpers.createChallenge(
297
+ {capabilityAgent, verifierId});
298
+
299
+ await vc.signPresentation({
300
+ presentation, suite, challenge, documentLoader: brDocLoader
301
+ });
302
+
303
+ let error;
304
+ let result;
305
+ try {
306
+ const zcapClient = helpers.createZcapClient({capabilityAgent});
307
+ result = await zcapClient.write({
308
+ url: `${verifierId}/presentations/verify`,
309
+ capability: rootZcap,
310
+ json: {
311
+ options: {
312
+ // intentionally omit challenge
313
+ checks: ['proof'],
314
+ },
315
+ verifiablePresentation: presentation
316
+ }
317
+ });
318
+ } catch(e) {
319
+ error = e;
320
+ }
321
+ should.exist(error);
322
+ should.exist(error.data);
323
+ should.not.exist(result);
324
+ error.data.should.be.an('object');
325
+ error.data.verified.should.be.a('boolean');
326
+ error.data.verified.should.equal(false);
327
+ error.data.error.message.should.equal('"options.challenge" is required.');
328
+ error.data.error.name.should.equal('TypeError');
329
+ });
330
+ it('does not verify a presentation with a bad credential', async () => {
331
+ // get signing key
332
+ const {methodFor} = await didKeyDriver.generate();
333
+ const signingKey = methodFor({purpose: 'assertionMethod'});
334
+ const suite = new Ed25519Signature2020({key: signingKey});
335
+
336
+ const badCredential = clone(mockCredential);
337
+ // change the degree name
338
+ badCredential.credentialSubject.degree.name =
339
+ 'Bachelor of Science in Nursing';
340
+ const presentation = vc.createPresentation({
341
+ id: 'urn:uuid:3e793029-d699-4096-8e74-5ebd956c3137',
342
+ verifiableCredential: badCredential
343
+ });
344
+
345
+ // get challenge from verifier
346
+ const {data: {challenge}} = await helpers.createChallenge(
347
+ {capabilityAgent, verifierId});
348
+
349
+ await vc.signPresentation({
350
+ presentation, suite, challenge, documentLoader: brDocLoader
351
+ });
352
+
353
+ let error;
354
+ let result;
355
+ try {
356
+ const zcapClient = helpers.createZcapClient({capabilityAgent});
357
+ result = await zcapClient.write({
358
+ url: `${verifierId}/presentations/verify`,
359
+ capability: rootZcap,
360
+ json: {
361
+ options: {
362
+ challenge,
363
+ checks: ['proof'],
364
+ },
365
+ verifiablePresentation: presentation
366
+ }
367
+ });
368
+ } catch(e) {
369
+ error = e;
370
+ }
371
+ should.exist(error);
372
+ should.not.exist(result);
373
+ should.exist(error.data.checks);
374
+ const {checks} = error.data;
375
+ checks.should.be.an('array');
376
+ checks.should.have.length(1);
377
+ checks[0].should.be.an('object');
378
+ checks[0].check.should.eql(['proof']);
379
+ should.exist(error.data.verified);
380
+ error.data.verified.should.be.a('boolean');
381
+ error.data.verified.should.equal(false);
382
+ should.exist(error.data.error);
383
+ error.data.error.errors.should.be.an('array');
384
+ error.data.error.errors.should.have.length(1);
385
+ error.data.error.name.should.equal('VerificationError');
386
+ const e = error.data.error.errors[0];
387
+ e.should.be.an('object');
388
+ should.exist(e.name);
389
+ e.message.should.equal('Invalid signature.');
390
+ });
391
+ });
392
+ });