@bedrock/vc-verifier 22.3.0 → 23.1.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/di.js +3 -3
- package/lib/documentLoader.js +8 -4
- package/lib/envelopes.js +5 -1
- package/lib/index.js +18 -1
- package/lib/status.js +35 -19
- package/lib/vcb.js +41 -6
- package/lib/verify.js +1 -1
- package/package.json +1 -1
package/lib/di.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Copyright (c) 2018-2025 Digital Bazaar, Inc. All rights reserved.
|
|
3
3
|
*/
|
|
4
4
|
import * as vc from '@digitalbazaar/vc';
|
|
5
|
-
import {
|
|
5
|
+
import {createCheckStatus} from './status.js';
|
|
6
6
|
import {createDocumentLoader} from './documentLoader.js';
|
|
7
7
|
import {createSuites} from './suites.js';
|
|
8
8
|
|
|
@@ -14,7 +14,7 @@ export async function verifyCredential({
|
|
|
14
14
|
|
|
15
15
|
// only check credential status when option is set
|
|
16
16
|
const checkStatus = checks.includes('credentialStatus') ?
|
|
17
|
-
|
|
17
|
+
createCheckStatus({config}) : () => ({verified: true});
|
|
18
18
|
|
|
19
19
|
const result = await vc.verifyCredential({
|
|
20
20
|
credential,
|
|
@@ -59,7 +59,7 @@ export async function verifyPresentation({
|
|
|
59
59
|
documentLoader: await createDocumentLoader({config}),
|
|
60
60
|
suite: createSuites(),
|
|
61
61
|
unsignedPresentation: !checks.includes('proof'),
|
|
62
|
-
checkStatus:
|
|
62
|
+
checkStatus: createCheckStatus({config}),
|
|
63
63
|
includeCredentials: true
|
|
64
64
|
};
|
|
65
65
|
return vc.verify(verifyOptions);
|
package/lib/documentLoader.js
CHANGED
|
@@ -46,10 +46,12 @@ bedrock.events.on('bedrock.init', () => {
|
|
|
46
46
|
*
|
|
47
47
|
* @param {object} options - The options to use.
|
|
48
48
|
* @param {object} options.config - The verifier instance config.
|
|
49
|
+
* @param {Set} [options.remoteUrlAllowList] - Remote URLs that are
|
|
50
|
+
* specifically allowed to be loaded (used for status list checks).
|
|
49
51
|
*
|
|
50
52
|
* @returns {Promise<Function>} The document loader.
|
|
51
53
|
*/
|
|
52
|
-
export async function createDocumentLoader({config} = {}) {
|
|
54
|
+
export async function createDocumentLoader({config, remoteUrlAllowList} = {}) {
|
|
53
55
|
const contextDocumentLoader = await createContextDocumentLoader(
|
|
54
56
|
{config, serviceType});
|
|
55
57
|
|
|
@@ -84,10 +86,12 @@ export async function createDocumentLoader({config} = {}) {
|
|
|
84
86
|
// try to resolve URL through context doc loader
|
|
85
87
|
return await contextDocumentLoader(url);
|
|
86
88
|
} catch(e) {
|
|
87
|
-
// use web loader if configured and instance config allows it
|
|
88
|
-
// the url starts with `http
|
|
89
|
+
// use web loader if configured and instance config allows it (or it is
|
|
90
|
+
// allowed by an allow list) and the url starts with `http` (and the core
|
|
91
|
+
// config allows it, i.e., `webLoader` exists)
|
|
89
92
|
const allowRemoteContexts = !config.verifyOptions?.documentLoader ||
|
|
90
|
-
config.verifyOptions.documentLoader.allowRemoteContexts
|
|
93
|
+
config.verifyOptions.documentLoader.allowRemoteContexts ||
|
|
94
|
+
remoteUrlAllowList?.has(url);
|
|
91
95
|
if(allowRemoteContexts &&
|
|
92
96
|
url.startsWith('http') && e.name === 'NotFoundError' && webLoader) {
|
|
93
97
|
return webLoader(url);
|
package/lib/envelopes.js
CHANGED
|
@@ -33,7 +33,7 @@ export async function verifyEnvelopedCredential({
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
export async function verifyEnvelopedPresentation({
|
|
36
|
-
envelopedPresentation, challenge, domain
|
|
36
|
+
config, envelopedPresentation, challenge, domain, checks
|
|
37
37
|
} = {}) {
|
|
38
38
|
let format;
|
|
39
39
|
try {
|
|
@@ -46,6 +46,10 @@ export async function verifyEnvelopedPresentation({
|
|
|
46
46
|
result = await vcjwt.verifyEnvelopedPresentation({
|
|
47
47
|
jwt: contents, challenge, domain
|
|
48
48
|
});
|
|
49
|
+
} else if(format.typeAndSubType === 'application/vcb') {
|
|
50
|
+
result = await vcb.verifyEnvelopedPresentation({
|
|
51
|
+
config, contents, format, challenge, checks
|
|
52
|
+
});
|
|
49
53
|
} else {
|
|
50
54
|
_throwUnknownFormat(format);
|
|
51
55
|
}
|
package/lib/index.js
CHANGED
|
@@ -21,7 +21,6 @@ bedrock.events.on('bedrock.init', async () => {
|
|
|
21
21
|
const updateConfigBody = structuredClone(schemas.updateConfigBody);
|
|
22
22
|
const schemasToUpdate = [createConfigBody, updateConfigBody];
|
|
23
23
|
for(const schema of schemasToUpdate) {
|
|
24
|
-
// verify options
|
|
25
24
|
schema.properties.verifyOptions = verifyOptions;
|
|
26
25
|
}
|
|
27
26
|
|
|
@@ -36,6 +35,7 @@ bedrock.events.on('bedrock.init', async () => {
|
|
|
36
35
|
validation: {
|
|
37
36
|
createConfigBody,
|
|
38
37
|
updateConfigBody,
|
|
38
|
+
validateConfigFn,
|
|
39
39
|
// require these zcaps (by reference ID)
|
|
40
40
|
zcapReferenceIds: [{
|
|
41
41
|
referenceId: 'edv',
|
|
@@ -66,3 +66,20 @@ bedrock.events.on('bedrock.init', async () => {
|
|
|
66
66
|
await initializeServiceAgent({serviceType});
|
|
67
67
|
});
|
|
68
68
|
});
|
|
69
|
+
|
|
70
|
+
async function validateConfigFn({config} = {}) {
|
|
71
|
+
try {
|
|
72
|
+
// set default `verifyOptions` if not given
|
|
73
|
+
const {verifyOptions} = config;
|
|
74
|
+
if(verifyOptions === undefined) {
|
|
75
|
+
config.verifyOptions = {
|
|
76
|
+
documentLoader: {
|
|
77
|
+
allowRemoteContexts: false
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
} catch(error) {
|
|
82
|
+
return {valid: false, error};
|
|
83
|
+
}
|
|
84
|
+
return {valid: true};
|
|
85
|
+
}
|
package/lib/status.js
CHANGED
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
statusTypeMatches as statusList2020StatusTypeMatches
|
|
15
15
|
} from '@digitalbazaar/vc-status-list';
|
|
16
16
|
import assert from 'assert-plus';
|
|
17
|
+
import {createDocumentLoader} from './documentLoader.js';
|
|
17
18
|
|
|
18
19
|
const handlerMap = new Map();
|
|
19
20
|
handlerMap.set('BitstringStatusListEntry', {
|
|
@@ -29,26 +30,41 @@ handlerMap.set('StatusList2021Entry', {
|
|
|
29
30
|
statusTypeMatches: statusList2020StatusTypeMatches
|
|
30
31
|
});
|
|
31
32
|
|
|
32
|
-
export
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
export function createCheckStatus({config} = {}) {
|
|
34
|
+
return async function checkStatus(options = {}) {
|
|
35
|
+
assert.object(options, 'options');
|
|
36
|
+
assert.object(options.credential, 'options.credential');
|
|
35
37
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
38
|
+
try {
|
|
39
|
+
const {credential} = options;
|
|
40
|
+
const {credentialStatus} = credential;
|
|
41
|
+
if(!credentialStatus) {
|
|
42
|
+
// no status to check
|
|
43
|
+
return {verified: true};
|
|
44
|
+
}
|
|
43
45
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
46
|
+
const handlers = handlerMap.get(credentialStatus.type);
|
|
47
|
+
if(!(handlers && handlers.statusTypeMatches({credential}))) {
|
|
48
|
+
throw new Error(
|
|
49
|
+
`Unsupported credentialStatus type "${credentialStatus.type}".`);
|
|
50
|
+
}
|
|
49
51
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
// document loader needs to only allow web loading of status
|
|
53
|
+
// list VCs, nothing else
|
|
54
|
+
const documentLoader = await createDocumentLoader({
|
|
55
|
+
config,
|
|
56
|
+
remoteUrlAllowList: new Set([
|
|
57
|
+
credentialStatus.statusListCredential ??
|
|
58
|
+
credentialStatus.revocationListCredential
|
|
59
|
+
])
|
|
60
|
+
});
|
|
61
|
+
options = {
|
|
62
|
+
...options,
|
|
63
|
+
documentLoader
|
|
64
|
+
};
|
|
65
|
+
return await handlers.checkStatus(options);
|
|
66
|
+
} catch(error) {
|
|
67
|
+
return {verified: false, error};
|
|
68
|
+
}
|
|
69
|
+
};
|
|
54
70
|
}
|
package/lib/vcb.js
CHANGED
|
@@ -29,6 +29,41 @@ const SUPPORTED_BARCODE_FORMATS = new Set([
|
|
|
29
29
|
|
|
30
30
|
const TEXT_DECODER = new TextDecoder();
|
|
31
31
|
|
|
32
|
+
export async function verifyEnvelopedPresentation({
|
|
33
|
+
config, contents, format, challenge, checks
|
|
34
|
+
} = {}) {
|
|
35
|
+
// handle base64 encoding
|
|
36
|
+
let {parameters} = format;
|
|
37
|
+
if(parameters.has('base64')) {
|
|
38
|
+
contents = new Uint8Array(Buffer.from(contents, 'base64'));
|
|
39
|
+
parameters = new Map(parameters);
|
|
40
|
+
parameters.delete('base64');
|
|
41
|
+
}
|
|
42
|
+
// only parameter understood is `barcode-format` with values of:
|
|
43
|
+
// 'qr_code' (default)
|
|
44
|
+
const barcodeFormat = parameters.size === 1 ?
|
|
45
|
+
parameters.get('barcode-format') : (parameters.size === 0 && 'qr_code');
|
|
46
|
+
if(barcodeFormat !== 'qr_code') {
|
|
47
|
+
_throwUnknownFormat(format);
|
|
48
|
+
}
|
|
49
|
+
// create loaders for JSON-LD contexts and CBOR-LD type tables
|
|
50
|
+
const documentLoader = await createDocumentLoader({config});
|
|
51
|
+
const typeTableLoader = await createCborldTypeTableLoader({
|
|
52
|
+
config, serviceType: 'vc-verifier'
|
|
53
|
+
});
|
|
54
|
+
// parse credential and any verification options from contents...
|
|
55
|
+
const {jsonldDocument: presentation, options} = await _parseQrCodeEnvelope({
|
|
56
|
+
contents, documentLoader, typeTableLoader, expectedHeader: 'VP1-'
|
|
57
|
+
});
|
|
58
|
+
// checks for VCB VPs only applies if there is actually a proof to check
|
|
59
|
+
checks = presentation.proof ? checks : [];
|
|
60
|
+
// verify VP
|
|
61
|
+
const result = await di.verifyPresentation({
|
|
62
|
+
config, presentation, options, challenge, checks
|
|
63
|
+
});
|
|
64
|
+
return {...result, presentation};
|
|
65
|
+
}
|
|
66
|
+
|
|
32
67
|
export async function verifyEnvelopedCredential({
|
|
33
68
|
config, contents, format, checks
|
|
34
69
|
} = {}) {
|
|
@@ -58,8 +93,8 @@ export async function verifyEnvelopedCredential({
|
|
|
58
93
|
let credential;
|
|
59
94
|
let options;
|
|
60
95
|
if(barcodeFormat === 'qr_code') {
|
|
61
|
-
({credential, options} = await _parseQrCodeEnvelope({
|
|
62
|
-
contents, documentLoader, typeTableLoader
|
|
96
|
+
({jsonldDocument: credential, options} = await _parseQrCodeEnvelope({
|
|
97
|
+
contents, documentLoader, typeTableLoader, expectedHeader: 'VC1-'
|
|
63
98
|
}));
|
|
64
99
|
}
|
|
65
100
|
if(barcodeFormat === 'pdf417') {
|
|
@@ -76,19 +111,19 @@ export async function verifyEnvelopedCredential({
|
|
|
76
111
|
}
|
|
77
112
|
|
|
78
113
|
async function _parseQrCodeEnvelope({
|
|
79
|
-
contents, documentLoader, typeTableLoader
|
|
114
|
+
contents, documentLoader, typeTableLoader, expectedHeader
|
|
80
115
|
}) {
|
|
81
116
|
// `fromQrCode` requires text, so convert to text as needed
|
|
82
117
|
if(contents instanceof Uint8Array) {
|
|
83
118
|
contents = TEXT_DECODER.decode(contents);
|
|
84
119
|
}
|
|
85
|
-
const {jsonldDocument
|
|
120
|
+
const {jsonldDocument} = await util.fromQrCode({
|
|
86
121
|
text: contents,
|
|
87
122
|
documentLoader,
|
|
88
123
|
typeTableLoader,
|
|
89
|
-
expectedHeader
|
|
124
|
+
expectedHeader
|
|
90
125
|
});
|
|
91
|
-
return {
|
|
126
|
+
return {jsonldDocument};
|
|
92
127
|
}
|
|
93
128
|
|
|
94
129
|
async function _parsePdf417Envelope({
|
package/lib/verify.js
CHANGED
|
@@ -74,7 +74,7 @@ export async function verifyPresentation({
|
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
const presentationResult = await verifyEnvelopedPresentation({
|
|
77
|
-
envelopedPresentation: presentation, challenge, domain
|
|
77
|
+
config, envelopedPresentation: presentation, challenge, domain, checks
|
|
78
78
|
});
|
|
79
79
|
// verify each `verifiableCredential` in the resulting VP
|
|
80
80
|
let verified = presentationResult.verified;
|