-
Notifications
You must be signed in to change notification settings - Fork 0
/
didcomm.dart
341 lines (301 loc) · 13 KB
/
didcomm.dart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
/*
This Example demonstrates the usage of this library with the following story:
Alice would buy a discounted Annual ticket for the Art museum. She gets the
discount because she is a student at an university. So she has to present
her student card to the museum.
After this the museum issues the Annual Ticket to Alice.
On a high level the process looks like this:
Alice Museum
<--request presentation (student-card)-------
---presentation (student card)-------------->
<--propose-credential (annual ticket)--------
---request-credential (annual ticket)------->
<--issue-credential (annual ticket)----------
*/
import 'dart:convert';
import 'package:dart_ssi/credentials.dart';
import 'package:dart_ssi/did.dart';
import 'package:dart_ssi/didcomm.dart';
import 'package:dart_ssi/util.dart';
import 'package:dart_ssi/wallet.dart';
import 'package:json_path/json_path.dart';
import 'package:json_schema/json_schema.dart';
import 'package:uuid/uuid.dart';
void main() async {
var alice = WalletStore('exampleData/didcomm/alice');
await alice.openBoxes('alicePassword');
await alice.initialize();
await _issueStudentCard(alice);
var museum = WalletStore('exampleData/didcomm/museum');
await museum.openBoxes('museumPassword');
await museum.initialize();
await museum.initializeIssuer(KeyType.ed25519);
await _issueBusinessId(museum);
// ****** Museum ********
var museumDid = await museum.getNextConnectionDID(KeyType.x25519);
var presentationDefinition =
PresentationDefinition(id: Uuid().v4(), inputDescriptors: [
InputDescriptor(
id: Uuid().v4(),
name: 'Studentcard-Descriptor',
constraints: InputDescriptorConstraints(fields: [
InputDescriptorField(
path: [JsonPath(r'$.type')],
filter: JsonSchema.create({
'type': 'array',
'contains': {'type': 'string', 'pattern': 'StudentCard'}
}))
])),
]);
var requestPresentationStudentCard =
RequestPresentation(presentationDefinition: [
PresentationDefinitionWithOptions(
domain: 'https://museum-of-modern-art.com',
challenge: Uuid().v4(),
presentationDefinition: presentationDefinition)
]);
var oob = OutOfBandMessage(from: museumDid, attachments: [
Attachment(
data: AttachmentData(json: requestPresentationStudentCard.toJson()))
]);
var oobUrl = oob.toUrl('http', 'museum-of-modern-art.com', 'somePath');
print(oob.attachments![0].data.json);
//This Message is rendered as QR-Code
// ***** ALICE *****
//Alice scans the QR-Code and decodes the message
var decodedOOB = oobMessageFromUrl(oobUrl);
//Alice resolves the Did-Document of the sender
var ddoMuseum = await resolveDidDocument(decodedOOB.from!);
//Alice converts the Did-Document in a form that is easier to use
ddoMuseum = ddoMuseum.resolveKeyIds();
ddoMuseum = ddoMuseum.convertAllKeysToJwk();
// Alice checks the Attachment and notice, that it is request-Presentation Message
RequestPresentation request;
try {
print(decodedOOB.attachments![0].data.json);
request =
RequestPresentation.fromJson(decodedOOB.attachments![0].data.json);
} catch (e) {
print('This is no RequestPresentation Message');
throw Exception(e);
}
//Alice searches her Wallet for matching credentials
var allCreds = alice.getAllCredentials();
List<VerifiableCredential> allW3CCreds = [];
for (var cred in allCreds.values) {
if (cred.w3cCredential != '') {
allW3CCreds.add(VerifiableCredential.fromJson(cred.w3cCredential));
}
}
var searchResult = searchCredentialsForPresentationDefinition(
allW3CCreds, request.presentationDefinition[0].presentationDefinition);
//Alice realize, that she should show her Student card and build verifiable Presentation out of it
var presentation = await buildPresentation(
searchResult, alice, request.presentationDefinition[0].challenge);
print(presentation);
//Now she puts this in a Presentation Message
var presentationMessage = Presentation(
verifiablePresentation: [VerifiablePresentation.fromJson(presentation)]);
//alice generates a did she encrypts the message with
var connectionDidAlice = await alice.getNextConnectionDID(KeyType.x25519);
var privateKey =
await alice.getPrivateKeyForConnectionDidAsJwk(connectionDidAlice);
var encryptedMessage = DidcommEncryptedMessage.fromPlaintext(
keyWrapAlgorithm: KeyWrapAlgorithm.ecdhES,
senderPrivateKeyJwk: privateKey!,
recipientPublicKeyJwk: [ddoMuseum.keyAgreement![0].publicKeyJwk],
plaintext: presentationMessage);
//This message she could send to the museum
//***** Museum *******
//The museum receives the Message. Because its anoncrypted, the museum could encrypt it without looking up a did-Document
var museumPrivateKey =
await museum.getPrivateKeyForConnectionDidAsJwk(museumDid);
var decrypted = await encryptedMessage.decrypt(museum);
//To send message back, the museum looks for the sender in protected Header skid value
Map<String, dynamic> decodedHeader = jsonDecode(utf8.decode(
base64Decode(addPaddingToBase64(encryptedMessage.protectedHeader))));
var senderKid = decodedHeader['skid'];
var sender = senderKid.split('#')[0];
print(sender);
var senderDDO = await resolveDidDocument(sender);
senderDDO = senderDDO.convertAllKeysToJwk();
senderDDO = senderDDO.resolveKeyIds();
//Normally a Wallet has to check which Type it gets here. For this example we know, that it is a plaintext-Massage
decrypted = decrypted as DidcommPlaintextMessage;
if (decrypted.type != 'https://didcomm.org/present-proof/3.0/presentation') {
throw Exception('Presentation Message expected');
}
var presentationMessageReceived = Presentation.fromJson(decrypted.toJson());
//verifyPresentation
var verified = await verifyPresentation(
presentationMessageReceived.verifiablePresentation[0].toJson(),
requestPresentationStudentCard.presentationDefinition[0].challenge);
if (!verified) throw Exception('Presentation could not been verified');
//check if the credential inside matches the presentation Definition
var result = searchCredentialsForPresentationDefinition([
presentationMessageReceived
.verifiablePresentation[0].verifiableCredential![0]
], presentationDefinition);
if (result.length != 1) throw Exception('Credential dont match definition');
//Now the Museum could start the issuance process for the annual ticket
var museumIssuerDid = museum.getStandardIssuerDid(KeyType.ed25519);
var credentialSubject = {
'id': 'did:key:00000',
'institution': 'Museum of modern Art',
'ticketType': 'Discounted Annual Ticket'
};
var offer = OfferCredential(detail: [
LdProofVcDetail(
credential: VerifiableCredential(
context: [
'https://www.w3.org/2018/credentials/v1',
'https://www.example.com/annualTicket/v1'
],
type: [
'VerifiableCredential',
'AnnualTicket'
],
credentialSubject: credentialSubject,
issuanceDate: DateTime.now(),
issuer: museumIssuerDid),
options: LdProofVcDetailOptions(proofType: 'Ed25519Signature2020'))
]);
var encryptedOffer = DidcommEncryptedMessage.fromPlaintext(
senderPrivateKeyJwk: museumPrivateKey!,
recipientPublicKeyJwk: [senderDDO.keyAgreement![0].publicKeyJwk],
plaintext: offer);
//This authcrypted Message is sent to alice
//**** Alice *******
//Alice decrypts the message (she know, that it is from the museum)
var decryptedOffer = encryptedOffer.decryptWithJwk(
privateKey, ddoMuseum.keyAgreement![0].publicKeyJwk);
//Here aswell we know that it is plaintext Message and has a type of offer-credential
var receivedOffer = OfferCredential.fromJson(decryptedOffer.toJson());
//Alice checks, if the did the credential should issued to is controlled by her
var did = receivedOffer.detail![0].credential.credentialSubject['id'];
print(did);
String? key;
try {
key = await alice.getPrivateKeyForCredentialDid(did);
} catch (e) {
print(e);
}
if (key == null) {
print('I do not control this did');
}
// in this case alice must sent a propose credential with a correct did
var vc = receivedOffer.detail![0].credential;
var aliceCredDid = await alice.getNextCredentialDID(KeyType.ed25519);
vc.credentialSubject['id'] = aliceCredDid;
var propose = ProposeCredential(detail: [
LdProofVcDetail(credential: vc, options: receivedOffer.detail![0].options)
]);
var encryptedPropose = DidcommEncryptedMessage.fromPlaintext(
senderPrivateKeyJwk: privateKey,
recipientPublicKeyJwk: [ddoMuseum.keyAgreement![0].publicKeyJwk],
plaintext: propose);
//This message could be sent to the museum
//***** Museum ****
//decrypt message and see, that it is an credential propose that do not differ much from the previous offer
// (not all checking steps are shown here because they are straight forward)
var decryptedPropose = encryptedPropose.decryptWithJwk(
museumPrivateKey, senderDDO.keyAgreement![0].publicKeyJwk);
var receivedPropose = ProposeCredential.fromJson(decryptedPropose.toJson());
//Therefore the museum construct a offer out of it
var offer2 = OfferCredential(detail: receivedPropose.detail!);
//This offer is encrypted like all messages before (not shown to avoid too much duplicated code) and sent to alice
//**** Alice ****
//Alice receives the offer and notice, that everything is fine now. So she can construct and send a Request-credential Message
var requestCredential = RequestCredential(detail: [
LdProofVcDetail(
credential: offer2.detail![0].credential,
options: LdProofVcDetailOptions(
proofType: 'Ed25519Signature2020', challenge: Uuid().v4()))
]);
//This is encrypted and sent
//**** Museum ****
//Takes credential from Request, checks if everything is fine and signs it
var signed = await signCredential(
museum, requestCredential.detail![0].credential.toJson(),
challenge: requestCredential.detail![0].options.challenge);
print(signed);
//construct a issue credential message and sent credential to alice
var issueMessage =
IssueCredential(credentials: [VerifiableCredential.fromJson(signed)]);
//***** Alice *****
var receivedVC = issueMessage.credentials![0];
//verify received credential
print(await verifyCredential(receivedVC,
expectedChallenge: requestCredential.detail![0].options.challenge));
//store credential
var path = alice.getCredential(aliceCredDid)!.hdPath;
await alice.storeCredential(receivedVC.toString(), '', path,
keyType: KeyType.ed25519);
//check if two creds are there
var aliceAllCreds = alice.getAllCredentials();
print(aliceAllCreds.length);
print(aliceAllCreds);
}
Future<void> _issueStudentCard(WalletStore wallet) async {
var someUniversity = WalletStore('example/didcomm/someUniversity');
await someUniversity.openBoxes('password');
await someUniversity.initialize();
await someUniversity.initializeIssuer(KeyType.ed25519);
var issuerDid = someUniversity.getStandardIssuerDid(KeyType.ed25519);
var holderDid = await wallet.getNextCredentialDID(KeyType.ed25519);
var studentCard = {
'id': holderDid,
'familyName': 'Schmidt',
'givenName': 'Alice',
'matriculationNumber': ' 5426745'
};
var cred = VerifiableCredential(
context: [
'https://www.w3.org/2018/credentials/v1',
'https://www.example.com/studentCard/v1'
],
type: [
'VerifiableCredential',
'StudentCard'
],
issuer: issuerDid,
credentialSubject: studentCard,
issuanceDate: DateTime.now());
var signedCred = await signCredential(someUniversity, cred);
var hdPath = wallet.getCredential(holderDid)!.hdPath;
await wallet.storeCredential(signedCred, '', hdPath,
keyType: KeyType.ed25519);
}
Future<void> _issueBusinessId(WalletStore wallet) async {
var someIssuer = WalletStore('example/didcomm/someIssuer');
await someIssuer.openBoxes('password');
await someIssuer.initialize();
await someIssuer.initializeIssuer(KeyType.ed25519);
var issuerDid = someIssuer.getStandardIssuerDid(KeyType.ed25519);
var holderDid = await wallet.getNextCredentialDID(KeyType.ed25519);
var businessId = {
'id': holderDid,
'name': 'Museum of modern Art',
'address': {
'streetAddress': 'Main Street 23',
'postalCode': '63587',
'addressLocality': 'Some City'
}
};
var cred = VerifiableCredential(
context: [
'https://www.w3.org/2018/credentials/v1',
'https://www.example.com/businesId/v1'
],
type: [
'VerifiableCredential',
'BusinessID'
],
issuer: issuerDid,
credentialSubject: businessId,
issuanceDate: DateTime.now());
var signedCred = await signCredential(someIssuer, cred);
var hdPath = wallet.getCredential(holderDid)!.hdPath;
await wallet.storeCredential(signedCred, '', hdPath,
keyType: KeyType.ed25519);
}