@didcid/keymaster 0.2.0 → 0.3.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/dist/cjs/keymaster-client.cjs +26 -26
- package/dist/cjs/keymaster.cjs +277 -208
- package/dist/esm/keymaster-client.js +26 -26
- package/dist/esm/keymaster-client.js.map +1 -1
- package/dist/esm/keymaster.js +218 -173
- package/dist/esm/keymaster.js.map +1 -1
- package/dist/types/keymaster-client.d.ts +16 -14
- package/dist/types/keymaster.d.ts +22 -20
- package/dist/types/types.d.ts +26 -25
- package/package.json +2 -2
package/dist/esm/keymaster.js
CHANGED
|
@@ -1,9 +1,18 @@
|
|
|
1
1
|
import { imageSize } from 'image-size';
|
|
2
2
|
import { fileTypeFromBuffer } from 'file-type';
|
|
3
|
+
import { base64url } from 'multiformats/bases/base64';
|
|
3
4
|
import { InvalidDIDError, InvalidParameterError, KeymasterError, UnknownIDError } from '@didcid/common/errors';
|
|
4
5
|
import { isWalletEncFile, isWalletFile } from './db/typeGuards.js';
|
|
5
6
|
import { isValidDID } from '@didcid/ipfs/utils';
|
|
6
7
|
import { decMnemonic, encMnemonic } from "./encryption.js";
|
|
8
|
+
function hexToBase64url(hex) {
|
|
9
|
+
const bytes = Buffer.from(hex, 'hex');
|
|
10
|
+
return base64url.baseEncode(bytes);
|
|
11
|
+
}
|
|
12
|
+
function base64urlToHex(b64) {
|
|
13
|
+
const bytes = base64url.baseDecode(b64);
|
|
14
|
+
return Buffer.from(bytes).toString('hex');
|
|
15
|
+
}
|
|
7
16
|
const DefaultSchema = {
|
|
8
17
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
9
18
|
"type": "object",
|
|
@@ -301,13 +310,15 @@ export default class Keymaster {
|
|
|
301
310
|
publicJwk: keypair.publicJwk,
|
|
302
311
|
};
|
|
303
312
|
const msgHash = this.cipher.hashJSON(operation);
|
|
304
|
-
const
|
|
313
|
+
const signatureHex = this.cipher.signHash(msgHash, keypair.privateJwk);
|
|
305
314
|
const signed = {
|
|
306
315
|
...operation,
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
316
|
+
proof: {
|
|
317
|
+
type: "EcdsaSecp256k1Signature2019",
|
|
318
|
+
created: new Date(0).toISOString(),
|
|
319
|
+
verificationMethod: "#key-1",
|
|
320
|
+
proofPurpose: "authentication",
|
|
321
|
+
proofValue: hexToBase64url(signatureHex),
|
|
311
322
|
}
|
|
312
323
|
};
|
|
313
324
|
const did = await this.gatekeeper.createDID(signed);
|
|
@@ -328,14 +339,15 @@ export default class Keymaster {
|
|
|
328
339
|
doc,
|
|
329
340
|
};
|
|
330
341
|
const msgHash = this.cipher.hashJSON(operation);
|
|
331
|
-
const
|
|
342
|
+
const signatureHex = this.cipher.signHash(msgHash, keypair.privateJwk);
|
|
332
343
|
const signed = {
|
|
333
344
|
...operation,
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
345
|
+
proof: {
|
|
346
|
+
type: "EcdsaSecp256k1Signature2019",
|
|
347
|
+
created: new Date().toISOString(),
|
|
348
|
+
verificationMethod: `${did}#key-1`,
|
|
349
|
+
proofPurpose: "authentication",
|
|
350
|
+
proofValue: hexToBase64url(signatureHex),
|
|
339
351
|
}
|
|
340
352
|
};
|
|
341
353
|
return await this.gatekeeper.updateDID(signed);
|
|
@@ -360,14 +372,15 @@ export default class Keymaster {
|
|
|
360
372
|
data: { backup: backup },
|
|
361
373
|
};
|
|
362
374
|
const msgHash = this.cipher.hashJSON(operation);
|
|
363
|
-
const
|
|
375
|
+
const signatureHex = this.cipher.signHash(msgHash, keypair.privateJwk);
|
|
364
376
|
const signed = {
|
|
365
377
|
...operation,
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
378
|
+
proof: {
|
|
379
|
+
type: "EcdsaSecp256k1Signature2019",
|
|
380
|
+
created: new Date().toISOString(),
|
|
381
|
+
verificationMethod: `${seedBank.didDocument?.id}#key-1`,
|
|
382
|
+
proofPurpose: "authentication",
|
|
383
|
+
proofValue: hexToBase64url(signatureHex),
|
|
371
384
|
}
|
|
372
385
|
};
|
|
373
386
|
const backupDID = await this.gatekeeper.createDID(signed);
|
|
@@ -548,7 +561,7 @@ export default class Keymaster {
|
|
|
548
561
|
controller: id.did,
|
|
549
562
|
data,
|
|
550
563
|
};
|
|
551
|
-
const signed = await this.
|
|
564
|
+
const signed = await this.addProof(operation, controller, "authentication");
|
|
552
565
|
const did = await this.gatekeeper.createDID(signed);
|
|
553
566
|
// Keep assets that will be garbage-collected out of the owned list
|
|
554
567
|
if (!validUntil) {
|
|
@@ -737,7 +750,7 @@ export default class Keymaster {
|
|
|
737
750
|
throw new InvalidParameterError('did not encrypted JSON');
|
|
738
751
|
}
|
|
739
752
|
}
|
|
740
|
-
async
|
|
753
|
+
async addProof(obj, controller, proofPurpose = "assertionMethod") {
|
|
741
754
|
if (obj == null) {
|
|
742
755
|
throw new InvalidParameterError('obj');
|
|
743
756
|
}
|
|
@@ -745,18 +758,23 @@ export default class Keymaster {
|
|
|
745
758
|
const id = await this.fetchIdInfo(controller);
|
|
746
759
|
const keypair = await this.fetchKeyPair(controller);
|
|
747
760
|
if (!keypair) {
|
|
748
|
-
throw new KeymasterError('
|
|
761
|
+
throw new KeymasterError('addProof: no keypair');
|
|
749
762
|
}
|
|
763
|
+
// Get the key fragment from the DID document
|
|
764
|
+
const doc = await this.resolveDID(id.did, { confirm: true });
|
|
765
|
+
const keyFragment = doc.didDocument?.verificationMethod?.[0]?.id || '#key-1';
|
|
750
766
|
try {
|
|
751
767
|
const msgHash = this.cipher.hashJSON(obj);
|
|
752
|
-
const
|
|
768
|
+
const signatureHex = this.cipher.signHash(msgHash, keypair.privateJwk);
|
|
769
|
+
const proofValue = hexToBase64url(signatureHex);
|
|
753
770
|
return {
|
|
754
771
|
...obj,
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
772
|
+
proof: {
|
|
773
|
+
type: "EcdsaSecp256k1Signature2019",
|
|
774
|
+
created: new Date().toISOString(),
|
|
775
|
+
verificationMethod: `${id.did}${keyFragment}`,
|
|
776
|
+
proofPurpose,
|
|
777
|
+
proofValue,
|
|
760
778
|
}
|
|
761
779
|
};
|
|
762
780
|
}
|
|
@@ -764,24 +782,27 @@ export default class Keymaster {
|
|
|
764
782
|
throw new InvalidParameterError('obj');
|
|
765
783
|
}
|
|
766
784
|
}
|
|
767
|
-
async
|
|
768
|
-
if (!obj?.
|
|
785
|
+
async verifyProof(obj) {
|
|
786
|
+
if (!obj?.proof) {
|
|
769
787
|
return false;
|
|
770
788
|
}
|
|
771
|
-
const {
|
|
772
|
-
if (
|
|
789
|
+
const { proof } = obj;
|
|
790
|
+
if (proof.type !== "EcdsaSecp256k1Signature2019") {
|
|
773
791
|
return false;
|
|
774
792
|
}
|
|
775
|
-
|
|
776
|
-
delete jsonCopy.signature;
|
|
777
|
-
const msgHash = this.cipher.hashJSON(jsonCopy);
|
|
778
|
-
if (signature.hash && signature.hash !== msgHash) {
|
|
793
|
+
if (!proof.verificationMethod) {
|
|
779
794
|
return false;
|
|
780
795
|
}
|
|
781
|
-
|
|
796
|
+
// Extract DID from verificationMethod
|
|
797
|
+
const [signerDid] = proof.verificationMethod.split('#');
|
|
798
|
+
const jsonCopy = JSON.parse(JSON.stringify(obj));
|
|
799
|
+
delete jsonCopy.proof;
|
|
800
|
+
const msgHash = this.cipher.hashJSON(jsonCopy);
|
|
801
|
+
const doc = await this.resolveDID(signerDid, { versionTime: proof.created });
|
|
782
802
|
const publicJwk = this.getPublicKeyJwk(doc);
|
|
783
803
|
try {
|
|
784
|
-
|
|
804
|
+
const signatureHex = base64urlToHex(proof.proofValue);
|
|
805
|
+
return this.cipher.verifySig(msgHash, signatureHex, publicJwk);
|
|
785
806
|
}
|
|
786
807
|
catch (error) {
|
|
787
808
|
return false;
|
|
@@ -810,7 +831,7 @@ export default class Keymaster {
|
|
|
810
831
|
else if (current.didDocumentRegistration?.type === 'asset') {
|
|
811
832
|
controller = current.didDocument?.controller;
|
|
812
833
|
}
|
|
813
|
-
const signed = await this.
|
|
834
|
+
const signed = await this.addProof(operation, controller, "authentication");
|
|
814
835
|
return this.gatekeeper.updateDID(signed);
|
|
815
836
|
}
|
|
816
837
|
async revokeDID(id) {
|
|
@@ -832,7 +853,7 @@ export default class Keymaster {
|
|
|
832
853
|
else if (current.didDocumentRegistration?.type === 'asset') {
|
|
833
854
|
controller = current.didDocument?.controller;
|
|
834
855
|
}
|
|
835
|
-
const signed = await this.
|
|
856
|
+
const signed = await this.addProof(operation, controller, "authentication");
|
|
836
857
|
const ok = await this.gatekeeper.deleteDID(signed);
|
|
837
858
|
if (ok && current.didDocument?.controller) {
|
|
838
859
|
await this.removeFromOwned(did, current.didDocument.controller);
|
|
@@ -912,9 +933,13 @@ export default class Keymaster {
|
|
|
912
933
|
}
|
|
913
934
|
const controller = docs.didDocument?.controller || docs.didDocument?.id;
|
|
914
935
|
const isOwned = await this.idInWallet(controller);
|
|
915
|
-
//
|
|
936
|
+
// Convert versionSequence string to numeric version
|
|
937
|
+
const versionSequence = docs.didDocumentMetadata?.versionSequence;
|
|
938
|
+
const version = versionSequence ? parseInt(versionSequence, 10) : undefined;
|
|
939
|
+
// Augment the DID document metadata with the DID ownership status and numeric version
|
|
916
940
|
docs.didDocumentMetadata = {
|
|
917
941
|
...docs.didDocumentMetadata,
|
|
942
|
+
version,
|
|
918
943
|
isOwned,
|
|
919
944
|
};
|
|
920
945
|
return docs;
|
|
@@ -1033,13 +1058,15 @@ export default class Keymaster {
|
|
|
1033
1058
|
publicJwk: keypair.publicJwk,
|
|
1034
1059
|
};
|
|
1035
1060
|
const msgHash = this.cipher.hashJSON(operation);
|
|
1036
|
-
const
|
|
1061
|
+
const signatureHex = this.cipher.signHash(msgHash, keypair.privateJwk);
|
|
1037
1062
|
const signed = {
|
|
1038
1063
|
...operation,
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1064
|
+
proof: {
|
|
1065
|
+
type: "EcdsaSecp256k1Signature2019",
|
|
1066
|
+
created: new Date().toISOString(),
|
|
1067
|
+
verificationMethod: "#key-1",
|
|
1068
|
+
proofPurpose: "authentication",
|
|
1069
|
+
proofValue: hexToBase64url(signatureHex),
|
|
1043
1070
|
},
|
|
1044
1071
|
};
|
|
1045
1072
|
return signed;
|
|
@@ -1093,10 +1120,10 @@ export default class Keymaster {
|
|
|
1093
1120
|
if (!registry) {
|
|
1094
1121
|
throw new InvalidParameterError('no registry found for agent DID');
|
|
1095
1122
|
}
|
|
1096
|
-
const
|
|
1123
|
+
const backupStoreDid = await this.createAsset({ backup: backup }, { registry, controller: name });
|
|
1097
1124
|
if (doc.didDocumentData) {
|
|
1098
1125
|
const currentData = doc.didDocumentData;
|
|
1099
|
-
const updatedData = { ...currentData,
|
|
1126
|
+
const updatedData = { ...currentData, backupStore: backupStoreDid };
|
|
1100
1127
|
return this.updateDID(name, { didDocumentData: updatedData });
|
|
1101
1128
|
}
|
|
1102
1129
|
return false;
|
|
@@ -1106,14 +1133,14 @@ export default class Keymaster {
|
|
|
1106
1133
|
const keypair = await this.hdKeyPair();
|
|
1107
1134
|
const doc = await this.resolveDID(did);
|
|
1108
1135
|
const docData = doc.didDocumentData;
|
|
1109
|
-
if (!docData.
|
|
1110
|
-
throw new InvalidDIDError('didDocumentData missing
|
|
1136
|
+
if (!docData.backupStore) {
|
|
1137
|
+
throw new InvalidDIDError('didDocumentData missing backupStore');
|
|
1111
1138
|
}
|
|
1112
|
-
const
|
|
1113
|
-
if (typeof
|
|
1114
|
-
throw new InvalidDIDError('backup not found in
|
|
1139
|
+
const backupStore = await this.resolveAsset(docData.backupStore);
|
|
1140
|
+
if (typeof backupStore.backup !== 'string') {
|
|
1141
|
+
throw new InvalidDIDError('backup not found in backupStore');
|
|
1115
1142
|
}
|
|
1116
|
-
const backup = this.cipher.decryptMessage(keypair.publicJwk, keypair.privateJwk,
|
|
1143
|
+
const backup = this.cipher.decryptMessage(keypair.publicJwk, keypair.privateJwk, backupStore.backup);
|
|
1117
1144
|
const data = JSON.parse(backup);
|
|
1118
1145
|
await this.mutateWallet((wallet) => {
|
|
1119
1146
|
if (wallet.ids[data.name]) {
|
|
@@ -1157,6 +1184,7 @@ export default class Keymaster {
|
|
|
1157
1184
|
...doc.didDocument,
|
|
1158
1185
|
verificationMethod: [vmethod],
|
|
1159
1186
|
authentication: [vmethod.id],
|
|
1187
|
+
assertionMethod: [vmethod.id],
|
|
1160
1188
|
};
|
|
1161
1189
|
ok = await this.updateDID(id.did, { didDocument: updatedDidDocument });
|
|
1162
1190
|
if (!ok) {
|
|
@@ -1169,7 +1197,7 @@ export default class Keymaster {
|
|
|
1169
1197
|
async listNames(options = {}) {
|
|
1170
1198
|
const { includeIDs = false } = options;
|
|
1171
1199
|
const wallet = await this.loadWallet();
|
|
1172
|
-
const names = wallet.names || {};
|
|
1200
|
+
const names = { ...(wallet.names || {}) };
|
|
1173
1201
|
if (includeIDs) {
|
|
1174
1202
|
for (const [name, id] of Object.entries(wallet.ids || {})) {
|
|
1175
1203
|
names[name] = id.did;
|
|
@@ -1207,42 +1235,60 @@ export default class Keymaster {
|
|
|
1207
1235
|
const doc = await this.resolveDID(id);
|
|
1208
1236
|
return doc.didDocumentRegistration?.type === 'agent';
|
|
1209
1237
|
}
|
|
1210
|
-
async bindCredential(
|
|
1211
|
-
let { validFrom, validUntil,
|
|
1238
|
+
async bindCredential(subjectId, options = {}) {
|
|
1239
|
+
let { schema, validFrom, validUntil, claims, types } = options;
|
|
1212
1240
|
if (!validFrom) {
|
|
1213
1241
|
validFrom = new Date().toISOString();
|
|
1214
1242
|
}
|
|
1215
1243
|
const id = await this.fetchIdInfo();
|
|
1216
|
-
const type = await this.lookupDID(schemaId);
|
|
1217
1244
|
const subjectDID = await this.lookupDID(subjectId);
|
|
1218
|
-
|
|
1219
|
-
const schema = await this.getSchema(type);
|
|
1220
|
-
credential = this.generateSchema(schema);
|
|
1221
|
-
}
|
|
1222
|
-
return {
|
|
1245
|
+
const vc = {
|
|
1223
1246
|
"@context": [
|
|
1224
1247
|
"https://www.w3.org/ns/credentials/v2",
|
|
1225
1248
|
"https://www.w3.org/ns/credentials/examples/v2"
|
|
1226
1249
|
],
|
|
1227
|
-
type: ["VerifiableCredential",
|
|
1250
|
+
type: ["VerifiableCredential", ...(types || [])],
|
|
1228
1251
|
issuer: id.did,
|
|
1229
1252
|
validFrom,
|
|
1230
1253
|
validUntil,
|
|
1231
1254
|
credentialSubject: {
|
|
1232
1255
|
id: subjectDID,
|
|
1233
1256
|
},
|
|
1234
|
-
credential,
|
|
1235
1257
|
};
|
|
1258
|
+
// If schema provided, add credentialSchema and generate claims from schema
|
|
1259
|
+
if (schema) {
|
|
1260
|
+
const schemaDID = await this.lookupDID(schema);
|
|
1261
|
+
const schemaDoc = await this.getSchema(schemaDID);
|
|
1262
|
+
if (!claims && schemaDoc) {
|
|
1263
|
+
claims = this.generateSchema(schemaDoc);
|
|
1264
|
+
}
|
|
1265
|
+
// If schema has $credentialTypes, add them to credential types (avoiding duplicates)
|
|
1266
|
+
if (schemaDoc?.$credentialTypes) {
|
|
1267
|
+
const newTypes = schemaDoc.$credentialTypes.filter(t => !vc.type.includes(t));
|
|
1268
|
+
vc.type.push(...newTypes);
|
|
1269
|
+
}
|
|
1270
|
+
vc.credentialSchema = {
|
|
1271
|
+
id: schemaDID,
|
|
1272
|
+
type: "JsonSchema",
|
|
1273
|
+
};
|
|
1274
|
+
}
|
|
1275
|
+
if (claims) {
|
|
1276
|
+
vc.credentialSubject = {
|
|
1277
|
+
id: subjectDID,
|
|
1278
|
+
...claims,
|
|
1279
|
+
};
|
|
1280
|
+
}
|
|
1281
|
+
return vc;
|
|
1236
1282
|
}
|
|
1237
1283
|
async issueCredential(credential, options = {}) {
|
|
1238
1284
|
const id = await this.fetchIdInfo();
|
|
1239
1285
|
if (options.schema && options.subject) {
|
|
1240
|
-
credential = await this.bindCredential(options.
|
|
1286
|
+
credential = await this.bindCredential(options.subject, { schema: options.schema, claims: options.claims, ...options });
|
|
1241
1287
|
}
|
|
1242
1288
|
if (credential.issuer !== id.did) {
|
|
1243
1289
|
throw new InvalidParameterError('credential.issuer');
|
|
1244
1290
|
}
|
|
1245
|
-
const signed = await this.
|
|
1291
|
+
const signed = await this.addProof(credential);
|
|
1246
1292
|
return this.encryptJSON(signed, credential.credentialSubject.id, { ...options, includeHash: true });
|
|
1247
1293
|
}
|
|
1248
1294
|
async sendCredential(did, options = {}) {
|
|
@@ -1272,13 +1318,12 @@ export default class Keymaster {
|
|
|
1272
1318
|
throw new InvalidParameterError("did is not a credential");
|
|
1273
1319
|
}
|
|
1274
1320
|
if (!credential ||
|
|
1275
|
-
!credential.credential ||
|
|
1276
1321
|
!credential.credentialSubject ||
|
|
1277
1322
|
!credential.credentialSubject.id) {
|
|
1278
1323
|
throw new InvalidParameterError('credential');
|
|
1279
1324
|
}
|
|
1280
|
-
delete credential.
|
|
1281
|
-
const signed = await this.
|
|
1325
|
+
delete credential.proof;
|
|
1326
|
+
const signed = await this.addProof(credential);
|
|
1282
1327
|
const msg = JSON.stringify(signed);
|
|
1283
1328
|
const id = await this.fetchIdInfo();
|
|
1284
1329
|
const senderKeypair = await this.fetchKeyPair();
|
|
@@ -1372,8 +1417,8 @@ export default class Keymaster {
|
|
|
1372
1417
|
data.manifest = {};
|
|
1373
1418
|
}
|
|
1374
1419
|
if (!reveal) {
|
|
1375
|
-
// Remove the
|
|
1376
|
-
vc.
|
|
1420
|
+
// Remove the claim values, keep only the subject id
|
|
1421
|
+
vc.credentialSubject = { id: vc.credentialSubject.id };
|
|
1377
1422
|
}
|
|
1378
1423
|
data.manifest[credential] = vc;
|
|
1379
1424
|
const ok = await this.updateDID(id.did, { didDocumentData: doc.didDocumentData });
|
|
@@ -1431,8 +1476,8 @@ export default class Keymaster {
|
|
|
1431
1476
|
// Attestor not trusted by Verifier
|
|
1432
1477
|
continue;
|
|
1433
1478
|
}
|
|
1434
|
-
if (doc.
|
|
1435
|
-
// Wrong
|
|
1479
|
+
if (doc.credentialSchema?.id !== credential.schema) {
|
|
1480
|
+
// Wrong schema
|
|
1436
1481
|
continue;
|
|
1437
1482
|
}
|
|
1438
1483
|
// TBD test for VC expiry too
|
|
@@ -1557,7 +1602,7 @@ export default class Keymaster {
|
|
|
1557
1602
|
continue;
|
|
1558
1603
|
}
|
|
1559
1604
|
const vp = await this.decryptJSON(credential.vp);
|
|
1560
|
-
const isValid = await this.
|
|
1605
|
+
const isValid = await this.verifyProof(vp);
|
|
1561
1606
|
if (!isValid) {
|
|
1562
1607
|
continue;
|
|
1563
1608
|
}
|
|
@@ -1565,8 +1610,8 @@ export default class Keymaster {
|
|
|
1565
1610
|
continue;
|
|
1566
1611
|
}
|
|
1567
1612
|
// Check VP against VCs specified in challenge
|
|
1568
|
-
if (vp.
|
|
1569
|
-
const schema = vp.
|
|
1613
|
+
if (vp.credentialSchema?.id) {
|
|
1614
|
+
const schema = vp.credentialSchema.id;
|
|
1570
1615
|
const credential = challenge.credentials?.find(item => item.schema === schema);
|
|
1571
1616
|
if (!credential) {
|
|
1572
1617
|
continue;
|
|
@@ -2064,7 +2109,7 @@ export default class Keymaster {
|
|
|
2064
2109
|
delete poll.results;
|
|
2065
2110
|
return this.updateAsset(pollId, { poll });
|
|
2066
2111
|
}
|
|
2067
|
-
async
|
|
2112
|
+
async createVault(options = {}) {
|
|
2068
2113
|
const id = await this.fetchIdInfo();
|
|
2069
2114
|
const idKeypair = await this.fetchKeyPair();
|
|
2070
2115
|
// version defaults to 1. To make version undefined (unit testing), set options.version to 0
|
|
@@ -2079,7 +2124,7 @@ export default class Keymaster {
|
|
|
2079
2124
|
const members = this.cipher.encryptMessage(publicJwk, vaultKeypair.privateJwk, JSON.stringify({}));
|
|
2080
2125
|
const items = this.cipher.encryptMessage(vaultKeypair.publicJwk, vaultKeypair.privateJwk, JSON.stringify({}));
|
|
2081
2126
|
const sha256 = this.cipher.hashJSON({});
|
|
2082
|
-
const
|
|
2127
|
+
const vault = {
|
|
2083
2128
|
version,
|
|
2084
2129
|
publicJwk: vaultKeypair.publicJwk,
|
|
2085
2130
|
salt,
|
|
@@ -2089,46 +2134,46 @@ export default class Keymaster {
|
|
|
2089
2134
|
items,
|
|
2090
2135
|
sha256,
|
|
2091
2136
|
};
|
|
2092
|
-
await this.addMemberKey(
|
|
2093
|
-
return this.createAsset({
|
|
2137
|
+
await this.addMemberKey(vault, id.did, vaultKeypair.privateJwk);
|
|
2138
|
+
return this.createAsset({ vault }, options);
|
|
2094
2139
|
}
|
|
2095
|
-
async
|
|
2096
|
-
const asset = await this.resolveAsset(
|
|
2097
|
-
if (!asset.
|
|
2098
|
-
throw new InvalidParameterError('
|
|
2140
|
+
async getVault(vaultId, options) {
|
|
2141
|
+
const asset = await this.resolveAsset(vaultId, options);
|
|
2142
|
+
if (!asset.vault) {
|
|
2143
|
+
throw new InvalidParameterError('vaultId');
|
|
2099
2144
|
}
|
|
2100
|
-
return asset.
|
|
2145
|
+
return asset.vault;
|
|
2101
2146
|
}
|
|
2102
|
-
async
|
|
2147
|
+
async testVault(id, options) {
|
|
2103
2148
|
try {
|
|
2104
|
-
const
|
|
2105
|
-
return
|
|
2149
|
+
const vault = await this.getVault(id, options);
|
|
2150
|
+
return vault !== null;
|
|
2106
2151
|
}
|
|
2107
2152
|
catch (error) {
|
|
2108
2153
|
return false;
|
|
2109
2154
|
}
|
|
2110
2155
|
}
|
|
2111
|
-
generateSaltedId(
|
|
2112
|
-
if (!
|
|
2113
|
-
return this.cipher.hashMessage(
|
|
2156
|
+
generateSaltedId(vault, memberDID) {
|
|
2157
|
+
if (!vault.version) {
|
|
2158
|
+
return this.cipher.hashMessage(vault.salt + memberDID);
|
|
2114
2159
|
}
|
|
2115
2160
|
const suffix = memberDID.split(':').pop();
|
|
2116
|
-
return this.cipher.hashMessage(
|
|
2161
|
+
return this.cipher.hashMessage(vault.salt + suffix);
|
|
2117
2162
|
}
|
|
2118
|
-
async
|
|
2163
|
+
async decryptVault(vault) {
|
|
2119
2164
|
const wallet = await this.loadWallet();
|
|
2120
2165
|
const id = await this.fetchIdInfo();
|
|
2121
|
-
const myMemberId = this.generateSaltedId(
|
|
2122
|
-
const myVaultKey =
|
|
2166
|
+
const myMemberId = this.generateSaltedId(vault, id.did);
|
|
2167
|
+
const myVaultKey = vault.keys[myMemberId];
|
|
2123
2168
|
if (!myVaultKey) {
|
|
2124
|
-
throw new KeymasterError('No access to
|
|
2169
|
+
throw new KeymasterError('No access to vault');
|
|
2125
2170
|
}
|
|
2126
|
-
const privKeyJSON = await this.decryptWithDerivedKeys(wallet, id,
|
|
2171
|
+
const privKeyJSON = await this.decryptWithDerivedKeys(wallet, id, vault.publicJwk, myVaultKey);
|
|
2127
2172
|
const privateJwk = JSON.parse(privKeyJSON);
|
|
2128
2173
|
let config = {};
|
|
2129
2174
|
let isOwner = false;
|
|
2130
2175
|
try {
|
|
2131
|
-
const configJSON = await this.decryptWithDerivedKeys(wallet, id,
|
|
2176
|
+
const configJSON = await this.decryptWithDerivedKeys(wallet, id, vault.publicJwk, vault.config);
|
|
2132
2177
|
config = JSON.parse(configJSON);
|
|
2133
2178
|
isOwner = true;
|
|
2134
2179
|
}
|
|
@@ -2138,7 +2183,7 @@ export default class Keymaster {
|
|
|
2138
2183
|
let members = {};
|
|
2139
2184
|
if (config.secretMembers) {
|
|
2140
2185
|
try {
|
|
2141
|
-
const membersJSON = await this.decryptWithDerivedKeys(wallet, id,
|
|
2186
|
+
const membersJSON = await this.decryptWithDerivedKeys(wallet, id, vault.publicJwk, vault.members);
|
|
2142
2187
|
members = JSON.parse(membersJSON);
|
|
2143
2188
|
}
|
|
2144
2189
|
catch (error) {
|
|
@@ -2146,13 +2191,13 @@ export default class Keymaster {
|
|
|
2146
2191
|
}
|
|
2147
2192
|
else {
|
|
2148
2193
|
try {
|
|
2149
|
-
const membersJSON = this.cipher.decryptMessage(
|
|
2194
|
+
const membersJSON = this.cipher.decryptMessage(vault.publicJwk, privateJwk, vault.members);
|
|
2150
2195
|
members = JSON.parse(membersJSON);
|
|
2151
2196
|
}
|
|
2152
2197
|
catch (error) {
|
|
2153
2198
|
}
|
|
2154
2199
|
}
|
|
2155
|
-
const itemsJSON = this.cipher.decryptMessage(
|
|
2200
|
+
const itemsJSON = this.cipher.decryptMessage(vault.publicJwk, privateJwk, vault.items);
|
|
2156
2201
|
const items = JSON.parse(itemsJSON);
|
|
2157
2202
|
return {
|
|
2158
2203
|
isOwner,
|
|
@@ -2162,7 +2207,7 @@ export default class Keymaster {
|
|
|
2162
2207
|
items,
|
|
2163
2208
|
};
|
|
2164
2209
|
}
|
|
2165
|
-
async
|
|
2210
|
+
async checkVaultOwner(vaultId) {
|
|
2166
2211
|
const id = await this.fetchIdInfo();
|
|
2167
2212
|
const vaultDoc = await this.resolveDID(vaultId);
|
|
2168
2213
|
const controller = vaultDoc.didDocument?.controller;
|
|
@@ -2171,30 +2216,30 @@ export default class Keymaster {
|
|
|
2171
2216
|
}
|
|
2172
2217
|
return controller;
|
|
2173
2218
|
}
|
|
2174
|
-
async addMemberKey(
|
|
2219
|
+
async addMemberKey(vault, memberDID, privateJwk) {
|
|
2175
2220
|
const memberDoc = await this.resolveDID(memberDID, { confirm: true });
|
|
2176
2221
|
const memberPublicJwk = this.getPublicKeyJwk(memberDoc);
|
|
2177
2222
|
const memberKey = this.cipher.encryptMessage(memberPublicJwk, privateJwk, JSON.stringify(privateJwk));
|
|
2178
|
-
const memberKeyId = this.generateSaltedId(
|
|
2179
|
-
|
|
2223
|
+
const memberKeyId = this.generateSaltedId(vault, memberDID);
|
|
2224
|
+
vault.keys[memberKeyId] = memberKey;
|
|
2180
2225
|
}
|
|
2181
|
-
async checkVaultVersion(vaultId,
|
|
2182
|
-
if (
|
|
2226
|
+
async checkVaultVersion(vaultId, vault) {
|
|
2227
|
+
if (vault.version === 1) {
|
|
2183
2228
|
return;
|
|
2184
2229
|
}
|
|
2185
|
-
if (!
|
|
2230
|
+
if (!vault.version) {
|
|
2186
2231
|
const id = await this.fetchIdInfo();
|
|
2187
|
-
const { privateJwk, members } = await this.
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
await this.addMemberKey(
|
|
2232
|
+
const { privateJwk, members } = await this.decryptVault(vault);
|
|
2233
|
+
vault.version = 1;
|
|
2234
|
+
vault.keys = {};
|
|
2235
|
+
await this.addMemberKey(vault, id.did, privateJwk);
|
|
2191
2236
|
for (const memberDID of Object.keys(members)) {
|
|
2192
|
-
await this.addMemberKey(
|
|
2237
|
+
await this.addMemberKey(vault, memberDID, privateJwk);
|
|
2193
2238
|
}
|
|
2194
|
-
await this.updateAsset(vaultId, {
|
|
2239
|
+
await this.updateAsset(vaultId, { vault });
|
|
2195
2240
|
return;
|
|
2196
2241
|
}
|
|
2197
|
-
throw new KeymasterError('Unsupported
|
|
2242
|
+
throw new KeymasterError('Unsupported vault version');
|
|
2198
2243
|
}
|
|
2199
2244
|
getAgentDID(doc) {
|
|
2200
2245
|
if (doc.didDocumentRegistration?.type !== 'agent') {
|
|
@@ -2206,11 +2251,11 @@ export default class Keymaster {
|
|
|
2206
2251
|
}
|
|
2207
2252
|
return did;
|
|
2208
2253
|
}
|
|
2209
|
-
async
|
|
2210
|
-
const owner = await this.
|
|
2254
|
+
async addVaultMember(vaultId, memberId) {
|
|
2255
|
+
const owner = await this.checkVaultOwner(vaultId);
|
|
2211
2256
|
const idKeypair = await this.fetchKeyPair();
|
|
2212
|
-
const
|
|
2213
|
-
const { privateJwk, config, members } = await this.
|
|
2257
|
+
const vault = await this.getVault(vaultId);
|
|
2258
|
+
const { privateJwk, config, members } = await this.decryptVault(vault);
|
|
2214
2259
|
const memberDoc = await this.resolveDID(memberId, { confirm: true });
|
|
2215
2260
|
const memberDID = this.getAgentDID(memberDoc);
|
|
2216
2261
|
// Don't allow adding the vault owner
|
|
@@ -2218,16 +2263,16 @@ export default class Keymaster {
|
|
|
2218
2263
|
return false;
|
|
2219
2264
|
}
|
|
2220
2265
|
members[memberDID] = { added: new Date().toISOString() };
|
|
2221
|
-
const publicJwk = config.secretMembers ? idKeypair.publicJwk :
|
|
2222
|
-
|
|
2223
|
-
await this.addMemberKey(
|
|
2224
|
-
return this.updateAsset(vaultId, {
|
|
2266
|
+
const publicJwk = config.secretMembers ? idKeypair.publicJwk : vault.publicJwk;
|
|
2267
|
+
vault.members = this.cipher.encryptMessage(publicJwk, privateJwk, JSON.stringify(members));
|
|
2268
|
+
await this.addMemberKey(vault, memberDID, privateJwk);
|
|
2269
|
+
return this.updateAsset(vaultId, { vault });
|
|
2225
2270
|
}
|
|
2226
|
-
async
|
|
2227
|
-
const owner = await this.
|
|
2271
|
+
async removeVaultMember(vaultId, memberId) {
|
|
2272
|
+
const owner = await this.checkVaultOwner(vaultId);
|
|
2228
2273
|
const idKeypair = await this.fetchKeyPair();
|
|
2229
|
-
const
|
|
2230
|
-
const { privateJwk, config, members } = await this.
|
|
2274
|
+
const vault = await this.getVault(vaultId);
|
|
2275
|
+
const { privateJwk, config, members } = await this.decryptVault(vault);
|
|
2231
2276
|
const memberDoc = await this.resolveDID(memberId, { confirm: true });
|
|
2232
2277
|
const memberDID = this.getAgentDID(memberDoc);
|
|
2233
2278
|
// Don't allow removing the vault owner
|
|
@@ -2235,26 +2280,26 @@ export default class Keymaster {
|
|
|
2235
2280
|
return false;
|
|
2236
2281
|
}
|
|
2237
2282
|
delete members[memberDID];
|
|
2238
|
-
const publicJwk = config.secretMembers ? idKeypair.publicJwk :
|
|
2239
|
-
|
|
2240
|
-
const memberKeyId = this.generateSaltedId(
|
|
2241
|
-
delete
|
|
2242
|
-
return this.updateAsset(vaultId, {
|
|
2243
|
-
}
|
|
2244
|
-
async
|
|
2245
|
-
const
|
|
2246
|
-
const { members, isOwner } = await this.
|
|
2283
|
+
const publicJwk = config.secretMembers ? idKeypair.publicJwk : vault.publicJwk;
|
|
2284
|
+
vault.members = this.cipher.encryptMessage(publicJwk, privateJwk, JSON.stringify(members));
|
|
2285
|
+
const memberKeyId = this.generateSaltedId(vault, memberDID);
|
|
2286
|
+
delete vault.keys[memberKeyId];
|
|
2287
|
+
return this.updateAsset(vaultId, { vault });
|
|
2288
|
+
}
|
|
2289
|
+
async listVaultMembers(vaultId) {
|
|
2290
|
+
const vault = await this.getVault(vaultId);
|
|
2291
|
+
const { members, isOwner } = await this.decryptVault(vault);
|
|
2247
2292
|
if (isOwner) {
|
|
2248
|
-
await this.checkVaultVersion(vaultId,
|
|
2293
|
+
await this.checkVaultVersion(vaultId, vault);
|
|
2249
2294
|
}
|
|
2250
2295
|
return members;
|
|
2251
2296
|
}
|
|
2252
|
-
async
|
|
2253
|
-
await this.
|
|
2254
|
-
const
|
|
2255
|
-
const { privateJwk, items } = await this.
|
|
2297
|
+
async addVaultItem(vaultId, name, buffer) {
|
|
2298
|
+
await this.checkVaultOwner(vaultId);
|
|
2299
|
+
const vault = await this.getVault(vaultId);
|
|
2300
|
+
const { privateJwk, items } = await this.decryptVault(vault);
|
|
2256
2301
|
const validName = this.validateName(name);
|
|
2257
|
-
const encryptedData = this.cipher.encryptBytes(
|
|
2302
|
+
const encryptedData = this.cipher.encryptBytes(vault.publicJwk, privateJwk, buffer);
|
|
2258
2303
|
const cid = await this.gatekeeper.addText(encryptedData);
|
|
2259
2304
|
const sha256 = this.cipher.hashMessage(buffer);
|
|
2260
2305
|
const type = await this.getMimeType(buffer);
|
|
@@ -2267,32 +2312,32 @@ export default class Keymaster {
|
|
|
2267
2312
|
added: new Date().toISOString(),
|
|
2268
2313
|
data,
|
|
2269
2314
|
};
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
return this.updateAsset(vaultId, {
|
|
2273
|
-
}
|
|
2274
|
-
async
|
|
2275
|
-
await this.
|
|
2276
|
-
const
|
|
2277
|
-
const { privateJwk, items } = await this.
|
|
2315
|
+
vault.items = this.cipher.encryptMessage(vault.publicJwk, privateJwk, JSON.stringify(items));
|
|
2316
|
+
vault.sha256 = this.cipher.hashJSON(items);
|
|
2317
|
+
return this.updateAsset(vaultId, { vault });
|
|
2318
|
+
}
|
|
2319
|
+
async removeVaultItem(vaultId, name) {
|
|
2320
|
+
await this.checkVaultOwner(vaultId);
|
|
2321
|
+
const vault = await this.getVault(vaultId);
|
|
2322
|
+
const { privateJwk, items } = await this.decryptVault(vault);
|
|
2278
2323
|
delete items[name];
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
return this.updateAsset(vaultId, {
|
|
2324
|
+
vault.items = this.cipher.encryptMessage(vault.publicJwk, privateJwk, JSON.stringify(items));
|
|
2325
|
+
vault.sha256 = this.cipher.hashJSON(items);
|
|
2326
|
+
return this.updateAsset(vaultId, { vault });
|
|
2282
2327
|
}
|
|
2283
|
-
async
|
|
2284
|
-
const
|
|
2285
|
-
const { items } = await this.
|
|
2328
|
+
async listVaultItems(vaultId, options) {
|
|
2329
|
+
const vault = await this.getVault(vaultId, options);
|
|
2330
|
+
const { items } = await this.decryptVault(vault);
|
|
2286
2331
|
return items;
|
|
2287
2332
|
}
|
|
2288
|
-
async
|
|
2333
|
+
async getVaultItem(vaultId, name, options) {
|
|
2289
2334
|
try {
|
|
2290
|
-
const
|
|
2291
|
-
const { privateJwk, items } = await this.
|
|
2335
|
+
const vault = await this.getVault(vaultId, options);
|
|
2336
|
+
const { privateJwk, items } = await this.decryptVault(vault);
|
|
2292
2337
|
if (items[name]) {
|
|
2293
2338
|
const encryptedData = items[name].data || await this.gatekeeper.getText(items[name].cid);
|
|
2294
2339
|
if (encryptedData) {
|
|
2295
|
-
const bytes = this.cipher.decryptBytes(
|
|
2340
|
+
const bytes = this.cipher.decryptBytes(vault.publicJwk, privateJwk, encryptedData);
|
|
2296
2341
|
return Buffer.from(bytes);
|
|
2297
2342
|
}
|
|
2298
2343
|
}
|
|
@@ -2421,28 +2466,28 @@ export default class Keymaster {
|
|
|
2421
2466
|
}
|
|
2422
2467
|
async createDmail(message, options = {}) {
|
|
2423
2468
|
const dmail = await this.verifyDmail(message);
|
|
2424
|
-
const did = await this.
|
|
2469
|
+
const did = await this.createVault(options);
|
|
2425
2470
|
for (const toDID of dmail.to) {
|
|
2426
|
-
await this.
|
|
2471
|
+
await this.addVaultMember(did, toDID);
|
|
2427
2472
|
}
|
|
2428
2473
|
for (const ccDID of dmail.cc) {
|
|
2429
|
-
await this.
|
|
2474
|
+
await this.addVaultMember(did, ccDID);
|
|
2430
2475
|
}
|
|
2431
2476
|
const buffer = Buffer.from(JSON.stringify({ dmail }), 'utf-8');
|
|
2432
|
-
await this.
|
|
2477
|
+
await this.addVaultItem(did, DmailTags.DMAIL, buffer);
|
|
2433
2478
|
await this.fileDmail(did, [DmailTags.DRAFT]);
|
|
2434
2479
|
return did;
|
|
2435
2480
|
}
|
|
2436
2481
|
async updateDmail(did, message) {
|
|
2437
2482
|
const dmail = await this.verifyDmail(message);
|
|
2438
2483
|
for (const toDID of dmail.to) {
|
|
2439
|
-
await this.
|
|
2484
|
+
await this.addVaultMember(did, toDID);
|
|
2440
2485
|
}
|
|
2441
2486
|
for (const ccDID of dmail.cc) {
|
|
2442
|
-
await this.
|
|
2487
|
+
await this.addVaultMember(did, ccDID);
|
|
2443
2488
|
}
|
|
2444
2489
|
const buffer = Buffer.from(JSON.stringify({ dmail }), 'utf-8');
|
|
2445
|
-
return this.
|
|
2490
|
+
return this.addVaultItem(did, DmailTags.DMAIL, buffer);
|
|
2446
2491
|
}
|
|
2447
2492
|
async sendDmail(did) {
|
|
2448
2493
|
const dmail = await this.getDmailMessage(did);
|
|
@@ -2462,11 +2507,11 @@ export default class Keymaster {
|
|
|
2462
2507
|
return notice;
|
|
2463
2508
|
}
|
|
2464
2509
|
async getDmailMessage(did, options) {
|
|
2465
|
-
const
|
|
2466
|
-
if (!
|
|
2510
|
+
const isVault = await this.testVault(did, options);
|
|
2511
|
+
if (!isVault) {
|
|
2467
2512
|
return null;
|
|
2468
2513
|
}
|
|
2469
|
-
const buffer = await this.
|
|
2514
|
+
const buffer = await this.getVaultItem(did, DmailTags.DMAIL, options);
|
|
2470
2515
|
if (!buffer) {
|
|
2471
2516
|
return null;
|
|
2472
2517
|
}
|
|
@@ -2479,7 +2524,7 @@ export default class Keymaster {
|
|
|
2479
2524
|
}
|
|
2480
2525
|
}
|
|
2481
2526
|
async listDmailAttachments(did, options) {
|
|
2482
|
-
let items = await this.
|
|
2527
|
+
let items = await this.listVaultItems(did, options);
|
|
2483
2528
|
delete items[DmailTags.DMAIL]; // Remove the dmail item itself from attachments
|
|
2484
2529
|
return items;
|
|
2485
2530
|
}
|
|
@@ -2487,16 +2532,16 @@ export default class Keymaster {
|
|
|
2487
2532
|
if (name === DmailTags.DMAIL) {
|
|
2488
2533
|
throw new InvalidParameterError('Cannot add attachment with reserved name "dmail"');
|
|
2489
2534
|
}
|
|
2490
|
-
return this.
|
|
2535
|
+
return this.addVaultItem(did, name, buffer);
|
|
2491
2536
|
}
|
|
2492
2537
|
async removeDmailAttachment(did, name) {
|
|
2493
2538
|
if (name === DmailTags.DMAIL) {
|
|
2494
2539
|
throw new InvalidParameterError('Cannot remove attachment with reserved name "dmail"');
|
|
2495
2540
|
}
|
|
2496
|
-
return this.
|
|
2541
|
+
return this.removeVaultItem(did, name);
|
|
2497
2542
|
}
|
|
2498
2543
|
async getDmailAttachment(did, name) {
|
|
2499
|
-
return this.
|
|
2544
|
+
return this.getVaultItem(did, name);
|
|
2500
2545
|
}
|
|
2501
2546
|
async importDmail(did) {
|
|
2502
2547
|
const dmail = await this.getDmailMessage(did);
|