@sovrahq/waci 3.4.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/.eslintignore +2 -0
- package/.eslintrc.js +21 -0
- package/.prettierrc +4 -0
- package/LICENSE +201 -0
- package/dist/callbacks/index.d.ts +108 -0
- package/dist/callbacks/index.js +9 -0
- package/dist/callbacks/index.js.map +1 -0
- package/dist/constants/index.d.ts +1 -0
- package/dist/constants/index.js +5 -0
- package/dist/constants/index.js.map +1 -0
- package/dist/handlers/common/problem-report.handler.d.ts +4 -0
- package/dist/handlers/common/problem-report.handler.js +72 -0
- package/dist/handlers/common/problem-report.handler.js.map +1 -0
- package/dist/handlers/common/step-2-oob-invitation.handler.d.ts +4 -0
- package/dist/handlers/common/step-2-oob-invitation.handler.js +93 -0
- package/dist/handlers/common/step-2-oob-invitation.handler.js.map +1 -0
- package/dist/handlers/decorators/register-handler.decorator.d.ts +2 -0
- package/dist/handlers/decorators/register-handler.decorator.js +13 -0
- package/dist/handlers/decorators/register-handler.decorator.js.map +1 -0
- package/dist/handlers/index.d.ts +6 -0
- package/dist/handlers/index.js +11 -0
- package/dist/handlers/index.js.map +1 -0
- package/dist/handlers/issuance/step-3-propose-credential.handler.d.ts +32 -0
- package/dist/handlers/issuance/step-3-propose-credential.handler.js +180 -0
- package/dist/handlers/issuance/step-3-propose-credential.handler.js.map +1 -0
- package/dist/handlers/issuance/step-4-1-offer-credential-proceed.handler.d.ts +5 -0
- package/dist/handlers/issuance/step-4-1-offer-credential-proceed.handler.js +152 -0
- package/dist/handlers/issuance/step-4-1-offer-credential-proceed.handler.js.map +1 -0
- package/dist/handlers/issuance/step-4-offer-credential.handler.d.ts +5 -0
- package/dist/handlers/issuance/step-4-offer-credential.handler.js +169 -0
- package/dist/handlers/issuance/step-4-offer-credential.handler.js.map +1 -0
- package/dist/handlers/issuance/step-5-request-credential.handler.d.ts +4 -0
- package/dist/handlers/issuance/step-5-request-credential.handler.js +275 -0
- package/dist/handlers/issuance/step-5-request-credential.handler.js.map +1 -0
- package/dist/handlers/issuance/step-6-issue-credential.handler.d.ts +4 -0
- package/dist/handlers/issuance/step-6-issue-credential.handler.js +106 -0
- package/dist/handlers/issuance/step-6-issue-credential.handler.js.map +1 -0
- package/dist/handlers/issuance/step-7-ack-message.handler.d.ts +4 -0
- package/dist/handlers/issuance/step-7-ack-message.handler.js +78 -0
- package/dist/handlers/issuance/step-7-ack-message.handler.js.map +1 -0
- package/dist/handlers/presentation/step-3-propose-presentation.handler.d.ts +5 -0
- package/dist/handlers/presentation/step-3-propose-presentation.handler.js +107 -0
- package/dist/handlers/presentation/step-3-propose-presentation.handler.js.map +1 -0
- package/dist/handlers/presentation/step-4-1-presentation-proceed.handler.d.ts +5 -0
- package/dist/handlers/presentation/step-4-1-presentation-proceed.handler.js +130 -0
- package/dist/handlers/presentation/step-4-1-presentation-proceed.handler.js.map +1 -0
- package/dist/handlers/presentation/step-4-request-presentation.handler.d.ts +5 -0
- package/dist/handlers/presentation/step-4-request-presentation.handler.js +151 -0
- package/dist/handlers/presentation/step-4-request-presentation.handler.js.map +1 -0
- package/dist/handlers/presentation/step-5-present-proof.handler.d.ts +4 -0
- package/dist/handlers/presentation/step-5-present-proof.handler.js +211 -0
- package/dist/handlers/presentation/step-5-present-proof.handler.js.map +1 -0
- package/dist/handlers/presentation/step-6-ack-message.handler.d.ts +4 -0
- package/dist/handlers/presentation/step-6-ack-message.handler.js +72 -0
- package/dist/handlers/presentation/step-6-ack-message.handler.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +37 -0
- package/dist/index.js.map +1 -0
- package/dist/services/waci-interpreter.d.ts +17 -0
- package/dist/services/waci-interpreter.js +222 -0
- package/dist/services/waci-interpreter.js.map +1 -0
- package/dist/types/actor.d.ts +5 -0
- package/dist/types/actor.js +10 -0
- package/dist/types/actor.js.map +1 -0
- package/dist/types/credential-application.d.ts +36 -0
- package/dist/types/credential-application.js +3 -0
- package/dist/types/credential-application.js.map +1 -0
- package/dist/types/credential-manifest.d.ts +173 -0
- package/dist/types/credential-manifest.js +3 -0
- package/dist/types/credential-manifest.js.map +1 -0
- package/dist/types/index.d.ts +4 -0
- package/dist/types/index.js +21 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/problem-report.d.ts +13 -0
- package/dist/types/problem-report.js +18 -0
- package/dist/types/problem-report.js.map +1 -0
- package/dist/types/waci-message.d.ts +106 -0
- package/dist/types/waci-message.js +106 -0
- package/dist/types/waci-message.js.map +1 -0
- package/dist/utils/erros.d.ts +15 -0
- package/dist/utils/erros.js +42 -0
- package/dist/utils/erros.js.map +1 -0
- package/dist/utils/index.d.ts +22 -0
- package/dist/utils/index.js +356 -0
- package/dist/utils/index.js.map +1 -0
- package/jest.config.json +17 -0
- package/package.json +39 -0
- package/readme.md +1 -0
- package/src/callbacks/index.ts +75 -0
- package/src/constants/index.ts +1 -0
- package/src/handlers/common/problem-report.handler.ts +15 -0
- package/src/handlers/common/step-2-oob-invitation.handler.ts +45 -0
- package/src/handlers/decorators/register-handler.decorator.ts +10 -0
- package/src/handlers/index.ts +7 -0
- package/src/handlers/issuance/step-3-propose-credential.handler.ts +186 -0
- package/src/handlers/issuance/step-4-1-offer-credential-proceed.handler.ts +129 -0
- package/src/handlers/issuance/step-4-offer-credential.handler.ts +137 -0
- package/src/handlers/issuance/step-5-request-credential.handler.ts +205 -0
- package/src/handlers/issuance/step-6-issue-credential.handler.ts +63 -0
- package/src/handlers/issuance/step-7-ack-message.handler.ts +21 -0
- package/src/handlers/presentation/step-3-propose-presentation.handler.ts +67 -0
- package/src/handlers/presentation/step-4-1-presentation-proceed.handler.ts +100 -0
- package/src/handlers/presentation/step-4-request-presentation.handler.ts +115 -0
- package/src/handlers/presentation/step-5-present-proof.handler.ts +159 -0
- package/src/handlers/presentation/step-6-ack-message.handler.ts +15 -0
- package/src/index.ts +18 -0
- package/src/services/waci-interpreter.ts +161 -0
- package/src/types/actor.ts +5 -0
- package/src/types/credential-application.ts +38 -0
- package/src/types/credential-manifest.ts +184 -0
- package/src/types/index.ts +4 -0
- package/src/types/problem-report.ts +29 -0
- package/src/types/waci-message.ts +148 -0
- package/src/utils/erros.ts +21 -0
- package/src/utils/index.ts +272 -0
- package/test/handlers/issuance/step-3-propose-credential.handler.spec.ts +43 -0
- package/test/handlers/issuance/step-4-offer-credential.handler.spec.ts +53 -0
- package/test/handlers/issuance/step-5-request-credential.handler.spec.ts +102 -0
- package/test/handlers/presentation/step-5-present-proof.handler.spec.ts +142 -0
- package/test/handlers/shared/step-2-oob-invitation.handler.spec.ts +55 -0
- package/test/stubs/index.ts +842 -0
- package/test/waci-interpreter.spec.ts +113 -0
- package/tsconfig.build.json +9 -0
- package/tsconfig.json +20 -0
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import * as UUID from 'uuid';
|
|
2
|
+
import * as jsonpath from 'jsonpath';
|
|
3
|
+
import * as jsonschema from 'jsonschema';
|
|
4
|
+
import {
|
|
5
|
+
CredentialApplication,
|
|
6
|
+
CredentialPresentation,
|
|
7
|
+
PresentationDefinition,
|
|
8
|
+
WACIMessage,
|
|
9
|
+
} from '../types';
|
|
10
|
+
import { Callback } from '../callbacks';
|
|
11
|
+
import { InputDescriptorError } from './erros';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Extract the claim key from a JSONPath expression.
|
|
15
|
+
* E.g. "$.credentialSubject.name" → "name", "$.type" → "type"
|
|
16
|
+
*/
|
|
17
|
+
export const extractClaimKeyFromPath = (path: string): string => {
|
|
18
|
+
const segments = path.replace(/^\$\.?/, '').split('.');
|
|
19
|
+
return segments[segments.length - 1];
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Parse an SD-JWT string and extract the disclosed claim names as a Set.
|
|
24
|
+
* SD-JWT format: <JWT>~<disclosure1>~<disclosure2>~...~[<KB-JWT>]
|
|
25
|
+
* Each disclosure is base64url-encoded JSON array: [salt, claimName, claimValue]
|
|
26
|
+
*/
|
|
27
|
+
export const extractSDJWTClaims = (sdJwtString: string): Set<string> => {
|
|
28
|
+
const claims = new Set<string>();
|
|
29
|
+
try {
|
|
30
|
+
const parts = sdJwtString.split('~');
|
|
31
|
+
// Skip first part (JWT) and filter out empty strings and potential KB-JWT (has dots)
|
|
32
|
+
const disclosures = parts.slice(1).filter(p => p.length > 0);
|
|
33
|
+
for (const disclosure of disclosures) {
|
|
34
|
+
// Skip if it looks like a JWT (KB-JWT)
|
|
35
|
+
if (disclosure.split('.').length === 3) continue;
|
|
36
|
+
try {
|
|
37
|
+
// Base64url decode
|
|
38
|
+
let base64 = disclosure.replace(/-/g, '+').replace(/_/g, '/');
|
|
39
|
+
while (base64.length % 4 !== 0) base64 += '=';
|
|
40
|
+
const decoded = Buffer.from(base64, 'base64').toString('utf-8');
|
|
41
|
+
const array = JSON.parse(decoded);
|
|
42
|
+
if (Array.isArray(array) && array.length >= 2) {
|
|
43
|
+
claims.add(array[1]); // claimName is second element
|
|
44
|
+
}
|
|
45
|
+
} catch {
|
|
46
|
+
// Skip malformed disclosures
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
} catch {
|
|
50
|
+
// Return empty set on any error
|
|
51
|
+
}
|
|
52
|
+
return claims;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export const getObjectValues = (object: any): string[] =>
|
|
56
|
+
Object.values<string>(object);
|
|
57
|
+
|
|
58
|
+
export const createUUID = UUID.v4;
|
|
59
|
+
|
|
60
|
+
export const verifyPresentation = async (
|
|
61
|
+
presentationDefinition: PresentationDefinition,
|
|
62
|
+
credentialApplication: CredentialApplication | CredentialPresentation,
|
|
63
|
+
verificationCallback: Callback<any, { result: boolean, error?: string[] }>,
|
|
64
|
+
): Promise<any> => {
|
|
65
|
+
try {
|
|
66
|
+
const vcs: any[] = [];
|
|
67
|
+
for await (const inputDescriptor of presentationDefinition.input_descriptors) {
|
|
68
|
+
|
|
69
|
+
const vcInput =
|
|
70
|
+
credentialApplication.data.json.presentation_submission.descriptor_map.find(
|
|
71
|
+
(descriptor) => inputDescriptor.id === descriptor.id,
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
if (!vcInput) return new InputDescriptorError();
|
|
75
|
+
const vc = jsonpath.query(
|
|
76
|
+
credentialApplication.data.json,
|
|
77
|
+
vcInput.path,
|
|
78
|
+
)[0];
|
|
79
|
+
|
|
80
|
+
vcs.push(vc);
|
|
81
|
+
|
|
82
|
+
// SD-JWT: if VC is a string (e.g. SD-JWT compact), verify signature then validate disclosed fields
|
|
83
|
+
if (typeof vc === 'string') {
|
|
84
|
+
const verificationResult = await verificationCallback(vc);
|
|
85
|
+
console.log('---- Verification Result (SD-JWT string) -----', verificationResult);
|
|
86
|
+
if (!verificationResult.result) {
|
|
87
|
+
return {
|
|
88
|
+
result: false,
|
|
89
|
+
error: verificationResult.error || ['Credential verification failed'],
|
|
90
|
+
vcs
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
// Validate that required fields from constraints are present in SD-JWT disclosures
|
|
94
|
+
if (inputDescriptor.constraints?.fields) {
|
|
95
|
+
const disclosedClaims = extractSDJWTClaims(vc);
|
|
96
|
+
for (const field of inputDescriptor.constraints.fields) {
|
|
97
|
+
const pathKey = extractClaimKeyFromPath(field.path[0]);
|
|
98
|
+
if (!disclosedClaims.has(pathKey)) {
|
|
99
|
+
return {
|
|
100
|
+
result: false,
|
|
101
|
+
error: [{ name: 'missing-field', description: `SD-JWT missing required disclosure: ${pathKey}` }],
|
|
102
|
+
vcs,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Verify fields
|
|
111
|
+
for (const field of inputDescriptor.constraints.fields) {
|
|
112
|
+
const fieldValue = jsonpath.query(vc, field.path[0])?.[0];
|
|
113
|
+
if (!fieldValue) {
|
|
114
|
+
return { result: false, error: [{ name: 'missing-field', description: `Missing required field: ${field.path[0]}` }], vcs };
|
|
115
|
+
}
|
|
116
|
+
if (field.filter) {
|
|
117
|
+
const { errors } = jsonschema.validate(fieldValue, field.filter);
|
|
118
|
+
if (errors.length) {
|
|
119
|
+
return { result: false, error: [{ name: 'invalid-field', description: `Field ${field.path[0]} does not match filter` }], vcs };
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Verify proof
|
|
125
|
+
const verificationResult = await verificationCallback(vc);
|
|
126
|
+
console.log('---- Verification Result -----', verificationResult);
|
|
127
|
+
|
|
128
|
+
if (!verificationResult.result) {
|
|
129
|
+
const error = verificationResult.error as any;
|
|
130
|
+
switch (error.name) {
|
|
131
|
+
case 'did-document-resolution-error':
|
|
132
|
+
// DIDDocumentResolutionError
|
|
133
|
+
console.log(`Cannot resolve DID document: ${error.did}`);
|
|
134
|
+
// Handle DID resolution failure
|
|
135
|
+
break;
|
|
136
|
+
|
|
137
|
+
case 'vc-invalid-signature':
|
|
138
|
+
// InvalidSignatureError
|
|
139
|
+
console.log('Invalid signature detected');
|
|
140
|
+
console.log('Description:', error.description);
|
|
141
|
+
// Handle signature validation failure
|
|
142
|
+
break;
|
|
143
|
+
|
|
144
|
+
case 'verification-method-not-found':
|
|
145
|
+
// VerificationMethodNotFound
|
|
146
|
+
console.log(`Verification method ${error.verificationMethod} not found in DID Document: ${error.did}`);
|
|
147
|
+
// Handle missing verification method
|
|
148
|
+
break;
|
|
149
|
+
|
|
150
|
+
case 'verification-relationship-invalid':
|
|
151
|
+
// VerificationRelationshipError
|
|
152
|
+
console.log(`Verification method ${error.verificationMethod} is not configured as ${error.expectedVerificationRelationship}`);
|
|
153
|
+
// Handle incorrect verification relationship
|
|
154
|
+
break;
|
|
155
|
+
|
|
156
|
+
case 'unexpected-challenge':
|
|
157
|
+
// UnexpectedChallengeError
|
|
158
|
+
console.log('Unexpected challenge error:', error.errorMessage);
|
|
159
|
+
// Handle challenge validation failure
|
|
160
|
+
break;
|
|
161
|
+
|
|
162
|
+
case 'authentication-purpose-challenge-required':
|
|
163
|
+
// AuthenticationPurposeChallengeRequired
|
|
164
|
+
console.log('Authentication purpose requires a challenge');
|
|
165
|
+
// Handle missing challenge for authentication
|
|
166
|
+
break;
|
|
167
|
+
|
|
168
|
+
case 'verifiable-credential-revoked':
|
|
169
|
+
// VerifiableCredentialRevoked
|
|
170
|
+
console.log('Credential has been revoked');
|
|
171
|
+
console.log('Revocation details:', error.errors);
|
|
172
|
+
return {
|
|
173
|
+
result: false,
|
|
174
|
+
error: verificationResult.error || ['Credential verification failed'],
|
|
175
|
+
vcs:vcs
|
|
176
|
+
};
|
|
177
|
+
// Handle revoked credential
|
|
178
|
+
break;
|
|
179
|
+
|
|
180
|
+
case 'verifiable-credential-suspended':
|
|
181
|
+
// VerifiableCredentialSuspended
|
|
182
|
+
console.log('Credential has been suspended');
|
|
183
|
+
console.log('Suspension details:', error.errors);
|
|
184
|
+
return {
|
|
185
|
+
result: false,
|
|
186
|
+
error: verificationResult.error || ['Credential verification failed'],
|
|
187
|
+
vcs:vcs
|
|
188
|
+
};
|
|
189
|
+
// Handle suspended credential
|
|
190
|
+
break;
|
|
191
|
+
|
|
192
|
+
case 'credential-status-service-error':
|
|
193
|
+
// CredentialStatusServiceError
|
|
194
|
+
console.log(`Error retrieving credential status from: ${error.endpoint}`);
|
|
195
|
+
console.log(`HTTP Status: ${error.httpStatusResult}`);
|
|
196
|
+
console.log(`Response Data: ${error.dataResult}`);
|
|
197
|
+
// Handle credential status service failure
|
|
198
|
+
break;
|
|
199
|
+
|
|
200
|
+
case 'verifiable-credential-expired':
|
|
201
|
+
// VerifiableCredentialExpired
|
|
202
|
+
console.log('Credential has expired');
|
|
203
|
+
return {
|
|
204
|
+
result: false,
|
|
205
|
+
error: verificationResult.error || ['Credential verification failed'],
|
|
206
|
+
vcs:vcs
|
|
207
|
+
};
|
|
208
|
+
// Handle expired credential
|
|
209
|
+
break;
|
|
210
|
+
|
|
211
|
+
case 'unhandled-vc-suite-error':
|
|
212
|
+
// UnhandledVCSuiteError
|
|
213
|
+
console.log('Unhandled VC suite error:', error.messageError);
|
|
214
|
+
// Handle unexpected verification errors
|
|
215
|
+
break;
|
|
216
|
+
|
|
217
|
+
default:
|
|
218
|
+
console.log('Unknown error type:', error.name);
|
|
219
|
+
console.log('Error description:', error.description);
|
|
220
|
+
console.log('Error code:', error.code);
|
|
221
|
+
// Handle unknown errors
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return {
|
|
226
|
+
result: true,
|
|
227
|
+
vcs
|
|
228
|
+
};
|
|
229
|
+
} catch (error) {
|
|
230
|
+
console.error(error);
|
|
231
|
+
return {
|
|
232
|
+
result: false, error, vcs: []
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
export const extractExpectedChallenge = (
|
|
238
|
+
presentationDefinitionMessage: WACIMessage,
|
|
239
|
+
): string => {
|
|
240
|
+
return presentationDefinitionMessage.attachments.find(
|
|
241
|
+
(attachment) => attachment?.data?.json?.options?.challenge,
|
|
242
|
+
).data.json.options.challenge;
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
export const validateVcByInputDescriptor = (vc, inputDescriptor): boolean => {
|
|
246
|
+
// SD-JWT: validate disclosed claims against required fields
|
|
247
|
+
if (typeof vc === 'string') {
|
|
248
|
+
if (!inputDescriptor.constraints?.fields) return true;
|
|
249
|
+
const disclosedClaims = extractSDJWTClaims(vc);
|
|
250
|
+
for (const field of inputDescriptor.constraints.fields) {
|
|
251
|
+
const pathKey = extractClaimKeyFromPath(field.path[0]);
|
|
252
|
+
if (!disclosedClaims.has(pathKey)) return false;
|
|
253
|
+
}
|
|
254
|
+
return true;
|
|
255
|
+
}
|
|
256
|
+
for (const field of inputDescriptor.constraints.fields) {
|
|
257
|
+
const fieldValues = field.path?.map((path) => {
|
|
258
|
+
return jsonpath.value(vc, path);
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
for (const value of fieldValues) {
|
|
262
|
+
if (!value) return false;
|
|
263
|
+
if (field.filter) {
|
|
264
|
+
const { errors } = jsonschema.validate(value, field.filter);
|
|
265
|
+
if (errors.length) {
|
|
266
|
+
return false;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
return true;
|
|
272
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import {
|
|
2
|
+
WACIMessage,
|
|
3
|
+
WACIMessageHandlerResponse,
|
|
4
|
+
WACIMessageResponseType,
|
|
5
|
+
WACIMessageType,
|
|
6
|
+
} from '../../../src';
|
|
7
|
+
import {
|
|
8
|
+
credentialManifestParamsStub,
|
|
9
|
+
offerCredentialMessageStub1,
|
|
10
|
+
} from '../../stubs';
|
|
11
|
+
import { ProposeCredentialHandler } from '../../../src/handlers/issuance/step-3-propose-credential.handler';
|
|
12
|
+
|
|
13
|
+
jest.mock('../../../src/utils', () => ({
|
|
14
|
+
extractSuffixFromUUID:
|
|
15
|
+
jest.requireActual('../../../src/utils').extractSuffixFromUUID,
|
|
16
|
+
verifyPresentation:
|
|
17
|
+
jest.requireActual('../../../src/utils').verifyPresentation,
|
|
18
|
+
createUUID: (): string => 'f137e0db-db7b-4776-9530-83c808a34a42',
|
|
19
|
+
}));
|
|
20
|
+
|
|
21
|
+
describe('ProposeCredentialHandler', () => {
|
|
22
|
+
const message: WACIMessage = {
|
|
23
|
+
type: WACIMessageType.ProposeCredential,
|
|
24
|
+
id: 'f137e0db-db7b-4776-9530-83c808a34a42',
|
|
25
|
+
from: 'did:example:holder',
|
|
26
|
+
pthid: 'f137e0db-db7b-4776-9530-83c808a34a42',
|
|
27
|
+
to: ['did:example:issuer'],
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const callbacks = {
|
|
31
|
+
issuer: { getCredentialManifest: () => credentialManifestParamsStub },
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const handler = new ProposeCredentialHandler();
|
|
35
|
+
it('should return an offer credential message', async () => {
|
|
36
|
+
const response = await handler.handle([message], callbacks);
|
|
37
|
+
const expectedResponse: WACIMessageHandlerResponse = {
|
|
38
|
+
responseType: WACIMessageResponseType.ReplyThread,
|
|
39
|
+
message: offerCredentialMessageStub1,
|
|
40
|
+
};
|
|
41
|
+
expect(response).toEqual(expectedResponse);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import {
|
|
2
|
+
WACIMessageHandlerResponse,
|
|
3
|
+
WACIMessageResponseType,
|
|
4
|
+
} from '../../../src';
|
|
5
|
+
import {
|
|
6
|
+
credentialsToPresentStub,
|
|
7
|
+
offerCredentialMessageStub2,
|
|
8
|
+
requestCredentialMessageStub,
|
|
9
|
+
} from '../../stubs';
|
|
10
|
+
import { OfferCredentialHandler } from '../../../src/handlers/issuance/step-4-offer-credential.handler';
|
|
11
|
+
|
|
12
|
+
jest.mock('../../../src/utils', () => ({
|
|
13
|
+
extractSuffixFromUUID:
|
|
14
|
+
jest.requireActual('../../../src/utils').extractSuffixFromUUID,
|
|
15
|
+
verifyPresentation:
|
|
16
|
+
jest.requireActual('../../../src/utils').verifyPresentation,
|
|
17
|
+
createUUID: (): string => 'f137e0db-db7b-4776-9530-83c808a34a42',
|
|
18
|
+
}));
|
|
19
|
+
|
|
20
|
+
describe('OfferCredentialHandler', () => {
|
|
21
|
+
const callbacks: any = {
|
|
22
|
+
holder: {
|
|
23
|
+
getCredentialApplication: () => ({
|
|
24
|
+
credentialsToPresent: credentialsToPresentStub,
|
|
25
|
+
presentationProofTypes: [
|
|
26
|
+
'JsonWebSignature2020',
|
|
27
|
+
'EcdsaSecp256k1Signature2019',
|
|
28
|
+
],
|
|
29
|
+
}),
|
|
30
|
+
signPresentation: ({ contentToSign }) => ({
|
|
31
|
+
...contentToSign,
|
|
32
|
+
proof: {
|
|
33
|
+
type: 'Ed25519Signature2018',
|
|
34
|
+
verificationMethod: 'did:example:123#key-0',
|
|
35
|
+
created: '2021-05-14T20:16:29.565377',
|
|
36
|
+
proofPurpose: 'authentication',
|
|
37
|
+
challenge: 'f137e0db-db7b-4776-9530-83c808a34a42',
|
|
38
|
+
jws: 'eyJhbGciOiAiRWREU0EiLCAiYjY0IjogZmFsc2UsICJjcml0IjogWyJiNjQiXX0..7M9LwdJR1_SQayHIWVHF5eSSRhbVsrjQHKUrfRhRRrlbuKlggm8mm_4EI_kTPeBpalQWiGiyCb_0OWFPtn2wAQ',
|
|
39
|
+
},
|
|
40
|
+
}),
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const handler = new OfferCredentialHandler();
|
|
45
|
+
it('should return a request credential message', async () => {
|
|
46
|
+
const response = await handler.handle([offerCredentialMessageStub2], callbacks);
|
|
47
|
+
const expectedResponse: WACIMessageHandlerResponse = {
|
|
48
|
+
responseType: WACIMessageResponseType.ReplyThread,
|
|
49
|
+
message: requestCredentialMessageStub,
|
|
50
|
+
};
|
|
51
|
+
expect(response).toEqual(expectedResponse);
|
|
52
|
+
});
|
|
53
|
+
});
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import {
|
|
2
|
+
WACIMessageHandlerResponse,
|
|
3
|
+
WACIMessageResponseType,
|
|
4
|
+
WACIMessageType,
|
|
5
|
+
} from '../../../src';
|
|
6
|
+
import {
|
|
7
|
+
badCredentialApplicationStub,
|
|
8
|
+
credentialFulfillmentStub,
|
|
9
|
+
offerCredentialMessageStub1,
|
|
10
|
+
requestCredentialMessageStub,
|
|
11
|
+
credentialSignatureStub,
|
|
12
|
+
offerCredentialMessageStub2,
|
|
13
|
+
} from '../../stubs';
|
|
14
|
+
import { RequestCredentialHandler } from '../../../src/handlers/issuance/step-5-request-credential.handler';
|
|
15
|
+
|
|
16
|
+
jest.mock('../../../src/utils', () => ({
|
|
17
|
+
extractSuffixFromUUID:
|
|
18
|
+
jest.requireActual('../../../src/utils').extractSuffixFromUUID,
|
|
19
|
+
verifyPresentation:
|
|
20
|
+
jest.requireActual('../../../src/utils').verifyPresentation,
|
|
21
|
+
extractExpectedChallenge:
|
|
22
|
+
jest.requireActual('../../../src/utils').extractExpectedChallenge,
|
|
23
|
+
createUUID: (): string => 'f137e0db-db7b-4776-9530-83c808a34a42',
|
|
24
|
+
}));
|
|
25
|
+
|
|
26
|
+
describe('RequestCredentialHandler', () => {
|
|
27
|
+
const callbacks: any = {
|
|
28
|
+
issuer: {
|
|
29
|
+
signCredential: (vc) => ({ ...vc, proof: credentialSignatureStub }),
|
|
30
|
+
verifyCredential: async () => true,
|
|
31
|
+
verifyPresentation: async () => true,
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const handler = new RequestCredentialHandler();
|
|
36
|
+
|
|
37
|
+
it('should return a issue credential message when credential application is successful', async () => {
|
|
38
|
+
const response = await handler.handle([
|
|
39
|
+
offerCredentialMessageStub1,
|
|
40
|
+
requestCredentialMessageStub,
|
|
41
|
+
], callbacks);
|
|
42
|
+
const expectedResponse: WACIMessageHandlerResponse = {
|
|
43
|
+
responseType: WACIMessageResponseType.ReplyThread,
|
|
44
|
+
message: {
|
|
45
|
+
type: WACIMessageType.IssueCredential,
|
|
46
|
+
id: 'f137e0db-db7b-4776-9530-83c808a34a42',
|
|
47
|
+
thid: 'f137e0db-db7b-4776-9530-83c808a34a42',
|
|
48
|
+
from: 'did:example:issuer',
|
|
49
|
+
to: ['did:example:holder'],
|
|
50
|
+
body: {},
|
|
51
|
+
attachments: [credentialFulfillmentStub],
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
expect(response).toEqual(expectedResponse);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('should return a issue credential message when credential application is successful', async () => {
|
|
58
|
+
const response = await handler.handle([
|
|
59
|
+
offerCredentialMessageStub1,
|
|
60
|
+
{
|
|
61
|
+
type: WACIMessageType.RequestCredential,
|
|
62
|
+
id: 'f137e0db-db7b-4776-9530-83c808a34a42',
|
|
63
|
+
thid: 'f137e0db-db7b-4776-9530-83c808a34a42',
|
|
64
|
+
from: 'did:example:holder',
|
|
65
|
+
to: ['did:example:issuer'],
|
|
66
|
+
body: {},
|
|
67
|
+
attachments: [],
|
|
68
|
+
},
|
|
69
|
+
], callbacks);
|
|
70
|
+
const expectedResponse: WACIMessageHandlerResponse = {
|
|
71
|
+
responseType: WACIMessageResponseType.ReplyThread,
|
|
72
|
+
message: {
|
|
73
|
+
type: WACIMessageType.IssueCredential,
|
|
74
|
+
id: 'f137e0db-db7b-4776-9530-83c808a34a42',
|
|
75
|
+
thid: 'f137e0db-db7b-4776-9530-83c808a34a42',
|
|
76
|
+
from: 'did:example:issuer',
|
|
77
|
+
to: ['did:example:holder'],
|
|
78
|
+
body: {},
|
|
79
|
+
attachments: [credentialFulfillmentStub],
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
expect(response).toEqual(expectedResponse);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('should return undefined/void when credential application fails', async () => {
|
|
86
|
+
const failCallbacks: any = {
|
|
87
|
+
issuer: {
|
|
88
|
+
signCredential: (vc) => ({ ...vc, proof: credentialSignatureStub }),
|
|
89
|
+
verifyCredential: async () => false,
|
|
90
|
+
verifyPresentation: async () => false,
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
const response = await handler.handle([
|
|
94
|
+
offerCredentialMessageStub2,
|
|
95
|
+
{
|
|
96
|
+
...requestCredentialMessageStub,
|
|
97
|
+
attachments: [badCredentialApplicationStub],
|
|
98
|
+
},
|
|
99
|
+
], failCallbacks);
|
|
100
|
+
expect(response).toBe(undefined);
|
|
101
|
+
});
|
|
102
|
+
});
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import {
|
|
2
|
+
badPresentProofMessageStub,
|
|
3
|
+
presentProofMessageStub,
|
|
4
|
+
requestPresentationMessageStub,
|
|
5
|
+
} from '../../stubs';
|
|
6
|
+
import {
|
|
7
|
+
AckStatus,
|
|
8
|
+
WACIMessageHandlerResponse,
|
|
9
|
+
WACIMessageResponseType,
|
|
10
|
+
WACIMessageType,
|
|
11
|
+
} from '../../../src';
|
|
12
|
+
import { PresentProofHandler } from '../../../src/handlers/presentation/step-5-present-proof.handler';
|
|
13
|
+
|
|
14
|
+
jest.mock('../../../src/utils', () => ({
|
|
15
|
+
extractSuffixFromUUID:
|
|
16
|
+
jest.requireActual('../../../src/utils').extractSuffixFromUUID,
|
|
17
|
+
verifyPresentation:
|
|
18
|
+
jest.requireActual('../../../src/utils').verifyPresentation,
|
|
19
|
+
extractExpectedChallenge:
|
|
20
|
+
jest.requireActual('../../../src/utils').extractExpectedChallenge,
|
|
21
|
+
createUUID: (): string => 'f137e0db-db7b-4776-9530-83c808a34a42',
|
|
22
|
+
}));
|
|
23
|
+
|
|
24
|
+
describe('PresentProofHandler', () => {
|
|
25
|
+
const callbacks: any = {
|
|
26
|
+
verifier: {
|
|
27
|
+
getPresentationDefinition: async () => [],
|
|
28
|
+
verifyCredential: () => true,
|
|
29
|
+
verifyPresentation: () => true,
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const handler = new PresentProofHandler();
|
|
34
|
+
it('should return status OK', async () => {
|
|
35
|
+
const response = await handler.handle([
|
|
36
|
+
requestPresentationMessageStub,
|
|
37
|
+
presentProofMessageStub,
|
|
38
|
+
], callbacks);
|
|
39
|
+
const expectedResponse: WACIMessageHandlerResponse = {
|
|
40
|
+
responseType: WACIMessageResponseType.ReplyThread,
|
|
41
|
+
message: {
|
|
42
|
+
type: WACIMessageType.PresentationAck,
|
|
43
|
+
id: 'f137e0db-db7b-4776-9530-83c808a34a42',
|
|
44
|
+
thid: 'f137e0db-db7b-4776-9530-83c808a34a42',
|
|
45
|
+
from: 'did:example:issuer',
|
|
46
|
+
to: ['did:example:holder'],
|
|
47
|
+
body: {
|
|
48
|
+
status: AckStatus.Ok,
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
expect(response).toEqual(expectedResponse);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('should return status FAIL when wrong data is presented', async () => {
|
|
57
|
+
const failCallbacks: any = {
|
|
58
|
+
verifier: {
|
|
59
|
+
getPresentationDefinition: async () => [
|
|
60
|
+
{
|
|
61
|
+
id: 'ed7d9b1f-9eed-4bde-b81c-3aa7485cf947',
|
|
62
|
+
media_type: 'application/json',
|
|
63
|
+
format: 'dif/presentation-exchange/definitions@v1.0',
|
|
64
|
+
data: {
|
|
65
|
+
json: {
|
|
66
|
+
options: {
|
|
67
|
+
challenge: '3fa85f64-5717-4562-b3fc-2c963f66afa7',
|
|
68
|
+
domain: '4jt78h47fh47',
|
|
69
|
+
},
|
|
70
|
+
presentation_definition: {
|
|
71
|
+
id: '32f54163-7166-48f1-93d8-ff217bdb0654',
|
|
72
|
+
frame: {
|
|
73
|
+
'@context': [
|
|
74
|
+
'https://www.w3.org/2018/credentials/v1',
|
|
75
|
+
'https://w3id.org/vaccination/v1',
|
|
76
|
+
'https://w3id.org/security/suites/bls12381-2020/v1',
|
|
77
|
+
],
|
|
78
|
+
type: ['VerifiableCredential', 'VaccinationCertificate'],
|
|
79
|
+
credentialSubject: {
|
|
80
|
+
'@explicit': true,
|
|
81
|
+
type: ['VaccinationEvent'],
|
|
82
|
+
batchNumber: {},
|
|
83
|
+
countryOfVaccination: {},
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
input_descriptors: [
|
|
87
|
+
{
|
|
88
|
+
id: 'vaccination_input',
|
|
89
|
+
name: 'Vaccination Certificate',
|
|
90
|
+
constraints: {
|
|
91
|
+
fields: [
|
|
92
|
+
{
|
|
93
|
+
path: ['$.credentialSubject.batchNumber'],
|
|
94
|
+
filter: {
|
|
95
|
+
type: 'string',
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
path: ['$.credentialSubject.countryOfVaccination'],
|
|
100
|
+
filter: {
|
|
101
|
+
type: 'string',
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
],
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
],
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
],
|
|
113
|
+
verifyCredential: () => true,
|
|
114
|
+
verifyPresentation: () => true,
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const response2 = await handler.handle([
|
|
119
|
+
requestPresentationMessageStub,
|
|
120
|
+
{
|
|
121
|
+
...badPresentProofMessageStub,
|
|
122
|
+
attachments: [],
|
|
123
|
+
},
|
|
124
|
+
], failCallbacks);
|
|
125
|
+
|
|
126
|
+
const expectedResponse = {
|
|
127
|
+
message: {
|
|
128
|
+
body: {
|
|
129
|
+
status: 'FAIL',
|
|
130
|
+
},
|
|
131
|
+
from: 'did:example:issuer',
|
|
132
|
+
id: 'f137e0db-db7b-4776-9530-83c808a34a42',
|
|
133
|
+
thid: 'f137e0db-db7b-4776-9530-83c808a34a42',
|
|
134
|
+
to: ['did:example:holder'],
|
|
135
|
+
type: 'https://didcomm.org/present-proof/3.0/ack',
|
|
136
|
+
},
|
|
137
|
+
responseType: 1,
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
expect(response2).toEqual(expectedResponse);
|
|
141
|
+
});
|
|
142
|
+
});
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { GoalCode, WACIMessage, WACIMessageType } from '../../../src';
|
|
2
|
+
import { callbacks } from '../../../src/callbacks';
|
|
3
|
+
import { OOBInvitationHandler } from '../../../src/handlers/common/step-2-oob-invitation.handler';
|
|
4
|
+
|
|
5
|
+
describe('OobInvitationHandler', () => {
|
|
6
|
+
const message : WACIMessage = {
|
|
7
|
+
type: WACIMessageType.OutOfBandInvitation,
|
|
8
|
+
id: 'f137e0db-db7b-4776-9530-83c808a34a42',
|
|
9
|
+
from: 'did:example:issuer',
|
|
10
|
+
body: {
|
|
11
|
+
goal_code: GoalCode.Issuance,
|
|
12
|
+
accept: [ 'didcomm/v2' ],
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
Object.assign(callbacks, {
|
|
17
|
+
holder: {
|
|
18
|
+
getHolderDID: () => 'did:modena:1323232',
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('should return a propose credential message when handling an issuance invitation', async () => {
|
|
23
|
+
const response = await new OOBInvitationHandler().handle([ message ]);
|
|
24
|
+
|
|
25
|
+
expect(response.message.type).toEqual(WACIMessageType.ProposeCredential);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('should return a propose presentation message when handling a presentation invitation', async () => {
|
|
29
|
+
const presentationInvitation = {
|
|
30
|
+
...message,
|
|
31
|
+
body: {
|
|
32
|
+
...message.body,
|
|
33
|
+
goal_code: GoalCode.Presentation,
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const response = await new OOBInvitationHandler().handle([
|
|
38
|
+
presentationInvitation,
|
|
39
|
+
]);
|
|
40
|
+
|
|
41
|
+
expect(response.message.type).toEqual(WACIMessageType.ProposePresentation);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('should return a message whose parent thread id matches the invitation message id', async () => {
|
|
45
|
+
const response = await new OOBInvitationHandler().handle([ message ]);
|
|
46
|
+
|
|
47
|
+
expect(response.message.pthid).toEqual(message.id);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('should return a message whose receiver matches the sender from the handled message', async () => {
|
|
51
|
+
const response = await new OOBInvitationHandler().handle([ message ]);
|
|
52
|
+
|
|
53
|
+
expect(response.message.to[0]).toEqual(message.from);
|
|
54
|
+
});
|
|
55
|
+
});
|