@twin.org/identity-connector-entity-storage 0.0.3-next.9 → 0.9.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/README.md +2 -2
- package/dist/es/entityStorageIdentityConnector.js +198 -42
- package/dist/es/entityStorageIdentityConnector.js.map +1 -1
- package/dist/es/entityStorageIdentityProfileConnector.js +3 -3
- package/dist/es/entityStorageIdentityProfileConnector.js.map +1 -1
- package/dist/es/entityStorageIdentityResolverConnector.js +1 -1
- package/dist/es/entityStorageIdentityResolverConnector.js.map +1 -1
- package/dist/es/models/IEntityStorageIdentityConnectorConstructorOptions.js.map +1 -1
- package/dist/types/entityStorageIdentityConnector.d.ts +58 -13
- package/dist/types/entityStorageIdentityProfileConnector.d.ts +3 -3
- package/docs/changelog.md +625 -91
- package/docs/examples.md +221 -1
- package/docs/reference/classes/EntityStorageIdentityConnector.md +177 -45
- package/docs/reference/classes/EntityStorageIdentityProfileConnector.md +11 -11
- package/docs/reference/classes/EntityStorageIdentityResolverConnector.md +4 -4
- package/docs/reference/classes/IdentityDocument.md +4 -4
- package/docs/reference/classes/IdentityProfile.md +5 -5
- package/docs/reference/interfaces/IEntityStorageIdentityConnectorConstructorOptions.md +4 -4
- package/docs/reference/interfaces/IEntityStorageIdentityProfileConnectorConstructorOptions.md +2 -2
- package/docs/reference/interfaces/IEntityStorageIdentityResolverConnectorConstructorOptions.md +4 -4
- package/locales/en.json +21 -16
- package/package.json +14 -14
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
# TWIN Identity
|
|
1
|
+
# TWIN Identity
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The identity-connector-entity-storage package provides an entity storage backed connector for identity workflows, enabling reliable persistence and retrieval of identity records. It supports implementations that need consistent storage semantics while staying aligned with shared contracts across the ecosystem.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
// Copyright 2024 IOTA Stiftung.
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0.
|
|
3
|
-
import { BaseError, BitString, Coerce, Compression, CompressionType, Converter, GeneralError, Guards, Is, JsonHelper, NotFoundError, ObjectHelper, RandomHelper } from "@twin.org/core";
|
|
4
|
-
import { JsonLdProcessor } from "@twin.org/data-json-ld";
|
|
3
|
+
import { ArrayHelper, BaseError, BitString, Coerce, Compression, CompressionType, Converter, GeneralError, Guards, Is, JsonHelper, NotFoundError, ObjectHelper, RandomHelper, Url, Urn } from "@twin.org/core";
|
|
4
|
+
import { JsonLdHelper, JsonLdProcessor } from "@twin.org/data-json-ld";
|
|
5
5
|
import { EntityStorageConnectorFactory } from "@twin.org/entity-storage-models";
|
|
6
6
|
import { DocumentHelper } from "@twin.org/identity-models";
|
|
7
|
-
import { DidContexts, DidTypes, DidVerificationMethodType, ProofHelper, ProofTypes } from "@twin.org/standards-w3c-did";
|
|
7
|
+
import { DidContexts, DidTypes, DidVerificationMethodType, JwsAlgorithms, ProofHelper, ProofTypes } from "@twin.org/standards-w3c-did";
|
|
8
8
|
import { VaultConnectorFactory, VaultConnectorHelper, VaultKeyType } from "@twin.org/vault-models";
|
|
9
9
|
import { Jwk, Jwt } from "@twin.org/web";
|
|
10
10
|
/**
|
|
@@ -45,6 +45,7 @@ export class EntityStorageIdentityConnector {
|
|
|
45
45
|
/**
|
|
46
46
|
* Build the key name to access the specified key in the vault.
|
|
47
47
|
* @param identity The identity of the user to access the vault keys.
|
|
48
|
+
* @param key The key to access in the vault.
|
|
48
49
|
* @returns The vault key.
|
|
49
50
|
* @internal
|
|
50
51
|
*/
|
|
@@ -54,6 +55,9 @@ export class EntityStorageIdentityConnector {
|
|
|
54
55
|
/**
|
|
55
56
|
* Verify the document in storage.
|
|
56
57
|
* @param didDocument The did document that was stored.
|
|
58
|
+
* @param vaultConnector The vault connector to use for verification.
|
|
59
|
+
* @returns A promise that resolves when verification is complete.
|
|
60
|
+
* @throws GeneralError if the document signature is invalid.
|
|
57
61
|
* @internal
|
|
58
62
|
*/
|
|
59
63
|
static async verifyDocument(didDocument, vaultConnector) {
|
|
@@ -84,7 +88,7 @@ export class EntityStorageIdentityConnector {
|
|
|
84
88
|
const bitString = new BitString(EntityStorageIdentityConnector._REVOCATION_BITS_SIZE);
|
|
85
89
|
const compressed = await Compression.compress(bitString.getBits(), CompressionType.Gzip);
|
|
86
90
|
const didDocument = {
|
|
87
|
-
"@context": DidContexts.
|
|
91
|
+
"@context": DidContexts.Context,
|
|
88
92
|
id: did,
|
|
89
93
|
service: [
|
|
90
94
|
{
|
|
@@ -105,7 +109,7 @@ export class EntityStorageIdentityConnector {
|
|
|
105
109
|
* Remove a document.
|
|
106
110
|
* @param controller The controller of the identity who can make changes.
|
|
107
111
|
* @param documentId The id of the document to remove.
|
|
108
|
-
* @returns
|
|
112
|
+
* @returns A promise that resolves when the document has been removed.
|
|
109
113
|
*/
|
|
110
114
|
async removeDocument(controller, documentId) {
|
|
111
115
|
Guards.stringValue(EntityStorageIdentityConnector.CLASS_NAME, "controller", controller);
|
|
@@ -148,7 +152,7 @@ export class EntityStorageIdentityConnector {
|
|
|
148
152
|
// If there is a verification method id, we will try to get the key from the vault.
|
|
149
153
|
try {
|
|
150
154
|
// If there is an existing key, we will use it.
|
|
151
|
-
const existingKey = await this._vaultConnector.getKey(EntityStorageIdentityConnector.buildVaultKey(didDocument.id, verificationMethodId));
|
|
155
|
+
const existingKey = await this._vaultConnector.getKey(EntityStorageIdentityConnector.buildVaultKey(didDocument.id, verificationMethodId), "public");
|
|
152
156
|
methodKeyPublic = existingKey.publicKey;
|
|
153
157
|
}
|
|
154
158
|
catch { }
|
|
@@ -187,7 +191,7 @@ export class EntityStorageIdentityConnector {
|
|
|
187
191
|
const didVerificationMethod = {
|
|
188
192
|
id: methodId,
|
|
189
193
|
controller: documentId,
|
|
190
|
-
type: "
|
|
194
|
+
type: "JsonWebKey2020",
|
|
191
195
|
publicKeyJwk: {
|
|
192
196
|
...jwkParams,
|
|
193
197
|
kid
|
|
@@ -215,7 +219,7 @@ export class EntityStorageIdentityConnector {
|
|
|
215
219
|
* Remove a verification method from the document.
|
|
216
220
|
* @param controller The controller of the identity who can make changes.
|
|
217
221
|
* @param verificationMethodId The id of the verification method.
|
|
218
|
-
* @returns
|
|
222
|
+
* @returns A promise that resolves when the verification method has been removed.
|
|
219
223
|
* @throws NotFoundError if the id can not be resolved.
|
|
220
224
|
* @throws NotSupportedError if the platform does not support multiple revocable keys.
|
|
221
225
|
*/
|
|
@@ -316,7 +320,7 @@ export class EntityStorageIdentityConnector {
|
|
|
316
320
|
* Remove a service from the document.
|
|
317
321
|
* @param controller The controller of the identity who can make changes.
|
|
318
322
|
* @param serviceId The id of the service.
|
|
319
|
-
* @returns
|
|
323
|
+
* @returns A promise that resolves when the service has been removed.
|
|
320
324
|
* @throws NotFoundError if the id can not be resolved.
|
|
321
325
|
*/
|
|
322
326
|
async removeService(controller, serviceId) {
|
|
@@ -341,6 +345,9 @@ export class EntityStorageIdentityConnector {
|
|
|
341
345
|
delete didDocument.service;
|
|
342
346
|
}
|
|
343
347
|
}
|
|
348
|
+
else {
|
|
349
|
+
throw new NotFoundError(EntityStorageIdentityConnector.CLASS_NAME, "serviceNotFound", serviceId);
|
|
350
|
+
}
|
|
344
351
|
}
|
|
345
352
|
else {
|
|
346
353
|
throw new NotFoundError(EntityStorageIdentityConnector.CLASS_NAME, "serviceNotFound", serviceId);
|
|
@@ -351,6 +358,81 @@ export class EntityStorageIdentityConnector {
|
|
|
351
358
|
throw new GeneralError(EntityStorageIdentityConnector.CLASS_NAME, "removeServiceFailed", undefined, error);
|
|
352
359
|
}
|
|
353
360
|
}
|
|
361
|
+
/**
|
|
362
|
+
* Add an alias to the alsoKnownAs property on the document.
|
|
363
|
+
* If the alias is already present the operation is a no-op.
|
|
364
|
+
* @param controller The controller of the identity who can make changes.
|
|
365
|
+
* @param documentId The id of the document to update.
|
|
366
|
+
* @param alias The alias to add. Must be a Url or Urn (typically another DID).
|
|
367
|
+
* @returns A promise that resolves when the alias has been added.
|
|
368
|
+
* @throws GeneralError if the alias is not a Url or Urn.
|
|
369
|
+
* @throws NotFoundError if the id can not be resolved.
|
|
370
|
+
*/
|
|
371
|
+
async addAlsoKnownAs(controller, documentId, alias) {
|
|
372
|
+
Guards.stringValue(EntityStorageIdentityConnector.CLASS_NAME, "controller", controller);
|
|
373
|
+
Guards.stringValue(EntityStorageIdentityConnector.CLASS_NAME, "documentId", documentId);
|
|
374
|
+
Guards.stringValue(EntityStorageIdentityConnector.CLASS_NAME, "alias", alias);
|
|
375
|
+
if (!Url.tryParseExact(alias) && !Urn.tryParseExact(alias)) {
|
|
376
|
+
throw new GeneralError(EntityStorageIdentityConnector.CLASS_NAME, "invalidAlias", { alias });
|
|
377
|
+
}
|
|
378
|
+
try {
|
|
379
|
+
const didIdentityDocument = await this._didDocumentEntityStorage.get(documentId);
|
|
380
|
+
if (Is.undefined(didIdentityDocument)) {
|
|
381
|
+
throw new NotFoundError(EntityStorageIdentityConnector.CLASS_NAME, "documentNotFound", documentId);
|
|
382
|
+
}
|
|
383
|
+
await EntityStorageIdentityConnector.verifyDocument(didIdentityDocument, this._vaultConnector);
|
|
384
|
+
const didDocument = didIdentityDocument.document;
|
|
385
|
+
const existing = Is.array(didDocument.alsoKnownAs) ? didDocument.alsoKnownAs : [];
|
|
386
|
+
if (existing.includes(alias)) {
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
didDocument.alsoKnownAs = [...existing, alias];
|
|
390
|
+
await this.updateDocument(controller, didDocument);
|
|
391
|
+
}
|
|
392
|
+
catch (error) {
|
|
393
|
+
throw new GeneralError(EntityStorageIdentityConnector.CLASS_NAME, "addAlsoKnownAsFailed", undefined, error);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* Remove an alias from the alsoKnownAs property on the document.
|
|
398
|
+
* If the alias is not present the operation is a no-op.
|
|
399
|
+
* @param controller The controller of the identity who can make changes.
|
|
400
|
+
* @param documentId The id of the document to update.
|
|
401
|
+
* @param alias The alias to remove. Must be a Url or Urn.
|
|
402
|
+
* @returns A promise that resolves when the alias has been removed.
|
|
403
|
+
* @throws GeneralError if the alias is not a Url or Urn.
|
|
404
|
+
* @throws NotFoundError if the id can not be resolved.
|
|
405
|
+
*/
|
|
406
|
+
async removeAlsoKnownAs(controller, documentId, alias) {
|
|
407
|
+
Guards.stringValue(EntityStorageIdentityConnector.CLASS_NAME, "controller", controller);
|
|
408
|
+
Guards.stringValue(EntityStorageIdentityConnector.CLASS_NAME, "documentId", documentId);
|
|
409
|
+
Guards.stringValue(EntityStorageIdentityConnector.CLASS_NAME, "alias", alias);
|
|
410
|
+
if (!Url.tryParseExact(alias) && !Urn.tryParseExact(alias)) {
|
|
411
|
+
throw new GeneralError(EntityStorageIdentityConnector.CLASS_NAME, "invalidAlias", { alias });
|
|
412
|
+
}
|
|
413
|
+
try {
|
|
414
|
+
const didIdentityDocument = await this._didDocumentEntityStorage.get(documentId);
|
|
415
|
+
if (Is.undefined(didIdentityDocument)) {
|
|
416
|
+
throw new NotFoundError(EntityStorageIdentityConnector.CLASS_NAME, "documentNotFound", documentId);
|
|
417
|
+
}
|
|
418
|
+
await EntityStorageIdentityConnector.verifyDocument(didIdentityDocument, this._vaultConnector);
|
|
419
|
+
const didDocument = didIdentityDocument.document;
|
|
420
|
+
if (!Is.array(didDocument.alsoKnownAs) || !didDocument.alsoKnownAs.includes(alias)) {
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
const filtered = didDocument.alsoKnownAs.filter(a => a !== alias);
|
|
424
|
+
if (filtered.length === 0) {
|
|
425
|
+
delete didDocument.alsoKnownAs;
|
|
426
|
+
}
|
|
427
|
+
else {
|
|
428
|
+
didDocument.alsoKnownAs = filtered;
|
|
429
|
+
}
|
|
430
|
+
await this.updateDocument(controller, didDocument);
|
|
431
|
+
}
|
|
432
|
+
catch (error) {
|
|
433
|
+
throw new GeneralError(EntityStorageIdentityConnector.CLASS_NAME, "removeAlsoKnownAsFailed", undefined, error);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
354
436
|
/**
|
|
355
437
|
* Create a verifiable credential for a verification method.
|
|
356
438
|
* @param controller The controller of the identity who can make changes.
|
|
@@ -360,6 +442,8 @@ export class EntityStorageIdentityConnector {
|
|
|
360
442
|
* @param options Additional options for creating the verifiable credential.
|
|
361
443
|
* @param options.revocationIndex The bitmap revocation index of the credential, if undefined will not have revocation status.
|
|
362
444
|
* @param options.expirationDate The date the verifiable credential is valid until.
|
|
445
|
+
* @param options.jwtHeaderFields Additional fields to add to the JWT header.
|
|
446
|
+
* @param options.jwtPayloadFields Additional fields to add to the JWT payload.
|
|
363
447
|
* @returns The created verifiable credential and its token.
|
|
364
448
|
* @throws NotFoundError if the id can not be resolved.
|
|
365
449
|
*/
|
|
@@ -370,6 +454,9 @@ export class EntityStorageIdentityConnector {
|
|
|
370
454
|
if (!Is.undefined(options?.revocationIndex)) {
|
|
371
455
|
Guards.number(EntityStorageIdentityConnector.CLASS_NAME, "options.revocationIndex", options.revocationIndex);
|
|
372
456
|
}
|
|
457
|
+
if (!Is.undefined(options?.expirationDate)) {
|
|
458
|
+
Guards.date(EntityStorageIdentityConnector.CLASS_NAME, "options.expirationDate", options.expirationDate);
|
|
459
|
+
}
|
|
373
460
|
try {
|
|
374
461
|
const idParts = DocumentHelper.parseId(verificationMethodId);
|
|
375
462
|
if (Is.empty(idParts.fragment)) {
|
|
@@ -401,19 +488,15 @@ export class EntityStorageIdentityConnector {
|
|
|
401
488
|
}
|
|
402
489
|
const revocationService = issuerDidDocument.service?.find(s => s.id.endsWith("#revocation"));
|
|
403
490
|
const subjectClone = ObjectHelper.clone(subject);
|
|
404
|
-
const finalTypes = [DidTypes.VerifiableCredential];
|
|
405
491
|
const credContext = ObjectHelper.extractProperty(subjectClone, [
|
|
406
492
|
"@context"
|
|
407
493
|
]);
|
|
408
494
|
const credId = ObjectHelper.extractProperty(subjectClone, ["@id", "id"], false);
|
|
409
|
-
const credType = ObjectHelper.extractProperty(subjectClone, ["@type", "type"]);
|
|
410
|
-
if (Is.stringValue(credType)) {
|
|
411
|
-
finalTypes.push(credType);
|
|
412
|
-
}
|
|
413
495
|
const verifiableCredential = {
|
|
414
|
-
"@context": JsonLdProcessor.combineContexts(DidContexts.
|
|
496
|
+
"@context": (JsonLdProcessor.combineContexts(DidContexts.ContextVCv1, credContext) ??
|
|
497
|
+
DidContexts.ContextVCv1),
|
|
415
498
|
id,
|
|
416
|
-
type:
|
|
499
|
+
type: DidTypes.VerifiableCredential,
|
|
417
500
|
credentialSubject: subjectClone,
|
|
418
501
|
issuer: issuerDidDocument.id,
|
|
419
502
|
issuanceDate: new Date(Date.now()).toISOString(),
|
|
@@ -431,9 +514,10 @@ export class EntityStorageIdentityConnector {
|
|
|
431
514
|
: undefined
|
|
432
515
|
};
|
|
433
516
|
const jwtHeader = {
|
|
517
|
+
...options?.jwtHeaderFields,
|
|
434
518
|
kid: verificationDidMethod.id,
|
|
435
519
|
typ: "JWT",
|
|
436
|
-
alg:
|
|
520
|
+
alg: JwsAlgorithms.EdDSA
|
|
437
521
|
};
|
|
438
522
|
const jwtVc = ObjectHelper.pick(ObjectHelper.clone(verifiableCredential), [
|
|
439
523
|
"@context",
|
|
@@ -441,6 +525,15 @@ export class EntityStorageIdentityConnector {
|
|
|
441
525
|
"credentialSubject",
|
|
442
526
|
"credentialStatus"
|
|
443
527
|
]);
|
|
528
|
+
// Add the proof to the VC after extracting the jwt data
|
|
529
|
+
// as the jwt does not include the proof
|
|
530
|
+
verifiableCredential.proof = await this.createProof(controller, verificationMethodId, ProofTypes.DataIntegrityProof, JsonLdHelper.toNodeObject(verifiableCredential));
|
|
531
|
+
// As we are adding the receipt to the data we update the JSON-LD context
|
|
532
|
+
const proofContext = verifiableCredential.proof["@context"];
|
|
533
|
+
if (!Is.empty(proofContext)) {
|
|
534
|
+
verifiableCredential["@context"] = (JsonLdProcessor.combineContexts(verifiableCredential["@context"], proofContext) ?? verifiableCredential["@context"]);
|
|
535
|
+
delete verifiableCredential.proof["@context"];
|
|
536
|
+
}
|
|
444
537
|
if (Is.array(jwtVc.credentialSubject)) {
|
|
445
538
|
jwtVc.credentialSubject = jwtVc.credentialSubject.map(c => {
|
|
446
539
|
ObjectHelper.propertyDelete(c, "id");
|
|
@@ -451,12 +544,16 @@ export class EntityStorageIdentityConnector {
|
|
|
451
544
|
ObjectHelper.propertyDelete(jwtVc.credentialSubject, "id");
|
|
452
545
|
}
|
|
453
546
|
const jwtPayload = {
|
|
547
|
+
...options?.jwtPayloadFields,
|
|
454
548
|
iss: idParts.id,
|
|
455
549
|
nbf: Math.floor(Date.now() / 1000),
|
|
456
550
|
jti: verifiableCredential.id,
|
|
457
551
|
sub: credId,
|
|
458
552
|
vc: jwtVc
|
|
459
553
|
};
|
|
554
|
+
if (Is.date(options?.expirationDate)) {
|
|
555
|
+
jwtPayload.exp = Math.floor(options.expirationDate.getTime() / 1000);
|
|
556
|
+
}
|
|
460
557
|
const signature = await Jwt.encodeWithSigner(jwtHeader, jwtPayload, async (header, payload) => VaultConnectorHelper.jwtSigner(this._vaultConnector, EntityStorageIdentityConnector.buildVaultKey(idParts.id, idParts.fragment ?? ""), header, payload));
|
|
461
558
|
return {
|
|
462
559
|
verifiableCredential,
|
|
@@ -469,13 +566,23 @@ export class EntityStorageIdentityConnector {
|
|
|
469
566
|
}
|
|
470
567
|
/**
|
|
471
568
|
* Check a verifiable credential is valid.
|
|
472
|
-
* @param
|
|
569
|
+
* @param credential The credential to verify.
|
|
473
570
|
* @returns The credential stored in the jwt and the revocation status.
|
|
474
571
|
*/
|
|
475
|
-
async checkVerifiableCredential(
|
|
476
|
-
|
|
572
|
+
async checkVerifiableCredential(credential) {
|
|
573
|
+
if (Is.object(credential)) {
|
|
574
|
+
Guards.objectValue(EntityStorageIdentityConnector.CLASS_NAME, "credential", credential);
|
|
575
|
+
Guards.objectValue(EntityStorageIdentityConnector.CLASS_NAME, "credential.proof", credential.proof);
|
|
576
|
+
const { proof, ...doc } = credential;
|
|
577
|
+
await this.verifyProof(JsonLdHelper.toNodeObject(doc), ArrayHelper.fromObjectOrArray(proof)[0]);
|
|
578
|
+
return {
|
|
579
|
+
revoked: false,
|
|
580
|
+
verifiableCredential: doc
|
|
581
|
+
};
|
|
582
|
+
}
|
|
583
|
+
Guards.stringValue(EntityStorageIdentityConnector.CLASS_NAME, "credential", credential);
|
|
477
584
|
try {
|
|
478
|
-
const jwtDecoded = await Jwt.decode(
|
|
585
|
+
const jwtDecoded = await Jwt.decode(credential);
|
|
479
586
|
const jwtHeader = jwtDecoded.header;
|
|
480
587
|
const jwtPayload = jwtDecoded.payload;
|
|
481
588
|
const jwtSignature = jwtDecoded.signature;
|
|
@@ -510,7 +617,7 @@ export class EntityStorageIdentityConnector {
|
|
|
510
617
|
method: jwtHeader.kid
|
|
511
618
|
});
|
|
512
619
|
}
|
|
513
|
-
await Jwt.verifySignature(
|
|
620
|
+
await Jwt.verifySignature(credential, await Jwk.toCryptoKey(didMethod.publicKeyJwk));
|
|
514
621
|
const verifiableCredential = jwtPayload.vc;
|
|
515
622
|
if (Is.object(verifiableCredential)) {
|
|
516
623
|
if (Is.string(jwtPayload.jti)) {
|
|
@@ -557,7 +664,7 @@ export class EntityStorageIdentityConnector {
|
|
|
557
664
|
* @param controller The controller of the identity who can make changes.
|
|
558
665
|
* @param issuerDocumentId The id of the document to update the revocation list for.
|
|
559
666
|
* @param credentialIndices The revocation bitmap index or indices to revoke.
|
|
560
|
-
* @returns
|
|
667
|
+
* @returns A promise that resolves when the credentials have been revoked.
|
|
561
668
|
*/
|
|
562
669
|
async revokeVerifiableCredentials(controller, issuerDocumentId, credentialIndices) {
|
|
563
670
|
Guards.stringValue(EntityStorageIdentityConnector.CLASS_NAME, "controller", controller);
|
|
@@ -597,7 +704,7 @@ export class EntityStorageIdentityConnector {
|
|
|
597
704
|
* @param controller The controller of the identity who can make changes.
|
|
598
705
|
* @param issuerDocumentId The id of the document to update the revocation list for.
|
|
599
706
|
* @param credentialIndices The revocation bitmap index or indices to un revoke.
|
|
600
|
-
* @returns
|
|
707
|
+
* @returns A promise that resolves when the credentials have been unrevoked.
|
|
601
708
|
*/
|
|
602
709
|
async unrevokeVerifiableCredentials(controller, issuerDocumentId, credentialIndices) {
|
|
603
710
|
Guards.stringValue(EntityStorageIdentityConnector.CLASS_NAME, "controller", controller);
|
|
@@ -640,11 +747,14 @@ export class EntityStorageIdentityConnector {
|
|
|
640
747
|
* @param contexts The contexts for the data stored in the verifiable credential.
|
|
641
748
|
* @param types The types for the data stored in the verifiable credential.
|
|
642
749
|
* @param verifiableCredentials The credentials to use for creating the presentation in jwt format.
|
|
643
|
-
* @param
|
|
750
|
+
* @param options Additional options for creating the verifiable presentation.
|
|
751
|
+
* @param options.expirationDate The date the verifiable presentation is valid until.
|
|
752
|
+
* @param options.jwtHeaderFields Additional fields to add to the JWT header.
|
|
753
|
+
* @param options.jwtPayloadFields Additional fields to add to the JWT payload.
|
|
644
754
|
* @returns The created verifiable presentation and its token.
|
|
645
755
|
* @throws NotFoundError if the id can not be resolved.
|
|
646
756
|
*/
|
|
647
|
-
async createVerifiablePresentation(controller, verificationMethodId, presentationId, contexts, types, verifiableCredentials,
|
|
757
|
+
async createVerifiablePresentation(controller, verificationMethodId, presentationId, contexts, types, verifiableCredentials, options) {
|
|
648
758
|
Guards.stringValue(EntityStorageIdentityConnector.CLASS_NAME, "controller", controller);
|
|
649
759
|
Guards.stringValue(EntityStorageIdentityConnector.CLASS_NAME, "verificationMethodId", verificationMethodId);
|
|
650
760
|
if (Is.array(types)) {
|
|
@@ -654,8 +764,8 @@ export class EntityStorageIdentityConnector {
|
|
|
654
764
|
Guards.stringValue(EntityStorageIdentityConnector.CLASS_NAME, "types", types);
|
|
655
765
|
}
|
|
656
766
|
Guards.arrayValue(EntityStorageIdentityConnector.CLASS_NAME, "verifiableCredentials", verifiableCredentials);
|
|
657
|
-
if (!Is.undefined(
|
|
658
|
-
Guards.
|
|
767
|
+
if (!Is.undefined(options?.expirationDate)) {
|
|
768
|
+
Guards.date(EntityStorageIdentityConnector.CLASS_NAME, "options.expirationDate", options.expirationDate);
|
|
659
769
|
}
|
|
660
770
|
try {
|
|
661
771
|
const idParts = DocumentHelper.parseId(verificationMethodId);
|
|
@@ -693,31 +803,37 @@ export class EntityStorageIdentityConnector {
|
|
|
693
803
|
else if (Is.stringValue(types)) {
|
|
694
804
|
finalTypes.push(types);
|
|
695
805
|
}
|
|
806
|
+
const combinedContext = JsonLdProcessor.combineContexts(DidContexts.ContextVCv1, contexts) ??
|
|
807
|
+
DidContexts.ContextVCv1;
|
|
696
808
|
const verifiablePresentation = {
|
|
697
|
-
"@context":
|
|
809
|
+
"@context": combinedContext,
|
|
698
810
|
id: presentationId,
|
|
699
811
|
type: finalTypes,
|
|
700
812
|
verifiableCredential: verifiableCredentials,
|
|
701
813
|
holder: idParts.id
|
|
702
814
|
};
|
|
703
815
|
const jwtHeader = {
|
|
816
|
+
...options?.jwtHeaderFields,
|
|
704
817
|
kid: didMethod.id,
|
|
705
818
|
typ: "JWT",
|
|
706
|
-
alg:
|
|
819
|
+
alg: JwsAlgorithms.EdDSA
|
|
707
820
|
};
|
|
708
821
|
const jwtVp = ObjectHelper.pick(ObjectHelper.clone(verifiablePresentation), [
|
|
709
822
|
"@context",
|
|
710
823
|
"type",
|
|
711
824
|
"verifiableCredential"
|
|
712
825
|
]);
|
|
826
|
+
// Add the proof to the VP after extracting the jwt data
|
|
827
|
+
// as the jwt does not include the proof
|
|
828
|
+
verifiablePresentation.proof = await this.createProof(controller, verificationMethodId, ProofTypes.DataIntegrityProof, JsonLdHelper.toNodeObject(verifiablePresentation));
|
|
713
829
|
const jwtPayload = {
|
|
714
|
-
|
|
830
|
+
...options?.jwtPayloadFields,
|
|
831
|
+
iss: verifiablePresentation.holder,
|
|
715
832
|
nbf: Math.floor(Date.now() / 1000),
|
|
716
833
|
vp: jwtVp
|
|
717
834
|
};
|
|
718
|
-
if (Is.
|
|
719
|
-
|
|
720
|
-
jwtPayload.exp = Math.floor(Date.now() / 1000) + expiresInSeconds;
|
|
835
|
+
if (Is.date(options?.expirationDate)) {
|
|
836
|
+
jwtPayload.exp = Math.floor(options.expirationDate.getTime() / 1000);
|
|
721
837
|
}
|
|
722
838
|
const signature = await Jwt.encodeWithSigner(jwtHeader, jwtPayload, async (header, payload) => VaultConnectorHelper.jwtSigner(this._vaultConnector, EntityStorageIdentityConnector.buildVaultKey(idParts.id, idParts.fragment ?? ""), header, payload));
|
|
723
839
|
return {
|
|
@@ -731,11 +847,19 @@ export class EntityStorageIdentityConnector {
|
|
|
731
847
|
}
|
|
732
848
|
/**
|
|
733
849
|
* Check a verifiable presentation is valid.
|
|
734
|
-
* @param
|
|
850
|
+
* @param presentation The presentation to verify.
|
|
735
851
|
* @returns The presentation stored in the jwt and the revocation status.
|
|
736
852
|
*/
|
|
737
|
-
async checkVerifiablePresentation(
|
|
738
|
-
|
|
853
|
+
async checkVerifiablePresentation(presentation) {
|
|
854
|
+
if (Is.object(presentation)) {
|
|
855
|
+
const { proof, ...doc } = presentation;
|
|
856
|
+
const proofEntry = ArrayHelper.fromObjectOrArray(proof)[0];
|
|
857
|
+
Guards.objectValue(EntityStorageIdentityConnector.CLASS_NAME, "proofEntry", proofEntry);
|
|
858
|
+
await this.verifyProof(JsonLdHelper.toNodeObject(doc), proofEntry);
|
|
859
|
+
return { revoked: false, verifiablePresentation: doc };
|
|
860
|
+
}
|
|
861
|
+
Guards.stringValue(EntityStorageIdentityConnector.CLASS_NAME, "presentation", presentation);
|
|
862
|
+
const presentationJwt = presentation;
|
|
739
863
|
try {
|
|
740
864
|
const jwtDecoded = await Jwt.decode(presentationJwt);
|
|
741
865
|
const jwtHeader = jwtDecoded.header;
|
|
@@ -771,7 +895,7 @@ export class EntityStorageIdentityConnector {
|
|
|
771
895
|
}
|
|
772
896
|
await EntityStorageIdentityConnector.verifyDocument(issuerDidDocument, this._vaultConnector);
|
|
773
897
|
issuers.push({
|
|
774
|
-
"@context": DidContexts.
|
|
898
|
+
"@context": DidContexts.Context,
|
|
775
899
|
...issuerDidDocument
|
|
776
900
|
});
|
|
777
901
|
const vc = jwt.payload.vc;
|
|
@@ -779,14 +903,14 @@ export class EntityStorageIdentityConnector {
|
|
|
779
903
|
const credentialStatus = vc.credentialStatus;
|
|
780
904
|
if (Is.object(credentialStatus)) {
|
|
781
905
|
revoked = await this.checkRevocation({
|
|
782
|
-
"@context": DidContexts.
|
|
906
|
+
"@context": DidContexts.Context,
|
|
783
907
|
...issuerDidDocument
|
|
784
908
|
}, credentialStatus.revocationBitmapIndex);
|
|
785
909
|
}
|
|
786
910
|
else if (Is.arrayValue(credentialStatus)) {
|
|
787
911
|
for (let i = 0; i < credentialStatus.length; i++) {
|
|
788
912
|
revoked = await this.checkRevocation({
|
|
789
|
-
"@context": DidContexts.
|
|
913
|
+
"@context": DidContexts.Context,
|
|
790
914
|
...issuerDidDocument
|
|
791
915
|
}, credentialStatus[i].revocationBitmapIndex);
|
|
792
916
|
if (revoked) {
|
|
@@ -820,11 +944,15 @@ export class EntityStorageIdentityConnector {
|
|
|
820
944
|
}
|
|
821
945
|
/**
|
|
822
946
|
* Create a proof for arbitrary data with the specified verification method.
|
|
947
|
+
* This method uses async signing to ensure the private key never leaves the vault,
|
|
948
|
+
* with algorithm validation to ensure key type compatibility.
|
|
823
949
|
* @param controller The controller of the identity who can make changes.
|
|
824
950
|
* @param verificationMethodId The verification method id to use.
|
|
825
951
|
* @param proofType The type of proof to create.
|
|
826
952
|
* @param unsecureDocument The unsecure document to create the proof for.
|
|
827
953
|
* @returns The proof.
|
|
954
|
+
* @throws NotFoundError if the identity or method is not found.
|
|
955
|
+
* @throws GeneralError if algorithm doesn't match key type or proof creation fails.
|
|
828
956
|
*/
|
|
829
957
|
async createProof(controller, verificationMethodId, proofType, unsecureDocument) {
|
|
830
958
|
Guards.stringValue(EntityStorageIdentityConnector.CLASS_NAME, "controller", controller);
|
|
@@ -861,8 +989,14 @@ export class EntityStorageIdentityConnector {
|
|
|
861
989
|
});
|
|
862
990
|
}
|
|
863
991
|
const vaultKey = EntityStorageIdentityConnector.buildVaultKey(didDocument.id, idParts.fragment ?? "");
|
|
864
|
-
const
|
|
865
|
-
|
|
992
|
+
const keyType = await this._vaultConnector.getKeyType(vaultKey);
|
|
993
|
+
if (Is.undefined(keyType)) {
|
|
994
|
+
throw new GeneralError(EntityStorageIdentityConnector.CLASS_NAME, "privateKeyMissing", {
|
|
995
|
+
keyId: vaultKey
|
|
996
|
+
});
|
|
997
|
+
}
|
|
998
|
+
const unsignedProof = ProofHelper.createUnsignedProof(proofType, verificationMethodId);
|
|
999
|
+
const signedProof = await ProofHelper.createProofWithSigner(proofType, unsecureDocument, unsignedProof, async (data, algorithm) => this.signWithVault(vaultKey, keyType, data, algorithm));
|
|
866
1000
|
return signedProof;
|
|
867
1001
|
}
|
|
868
1002
|
catch (error) {
|
|
@@ -915,6 +1049,27 @@ export class EntityStorageIdentityConnector {
|
|
|
915
1049
|
throw new GeneralError(EntityStorageIdentityConnector.CLASS_NAME, "verifyProofFailed", undefined, error);
|
|
916
1050
|
}
|
|
917
1051
|
}
|
|
1052
|
+
/**
|
|
1053
|
+
* Signs data using the vault connector with algorithm validation.
|
|
1054
|
+
* @param vaultKey The vault key identifier.
|
|
1055
|
+
* @param keyType The type of the key.
|
|
1056
|
+
* @param data The data to sign.
|
|
1057
|
+
* @param algorithm The signing algorithm.
|
|
1058
|
+
* @returns The signature bytes.
|
|
1059
|
+
* @throws GeneralError if algorithm doesn't match key type.
|
|
1060
|
+
* @internal
|
|
1061
|
+
*/
|
|
1062
|
+
async signWithVault(vaultKey, keyType, data, algorithm) {
|
|
1063
|
+
if (algorithm === JwsAlgorithms.EdDSA && keyType !== VaultKeyType.Ed25519) {
|
|
1064
|
+
throw new GeneralError(EntityStorageIdentityConnector.CLASS_NAME, "algorithmKeyTypeMismatch", {
|
|
1065
|
+
algorithm,
|
|
1066
|
+
expectedKeyType: VaultKeyType.Ed25519,
|
|
1067
|
+
actualKeyType: keyType,
|
|
1068
|
+
keyId: vaultKey
|
|
1069
|
+
});
|
|
1070
|
+
}
|
|
1071
|
+
return this._vaultConnector.sign(vaultKey, data);
|
|
1072
|
+
}
|
|
918
1073
|
/**
|
|
919
1074
|
* Get all the methods from a document.
|
|
920
1075
|
* @param document The document to get the methods from.
|
|
@@ -966,6 +1121,7 @@ export class EntityStorageIdentityConnector {
|
|
|
966
1121
|
* Update the document in storage.
|
|
967
1122
|
* @param controller The controller of the document.
|
|
968
1123
|
* @param didDocument The did document to store.
|
|
1124
|
+
* @returns A promise that resolves when the document has been signed and persisted.
|
|
969
1125
|
* @internal
|
|
970
1126
|
*/
|
|
971
1127
|
async updateDocument(controller, didDocument) {
|