@twin.org/identity-connector-entity-storage 0.9.2 → 0.9.3-next.1
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/es/entityStorageIdentityConnector.js +207 -138
- package/dist/es/entityStorageIdentityConnector.js.map +1 -1
- package/dist/es/index.js +1 -0
- package/dist/es/index.js.map +1 -1
- package/dist/es/models/IEntityStorageIdentityConnectorConfig.js +4 -0
- package/dist/es/models/IEntityStorageIdentityConnectorConfig.js.map +1 -0
- package/dist/es/models/IEntityStorageIdentityConnectorConstructorOptions.js.map +1 -1
- package/dist/types/entityStorageIdentityConnector.d.ts +11 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/models/IEntityStorageIdentityConnectorConfig.d.ts +26 -0
- package/dist/types/models/IEntityStorageIdentityConnectorConstructorOptions.d.ts +5 -0
- package/docs/changelog.md +55 -0
- package/docs/reference/classes/EntityStorageIdentityConnector.md +36 -1
- package/docs/reference/index.md +1 -0
- package/docs/reference/interfaces/IEntityStorageIdentityConnectorConfig.md +46 -0
- package/docs/reference/interfaces/IEntityStorageIdentityConnectorConstructorOptions.md +8 -0
- package/locales/en.json +1 -0
- package/package.json +11 -11
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// Copyright 2024 IOTA Stiftung.
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0.
|
|
3
|
-
import { ArrayHelper, BaseError, BitString, Coerce, Compression, CompressionType, Converter, GeneralError, Guards, Is, JsonHelper, NotFoundError, ObjectHelper, RandomHelper, Url, Urn } from "@twin.org/core";
|
|
3
|
+
import { ArrayHelper, BaseError, BitString, Coerce, Compression, CompressionType, Converter, GeneralError, Guards, Is, JsonHelper, LruCache, NotFoundError, ObjectHelper, RandomHelper, Url, Urn } from "@twin.org/core";
|
|
4
4
|
import { JsonLdHelper, JsonLdProcessor } from "@twin.org/data-json-ld";
|
|
5
5
|
import { EntityStorageConnectorFactory } from "@twin.org/entity-storage-models";
|
|
6
|
-
import { DocumentHelper } from "@twin.org/identity-models";
|
|
6
|
+
import { DocumentHelper, VerificationHelper } from "@twin.org/identity-models";
|
|
7
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";
|
|
@@ -34,6 +34,28 @@ export class EntityStorageIdentityConnector {
|
|
|
34
34
|
* @internal
|
|
35
35
|
*/
|
|
36
36
|
_vaultConnector;
|
|
37
|
+
/**
|
|
38
|
+
* TTL in ms for caching DID documents resolved for this connector's own sign/mutate
|
|
39
|
+
* operations. 0 disables caching (see resolveOwnDocumentCached). Never applied to proof or
|
|
40
|
+
* credential verification of third-party claims.
|
|
41
|
+
* @internal
|
|
42
|
+
*/
|
|
43
|
+
_didResolutionCacheTtlMs;
|
|
44
|
+
/**
|
|
45
|
+
* Maximum number of own DID documents retained in cache.
|
|
46
|
+
* @internal
|
|
47
|
+
*/
|
|
48
|
+
_didResolutionCacheCapacity;
|
|
49
|
+
/**
|
|
50
|
+
* Maximum wait time for own DID cache getOrSet mutex acquisition in milliseconds.
|
|
51
|
+
* @internal
|
|
52
|
+
*/
|
|
53
|
+
_didResolutionCacheMutexTimeoutMs;
|
|
54
|
+
/**
|
|
55
|
+
* LRU cache for own DID documents. Undefined when caching is disabled (ttl is 0).
|
|
56
|
+
* @internal
|
|
57
|
+
*/
|
|
58
|
+
_didResolutionCache;
|
|
37
59
|
/**
|
|
38
60
|
* Create a new instance of EntityStorageIdentityConnector.
|
|
39
61
|
* @param options The options for the identity connector.
|
|
@@ -41,6 +63,18 @@ export class EntityStorageIdentityConnector {
|
|
|
41
63
|
constructor(options) {
|
|
42
64
|
this._didDocumentEntityStorage = EntityStorageConnectorFactory.get(options?.didDocumentEntityStorageType ?? "identity-document");
|
|
43
65
|
this._vaultConnector = VaultConnectorFactory.get(options?.vaultConnectorType ?? "vault");
|
|
66
|
+
const config = options?.config ?? {};
|
|
67
|
+
this._didResolutionCacheTtlMs = config.didResolutionCacheTtlMs ?? 30_000;
|
|
68
|
+
this._didResolutionCacheCapacity = config.didResolutionCacheCapacity ?? 1000;
|
|
69
|
+
this._didResolutionCacheMutexTimeoutMs = config.didResolutionCacheMutexTimeoutMs;
|
|
70
|
+
this._didResolutionCache =
|
|
71
|
+
this._didResolutionCacheTtlMs > 0
|
|
72
|
+
? new LruCache({
|
|
73
|
+
capacity: this._didResolutionCacheCapacity,
|
|
74
|
+
ttiMs: this._didResolutionCacheTtlMs,
|
|
75
|
+
mutexTimeoutMs: this._didResolutionCacheMutexTimeoutMs
|
|
76
|
+
})
|
|
77
|
+
: undefined;
|
|
44
78
|
}
|
|
45
79
|
/**
|
|
46
80
|
* Build the key name to access the specified key in the vault.
|
|
@@ -75,6 +109,15 @@ export class EntityStorageIdentityConnector {
|
|
|
75
109
|
className() {
|
|
76
110
|
return EntityStorageIdentityConnector.CLASS_NAME;
|
|
77
111
|
}
|
|
112
|
+
/**
|
|
113
|
+
* Stop the service.
|
|
114
|
+
* Destroys in-memory resources owned by this component.
|
|
115
|
+
* @param nodeLoggingComponentType The node logging component type.
|
|
116
|
+
* @returns A promise that resolves when the service has stopped.
|
|
117
|
+
*/
|
|
118
|
+
async stop(nodeLoggingComponentType) {
|
|
119
|
+
this._didResolutionCache?.destroy();
|
|
120
|
+
}
|
|
78
121
|
/**
|
|
79
122
|
* Create a new document.
|
|
80
123
|
* @param controller The controller of the identity who can make changes.
|
|
@@ -122,6 +165,7 @@ export class EntityStorageIdentityConnector {
|
|
|
122
165
|
throw new NotFoundError(EntityStorageIdentityConnector.CLASS_NAME, "documentNotFound", documentId);
|
|
123
166
|
}
|
|
124
167
|
await this._didDocumentEntityStorage.remove(documentId);
|
|
168
|
+
this._didResolutionCache?.delete(this.ownDidCacheKey(documentId));
|
|
125
169
|
if (options?.removeKeys ?? false) {
|
|
126
170
|
const methods = this.getAllMethods(didDocument.document);
|
|
127
171
|
for (const { method } of methods) {
|
|
@@ -158,11 +202,7 @@ export class EntityStorageIdentityConnector {
|
|
|
158
202
|
Guards.arrayOneOf(EntityStorageIdentityConnector.CLASS_NAME, "verificationMethodType", verificationMethodType, Object.values(DidVerificationMethodType));
|
|
159
203
|
let tempKeyId;
|
|
160
204
|
try {
|
|
161
|
-
const didIdentityDocument = await this.
|
|
162
|
-
if (Is.undefined(didIdentityDocument)) {
|
|
163
|
-
throw new NotFoundError(EntityStorageIdentityConnector.CLASS_NAME, "documentNotFound", documentId);
|
|
164
|
-
}
|
|
165
|
-
await EntityStorageIdentityConnector.verifyDocument(didIdentityDocument, this._vaultConnector);
|
|
205
|
+
const didIdentityDocument = await this.resolveOwnDocumentCached(documentId);
|
|
166
206
|
const didDocument = didIdentityDocument.document;
|
|
167
207
|
let methodKeyPublic;
|
|
168
208
|
if (Is.stringValue(verificationMethodId)) {
|
|
@@ -250,11 +290,7 @@ export class EntityStorageIdentityConnector {
|
|
|
250
290
|
if (Is.empty(idParts.fragment)) {
|
|
251
291
|
throw new NotFoundError(EntityStorageIdentityConnector.CLASS_NAME, "missingDid", verificationMethodId);
|
|
252
292
|
}
|
|
253
|
-
const didIdentityDocument = await this.
|
|
254
|
-
if (Is.undefined(didIdentityDocument)) {
|
|
255
|
-
throw new NotFoundError(EntityStorageIdentityConnector.CLASS_NAME, "documentNotFound", idParts.id);
|
|
256
|
-
}
|
|
257
|
-
await EntityStorageIdentityConnector.verifyDocument(didIdentityDocument, this._vaultConnector);
|
|
293
|
+
const didIdentityDocument = await this.resolveOwnDocumentCached(idParts.id);
|
|
258
294
|
const didDocument = didIdentityDocument.document;
|
|
259
295
|
const methods = this.getAllMethods(didDocument);
|
|
260
296
|
const existingMethodIndex = methods.findIndex(m => {
|
|
@@ -314,11 +350,7 @@ export class EntityStorageIdentityConnector {
|
|
|
314
350
|
Guards.stringValue(EntityStorageIdentityConnector.CLASS_NAME, "serviceEndpoint", serviceEndpoint);
|
|
315
351
|
}
|
|
316
352
|
try {
|
|
317
|
-
const didIdentityDocument = await this.
|
|
318
|
-
if (Is.undefined(didIdentityDocument)) {
|
|
319
|
-
throw new NotFoundError(EntityStorageIdentityConnector.CLASS_NAME, "documentNotFound", documentId);
|
|
320
|
-
}
|
|
321
|
-
await EntityStorageIdentityConnector.verifyDocument(didIdentityDocument, this._vaultConnector);
|
|
353
|
+
const didIdentityDocument = await this.resolveOwnDocumentCached(documentId);
|
|
322
354
|
const didDocument = didIdentityDocument.document;
|
|
323
355
|
const fullServiceId = serviceId.includes("#") ? serviceId : `${documentId}#${serviceId}`;
|
|
324
356
|
if (Is.array(didDocument.service)) {
|
|
@@ -356,11 +388,7 @@ export class EntityStorageIdentityConnector {
|
|
|
356
388
|
if (Is.empty(idParts.fragment)) {
|
|
357
389
|
throw new NotFoundError(EntityStorageIdentityConnector.CLASS_NAME, "missingDid", serviceId);
|
|
358
390
|
}
|
|
359
|
-
const didIdentityDocument = await this.
|
|
360
|
-
if (Is.undefined(didIdentityDocument)) {
|
|
361
|
-
throw new NotFoundError(EntityStorageIdentityConnector.CLASS_NAME, "documentNotFound", idParts.id);
|
|
362
|
-
}
|
|
363
|
-
await EntityStorageIdentityConnector.verifyDocument(didIdentityDocument, this._vaultConnector);
|
|
391
|
+
const didIdentityDocument = await this.resolveOwnDocumentCached(idParts.id);
|
|
364
392
|
const didDocument = didIdentityDocument.document;
|
|
365
393
|
if (Is.array(didDocument.service)) {
|
|
366
394
|
const existingServiceIndex = didDocument.service.findIndex(s => s.id === serviceId);
|
|
@@ -401,11 +429,7 @@ export class EntityStorageIdentityConnector {
|
|
|
401
429
|
throw new GeneralError(EntityStorageIdentityConnector.CLASS_NAME, "invalidAlias", { alias });
|
|
402
430
|
}
|
|
403
431
|
try {
|
|
404
|
-
const didIdentityDocument = await this.
|
|
405
|
-
if (Is.undefined(didIdentityDocument)) {
|
|
406
|
-
throw new NotFoundError(EntityStorageIdentityConnector.CLASS_NAME, "documentNotFound", documentId);
|
|
407
|
-
}
|
|
408
|
-
await EntityStorageIdentityConnector.verifyDocument(didIdentityDocument, this._vaultConnector);
|
|
432
|
+
const didIdentityDocument = await this.resolveOwnDocumentCached(documentId);
|
|
409
433
|
const didDocument = didIdentityDocument.document;
|
|
410
434
|
const existing = Is.array(didDocument.alsoKnownAs) ? didDocument.alsoKnownAs : [];
|
|
411
435
|
if (existing.includes(alias)) {
|
|
@@ -436,11 +460,7 @@ export class EntityStorageIdentityConnector {
|
|
|
436
460
|
throw new GeneralError(EntityStorageIdentityConnector.CLASS_NAME, "invalidAlias", { alias });
|
|
437
461
|
}
|
|
438
462
|
try {
|
|
439
|
-
const didIdentityDocument = await this.
|
|
440
|
-
if (Is.undefined(didIdentityDocument)) {
|
|
441
|
-
throw new NotFoundError(EntityStorageIdentityConnector.CLASS_NAME, "documentNotFound", documentId);
|
|
442
|
-
}
|
|
443
|
-
await EntityStorageIdentityConnector.verifyDocument(didIdentityDocument, this._vaultConnector);
|
|
463
|
+
const didIdentityDocument = await this.resolveOwnDocumentCached(documentId);
|
|
444
464
|
const didDocument = didIdentityDocument.document;
|
|
445
465
|
if (!Is.array(didDocument.alsoKnownAs) || !didDocument.alsoKnownAs.includes(alias)) {
|
|
446
466
|
return;
|
|
@@ -487,11 +507,7 @@ export class EntityStorageIdentityConnector {
|
|
|
487
507
|
if (Is.empty(idParts.fragment)) {
|
|
488
508
|
throw new NotFoundError(EntityStorageIdentityConnector.CLASS_NAME, "missingDid", verificationMethodId);
|
|
489
509
|
}
|
|
490
|
-
const issuerIdentityDocument = await this.
|
|
491
|
-
if (Is.undefined(issuerIdentityDocument)) {
|
|
492
|
-
throw new NotFoundError(EntityStorageIdentityConnector.CLASS_NAME, "documentNotFound", idParts.id);
|
|
493
|
-
}
|
|
494
|
-
await EntityStorageIdentityConnector.verifyDocument(issuerIdentityDocument, this._vaultConnector);
|
|
510
|
+
const issuerIdentityDocument = await this.resolveOwnDocumentCached(idParts.id);
|
|
495
511
|
const issuerDidDocument = issuerIdentityDocument.document;
|
|
496
512
|
const methods = this.getAllMethods(issuerDidDocument);
|
|
497
513
|
const methodAndArray = methods.find(m => {
|
|
@@ -552,7 +568,7 @@ export class EntityStorageIdentityConnector {
|
|
|
552
568
|
]);
|
|
553
569
|
// Add the proof to the VC after extracting the jwt data
|
|
554
570
|
// as the jwt does not include the proof
|
|
555
|
-
verifiableCredential.proof = await this.createProof(controller, verificationMethodId, ProofTypes.DataIntegrityProof, JsonLdHelper.toNodeObject(verifiableCredential));
|
|
571
|
+
verifiableCredential.proof = await this.createProof(controller, verificationMethodId, ProofTypes.DataIntegrityProof, JsonLdHelper.toNodeObject(verifiableCredential), issuerDidDocument);
|
|
556
572
|
// As we are adding the receipt to the data we update the JSON-LD context
|
|
557
573
|
const proofContext = verifiableCredential.proof["@context"];
|
|
558
574
|
if (!Is.empty(proofContext)) {
|
|
@@ -598,14 +614,28 @@ export class EntityStorageIdentityConnector {
|
|
|
598
614
|
if (Is.object(credential)) {
|
|
599
615
|
Guards.objectValue(EntityStorageIdentityConnector.CLASS_NAME, "credential", credential);
|
|
600
616
|
Guards.objectValue(EntityStorageIdentityConnector.CLASS_NAME, "credential.proof", credential.proof);
|
|
617
|
+
VerificationHelper.checkValidityPeriod(credential);
|
|
601
618
|
const { proof, ...doc } = credential;
|
|
602
|
-
const
|
|
619
|
+
const proofEntry = ArrayHelper.fromObjectOrArray(proof)[0];
|
|
620
|
+
Guards.stringValue(EntityStorageIdentityConnector.CLASS_NAME, "proofEntry.verificationMethod", proofEntry.verificationMethod);
|
|
621
|
+
const issuer = Is.object(doc.issuer) ? doc.issuer.id : doc.issuer;
|
|
622
|
+
const signerDid = DocumentHelper.parseId(proofEntry.verificationMethod).id;
|
|
623
|
+
if (Is.stringValue(issuer) && issuer !== signerDid) {
|
|
624
|
+
throw new GeneralError(EntityStorageIdentityConnector.CLASS_NAME, "issuerMismatch", {
|
|
625
|
+
issuer,
|
|
626
|
+
method: proofEntry.verificationMethod
|
|
627
|
+
});
|
|
628
|
+
}
|
|
629
|
+
const issuerDidDocument = await this.resolveAssertionMethodDocument(proofEntry.verificationMethod);
|
|
630
|
+
const publicKeyJwk = DocumentHelper.getJwk(issuerDidDocument, proofEntry.verificationMethod, DidVerificationMethodType.AssertionMethod);
|
|
631
|
+
const credentialVerified = await ProofHelper.verifyProof(JsonLdHelper.toNodeObject(doc), proofEntry, publicKeyJwk);
|
|
603
632
|
if (!credentialVerified) {
|
|
604
633
|
throw new GeneralError(EntityStorageIdentityConnector.CLASS_NAME, "signatureVerificationFailed");
|
|
605
634
|
}
|
|
635
|
+
const revoked = await this.checkCredentialStatusRevoked(issuerDidDocument, doc.credentialStatus);
|
|
606
636
|
return {
|
|
607
|
-
revoked
|
|
608
|
-
verifiableCredential: doc
|
|
637
|
+
revoked,
|
|
638
|
+
verifiableCredential: revoked ? undefined : doc
|
|
609
639
|
};
|
|
610
640
|
}
|
|
611
641
|
Guards.stringValue(EntityStorageIdentityConnector.CLASS_NAME, "credential", credential);
|
|
@@ -627,25 +657,9 @@ export class EntityStorageIdentityConnector {
|
|
|
627
657
|
}
|
|
628
658
|
await EntityStorageIdentityConnector.verifyDocument(issuerIdentityDocument, this._vaultConnector);
|
|
629
659
|
const issuerDidDocument = issuerIdentityDocument.document;
|
|
630
|
-
|
|
631
|
-
const
|
|
632
|
-
|
|
633
|
-
return m.method === jwtHeader.kid;
|
|
634
|
-
}
|
|
635
|
-
return m.method.id === jwtHeader.kid;
|
|
636
|
-
});
|
|
637
|
-
if (!methodAndArray) {
|
|
638
|
-
throw new GeneralError(EntityStorageIdentityConnector.CLASS_NAME, "methodMissing", {
|
|
639
|
-
method: jwtHeader.kid
|
|
640
|
-
});
|
|
641
|
-
}
|
|
642
|
-
const didMethod = methodAndArray.method;
|
|
643
|
-
if (!Is.stringValue(didMethod.publicKeyJwk?.x)) {
|
|
644
|
-
throw new GeneralError(EntityStorageIdentityConnector.CLASS_NAME, "publicKeyJwkMissing", {
|
|
645
|
-
method: jwtHeader.kid
|
|
646
|
-
});
|
|
647
|
-
}
|
|
648
|
-
await Jwt.verifySignature(credential, await Jwk.toCryptoKey(didMethod.publicKeyJwk));
|
|
660
|
+
Guards.stringValue(EntityStorageIdentityConnector.CLASS_NAME, "jwtHeader.kid", jwtHeader.kid);
|
|
661
|
+
const publicKeyJwk = DocumentHelper.getJwk(issuerDidDocument, jwtHeader.kid, DidVerificationMethodType.AssertionMethod);
|
|
662
|
+
await Jwt.verifySignature(credential, await Jwk.toCryptoKey(publicKeyJwk));
|
|
649
663
|
const verifiableCredential = jwtPayload.vc;
|
|
650
664
|
if (Is.object(verifiableCredential)) {
|
|
651
665
|
if (Is.string(jwtPayload.jti)) {
|
|
@@ -655,6 +669,9 @@ export class EntityStorageIdentityConnector {
|
|
|
655
669
|
if (Is.number(jwtPayload.nbf)) {
|
|
656
670
|
verifiableCredential.issuanceDate = new Date(jwtPayload.nbf * 1000).toISOString();
|
|
657
671
|
}
|
|
672
|
+
if (Is.number(jwtPayload.exp)) {
|
|
673
|
+
verifiableCredential.expirationDate = new Date(jwtPayload.exp * 1000).toISOString();
|
|
674
|
+
}
|
|
658
675
|
if (Is.array(verifiableCredential.credentialSubject)) {
|
|
659
676
|
verifiableCredential.credentialSubject = verifiableCredential.credentialSubject.map(c => {
|
|
660
677
|
ObjectHelper.propertySet(c, "id", jwtPayload.sub);
|
|
@@ -665,19 +682,8 @@ export class EntityStorageIdentityConnector {
|
|
|
665
682
|
ObjectHelper.propertySet(verifiableCredential.credentialSubject, "id", jwtPayload.sub);
|
|
666
683
|
}
|
|
667
684
|
}
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
if (Is.object(credentialStatus)) {
|
|
671
|
-
revoked = await this.checkRevocation(issuerDidDocument, credentialStatus.revocationBitmapIndex);
|
|
672
|
-
}
|
|
673
|
-
else if (Is.arrayValue(credentialStatus)) {
|
|
674
|
-
for (let i = 0; i < credentialStatus.length; i++) {
|
|
675
|
-
revoked = await this.checkRevocation(issuerDidDocument, credentialStatus[i].revocationBitmapIndex);
|
|
676
|
-
if (revoked) {
|
|
677
|
-
break;
|
|
678
|
-
}
|
|
679
|
-
}
|
|
680
|
-
}
|
|
685
|
+
VerificationHelper.checkValidityPeriod(verifiableCredential);
|
|
686
|
+
const revoked = await this.checkCredentialStatusRevoked(issuerDidDocument, verifiableCredential.credentialStatus);
|
|
681
687
|
return {
|
|
682
688
|
revoked,
|
|
683
689
|
verifiableCredential: revoked ? undefined : verifiableCredential
|
|
@@ -699,11 +705,7 @@ export class EntityStorageIdentityConnector {
|
|
|
699
705
|
Guards.stringValue(EntityStorageIdentityConnector.CLASS_NAME, "issuerDocumentId", issuerDocumentId);
|
|
700
706
|
Guards.arrayValue(EntityStorageIdentityConnector.CLASS_NAME, "credentialIndices", credentialIndices);
|
|
701
707
|
try {
|
|
702
|
-
const issuerIdentityDocument = await this.
|
|
703
|
-
if (Is.undefined(issuerIdentityDocument)) {
|
|
704
|
-
throw new NotFoundError(EntityStorageIdentityConnector.CLASS_NAME, "documentNotFound", issuerDocumentId);
|
|
705
|
-
}
|
|
706
|
-
await EntityStorageIdentityConnector.verifyDocument(issuerIdentityDocument, this._vaultConnector);
|
|
708
|
+
const issuerIdentityDocument = await this.resolveOwnDocumentCached(issuerDocumentId);
|
|
707
709
|
const issuerDidDocument = issuerIdentityDocument.document;
|
|
708
710
|
const revocationService = issuerDidDocument.service?.find(s => s.id.endsWith("#revocation"));
|
|
709
711
|
if (revocationService &&
|
|
@@ -739,11 +741,7 @@ export class EntityStorageIdentityConnector {
|
|
|
739
741
|
Guards.stringValue(EntityStorageIdentityConnector.CLASS_NAME, "issuerDocumentId", issuerDocumentId);
|
|
740
742
|
Guards.arrayValue(EntityStorageIdentityConnector.CLASS_NAME, "credentialIndices", credentialIndices);
|
|
741
743
|
try {
|
|
742
|
-
const issuerIdentityDocument = await this.
|
|
743
|
-
if (Is.undefined(issuerIdentityDocument)) {
|
|
744
|
-
throw new NotFoundError(EntityStorageIdentityConnector.CLASS_NAME, "documentNotFound", issuerDocumentId);
|
|
745
|
-
}
|
|
746
|
-
await EntityStorageIdentityConnector.verifyDocument(issuerIdentityDocument, this._vaultConnector);
|
|
744
|
+
const issuerIdentityDocument = await this.resolveOwnDocumentCached(issuerDocumentId);
|
|
747
745
|
const issuerDidDocument = issuerIdentityDocument.document;
|
|
748
746
|
const revocationService = issuerDidDocument.service?.find(s => s.id.endsWith("#revocation"));
|
|
749
747
|
if (revocationService &&
|
|
@@ -800,11 +798,7 @@ export class EntityStorageIdentityConnector {
|
|
|
800
798
|
if (Is.empty(idParts.fragment)) {
|
|
801
799
|
throw new NotFoundError(EntityStorageIdentityConnector.CLASS_NAME, "missingDid", verificationMethodId);
|
|
802
800
|
}
|
|
803
|
-
const holderIdentityDocument = await this.
|
|
804
|
-
if (Is.undefined(holderIdentityDocument)) {
|
|
805
|
-
throw new NotFoundError(EntityStorageIdentityConnector.CLASS_NAME, "documentNotFound", idParts.id);
|
|
806
|
-
}
|
|
807
|
-
await EntityStorageIdentityConnector.verifyDocument(holderIdentityDocument, this._vaultConnector);
|
|
801
|
+
const holderIdentityDocument = await this.resolveOwnDocumentCached(idParts.id);
|
|
808
802
|
const holderDidDocument = holderIdentityDocument.document;
|
|
809
803
|
const methods = this.getAllMethods(holderDidDocument);
|
|
810
804
|
const methodAndArray = methods.find(m => {
|
|
@@ -853,7 +847,7 @@ export class EntityStorageIdentityConnector {
|
|
|
853
847
|
]);
|
|
854
848
|
// Add the proof to the VP after extracting the jwt data
|
|
855
849
|
// as the jwt does not include the proof
|
|
856
|
-
verifiablePresentation.proof = await this.createProof(controller, verificationMethodId, ProofTypes.DataIntegrityProof, JsonLdHelper.toNodeObject(verifiablePresentation));
|
|
850
|
+
verifiablePresentation.proof = await this.createProof(controller, verificationMethodId, ProofTypes.DataIntegrityProof, JsonLdHelper.toNodeObject(verifiablePresentation), holderDidDocument);
|
|
857
851
|
const jwtPayload = {
|
|
858
852
|
...options?.jwtPayloadFields,
|
|
859
853
|
iss: verifiablePresentation.holder,
|
|
@@ -887,7 +881,15 @@ export class EntityStorageIdentityConnector {
|
|
|
887
881
|
if (!presentationVerified) {
|
|
888
882
|
throw new GeneralError(EntityStorageIdentityConnector.CLASS_NAME, "signatureVerificationFailed");
|
|
889
883
|
}
|
|
890
|
-
|
|
884
|
+
let revoked = false;
|
|
885
|
+
for (const embeddedCredential of doc.verifiableCredential ?? []) {
|
|
886
|
+
const credentialCheck = await this.checkVerifiableCredential(embeddedCredential);
|
|
887
|
+
if (credentialCheck.revoked) {
|
|
888
|
+
revoked = true;
|
|
889
|
+
break;
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
return { revoked, verifiablePresentation: doc };
|
|
891
893
|
}
|
|
892
894
|
Guards.stringValue(EntityStorageIdentityConnector.CLASS_NAME, "presentation", presentation);
|
|
893
895
|
const presentationJwt = presentation;
|
|
@@ -914,7 +916,7 @@ export class EntityStorageIdentityConnector {
|
|
|
914
916
|
if (Is.object(verifiablePresentation) &&
|
|
915
917
|
Is.array(verifiablePresentation.verifiableCredential)) {
|
|
916
918
|
for (const vcJwt of verifiablePresentation.verifiableCredential) {
|
|
917
|
-
let revoked =
|
|
919
|
+
let revoked = false;
|
|
918
920
|
if (Is.stringValue(vcJwt)) {
|
|
919
921
|
const jwt = await Jwt.decode(vcJwt);
|
|
920
922
|
if (Is.string(jwt.payload?.iss)) {
|
|
@@ -929,31 +931,9 @@ export class EntityStorageIdentityConnector {
|
|
|
929
931
|
"@context": DidContexts.Context,
|
|
930
932
|
...issuerDidDocument
|
|
931
933
|
});
|
|
932
|
-
const vc = jwt.payload.vc;
|
|
933
|
-
if (Is.object(vc)) {
|
|
934
|
-
const credentialStatus = vc.credentialStatus;
|
|
935
|
-
if (Is.object(credentialStatus)) {
|
|
936
|
-
revoked = await this.checkRevocation({
|
|
937
|
-
"@context": DidContexts.Context,
|
|
938
|
-
...issuerDidDocument
|
|
939
|
-
}, credentialStatus.revocationBitmapIndex);
|
|
940
|
-
}
|
|
941
|
-
else if (Is.arrayValue(credentialStatus)) {
|
|
942
|
-
for (let i = 0; i < credentialStatus.length; i++) {
|
|
943
|
-
revoked = await this.checkRevocation({
|
|
944
|
-
"@context": DidContexts.Context,
|
|
945
|
-
...issuerDidDocument
|
|
946
|
-
}, credentialStatus[i].revocationBitmapIndex);
|
|
947
|
-
if (revoked) {
|
|
948
|
-
break;
|
|
949
|
-
}
|
|
950
|
-
}
|
|
951
|
-
}
|
|
952
|
-
}
|
|
953
934
|
}
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
revoked = false;
|
|
935
|
+
const credentialCheck = await this.checkVerifiableCredential(vcJwt);
|
|
936
|
+
revoked = credentialCheck.revoked;
|
|
957
937
|
}
|
|
958
938
|
tokensRevoked.push(revoked);
|
|
959
939
|
}
|
|
@@ -981,26 +961,29 @@ export class EntityStorageIdentityConnector {
|
|
|
981
961
|
* @param verificationMethodId The verification method id to use.
|
|
982
962
|
* @param proofType The type of proof to create.
|
|
983
963
|
* @param unsecureDocument The unsecure document to create the proof for.
|
|
964
|
+
* @param resolvedDocument Optional already-resolved document for the DID, so a caller that
|
|
965
|
+
* just resolved it (e.g. createVerifiableCredential) skips a redundant re-resolve. Resolves
|
|
966
|
+
* it itself if omitted.
|
|
984
967
|
* @returns The proof.
|
|
985
968
|
* @throws NotFoundError if the identity or method is not found.
|
|
986
969
|
* @throws GeneralError if algorithm doesn't match key type or proof creation fails.
|
|
987
970
|
*/
|
|
988
|
-
async createProof(controller, verificationMethodId, proofType, unsecureDocument) {
|
|
971
|
+
async createProof(controller, verificationMethodId, proofType, unsecureDocument, resolvedDocument) {
|
|
989
972
|
Guards.stringValue(EntityStorageIdentityConnector.CLASS_NAME, "controller", controller);
|
|
990
973
|
Guards.stringValue(EntityStorageIdentityConnector.CLASS_NAME, "verificationMethodId", verificationMethodId);
|
|
991
974
|
Guards.arrayOneOf(EntityStorageIdentityConnector.CLASS_NAME, "proofType", proofType, Object.values(ProofTypes));
|
|
992
975
|
Guards.object(EntityStorageIdentityConnector.CLASS_NAME, "unsecureDocument", unsecureDocument);
|
|
976
|
+
if (!Is.undefined(resolvedDocument)) {
|
|
977
|
+
Guards.object(EntityStorageIdentityConnector.CLASS_NAME, "resolvedDocument", resolvedDocument);
|
|
978
|
+
}
|
|
993
979
|
try {
|
|
994
980
|
const idParts = DocumentHelper.parseId(verificationMethodId);
|
|
995
981
|
if (Is.empty(idParts.fragment)) {
|
|
996
982
|
throw new NotFoundError(EntityStorageIdentityConnector.CLASS_NAME, "missingDid", verificationMethodId);
|
|
997
983
|
}
|
|
998
|
-
const
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
}
|
|
1002
|
-
await EntityStorageIdentityConnector.verifyDocument(didIdentityDocument, this._vaultConnector);
|
|
1003
|
-
const didDocument = didIdentityDocument.document;
|
|
984
|
+
const didDocument = Is.undefined(resolvedDocument)
|
|
985
|
+
? (await this.resolveOwnDocumentCached(idParts.id)).document
|
|
986
|
+
: resolvedDocument;
|
|
1004
987
|
const methods = this.getAllMethods(didDocument);
|
|
1005
988
|
const methodAndArray = methods.find(m => {
|
|
1006
989
|
if (Is.string(m.method)) {
|
|
@@ -1121,6 +1104,92 @@ export class EntityStorageIdentityConnector {
|
|
|
1121
1104
|
}
|
|
1122
1105
|
return methods;
|
|
1123
1106
|
}
|
|
1107
|
+
/**
|
|
1108
|
+
* Resolve the issuer document for a verification method, verified against its stored signature.
|
|
1109
|
+
* @param verificationMethodId The verification method id whose owning DID to resolve.
|
|
1110
|
+
* @returns The resolved document.
|
|
1111
|
+
* @throws NotFoundError if the id has no DID, or the DID cannot be resolved.
|
|
1112
|
+
* @internal
|
|
1113
|
+
*/
|
|
1114
|
+
async resolveAssertionMethodDocument(verificationMethodId) {
|
|
1115
|
+
const idParts = DocumentHelper.parseId(verificationMethodId);
|
|
1116
|
+
if (Is.empty(idParts.fragment)) {
|
|
1117
|
+
throw new NotFoundError(EntityStorageIdentityConnector.CLASS_NAME, "missingDid", verificationMethodId);
|
|
1118
|
+
}
|
|
1119
|
+
const identityDocument = await this._didDocumentEntityStorage.get(idParts.id);
|
|
1120
|
+
if (Is.undefined(identityDocument)) {
|
|
1121
|
+
throw new NotFoundError(EntityStorageIdentityConnector.CLASS_NAME, "documentNotFound", idParts.id);
|
|
1122
|
+
}
|
|
1123
|
+
await EntityStorageIdentityConnector.verifyDocument(identityDocument, this._vaultConnector);
|
|
1124
|
+
return identityDocument.document;
|
|
1125
|
+
}
|
|
1126
|
+
/**
|
|
1127
|
+
* Cache key for a DID resolved for this connector's own sign/mutate operations (role 1).
|
|
1128
|
+
* Deliberately namespaced ("own") and never shared with proof/credential verification of
|
|
1129
|
+
* third-party claims (role 2/3), which this connector never caches.
|
|
1130
|
+
* @param documentId The DID being resolved.
|
|
1131
|
+
* @returns The cache key.
|
|
1132
|
+
* @internal
|
|
1133
|
+
*/
|
|
1134
|
+
ownDidCacheKey(documentId) {
|
|
1135
|
+
return `${EntityStorageIdentityConnector.CLASS_NAME}:own:${documentId}`;
|
|
1136
|
+
}
|
|
1137
|
+
/**
|
|
1138
|
+
* Load and verify a document's own storage entity, uncached.
|
|
1139
|
+
* @param documentId The DID to load.
|
|
1140
|
+
* @returns The verified storage entity.
|
|
1141
|
+
* @throws NotFoundError if the DID could not be resolved.
|
|
1142
|
+
* @internal
|
|
1143
|
+
*/
|
|
1144
|
+
async loadOwnDocument(documentId) {
|
|
1145
|
+
const identityDocument = await this._didDocumentEntityStorage.get(documentId);
|
|
1146
|
+
if (Is.undefined(identityDocument)) {
|
|
1147
|
+
throw new NotFoundError(EntityStorageIdentityConnector.CLASS_NAME, "documentNotFound", documentId);
|
|
1148
|
+
}
|
|
1149
|
+
await EntityStorageIdentityConnector.verifyDocument(identityDocument, this._vaultConnector);
|
|
1150
|
+
return identityDocument;
|
|
1151
|
+
}
|
|
1152
|
+
/**
|
|
1153
|
+
* Resolve a document's own storage entity for this connector's own sign/mutate operations,
|
|
1154
|
+
* cached for didResolutionCacheTtlMs (0 disables caching and resolves fresh every call).
|
|
1155
|
+
* Only ever used for the connector's own create/update/revoke paths - never for verifyProof
|
|
1156
|
+
* or credential/presentation verification, which must stay uncached.
|
|
1157
|
+
* A clone is always returned: mutators edit the entity in place before storing it, and the
|
|
1158
|
+
* cached instance must never be handed out by reference.
|
|
1159
|
+
* @param documentId The DID to resolve.
|
|
1160
|
+
* @returns The verified storage entity.
|
|
1161
|
+
* @throws NotFoundError if the DID could not be resolved.
|
|
1162
|
+
* @internal
|
|
1163
|
+
*/
|
|
1164
|
+
async resolveOwnDocumentCached(documentId) {
|
|
1165
|
+
if (Is.undefined(this._didResolutionCache)) {
|
|
1166
|
+
return this.loadOwnDocument(documentId);
|
|
1167
|
+
}
|
|
1168
|
+
const identityDocument = await this._didResolutionCache.getOrSet(this.ownDidCacheKey(documentId), async () => this.loadOwnDocument(documentId));
|
|
1169
|
+
return ObjectHelper.clone(identityDocument);
|
|
1170
|
+
}
|
|
1171
|
+
/**
|
|
1172
|
+
* Check whether a credential's status entry or entries report it as revoked.
|
|
1173
|
+
* @param document The issuer document owning the revocation bitmap service.
|
|
1174
|
+
* @param credentialStatus The credential's status entry or entries to check.
|
|
1175
|
+
* @returns True if any entry is reported revoked.
|
|
1176
|
+
* @internal
|
|
1177
|
+
*/
|
|
1178
|
+
async checkCredentialStatusRevoked(document, credentialStatus) {
|
|
1179
|
+
let statuses = [];
|
|
1180
|
+
if (Is.array(credentialStatus)) {
|
|
1181
|
+
statuses = credentialStatus;
|
|
1182
|
+
}
|
|
1183
|
+
else if (Is.object(credentialStatus)) {
|
|
1184
|
+
statuses = [credentialStatus];
|
|
1185
|
+
}
|
|
1186
|
+
for (const status of statuses) {
|
|
1187
|
+
if (await this.checkRevocation(document, status.revocationBitmapIndex)) {
|
|
1188
|
+
return true;
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
return false;
|
|
1192
|
+
}
|
|
1124
1193
|
/**
|
|
1125
1194
|
* Check if a revocation index is revoked.
|
|
1126
1195
|
* @param document The document to check.
|
|
@@ -1129,20 +1198,18 @@ export class EntityStorageIdentityConnector {
|
|
|
1129
1198
|
* @internal
|
|
1130
1199
|
*/
|
|
1131
1200
|
async checkRevocation(document, revocationBitmapIndex) {
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
return bitString.getBit(revocationIndex);
|
|
1145
|
-
}
|
|
1201
|
+
const revocationIndex = Coerce.number(revocationBitmapIndex);
|
|
1202
|
+
if (Is.number(revocationIndex)) {
|
|
1203
|
+
const revocationService = document.service?.find(s => s.id.endsWith("#revocation"));
|
|
1204
|
+
if (revocationService &&
|
|
1205
|
+
Is.string(revocationService.serviceEndpoint) &&
|
|
1206
|
+
revocationService.type === "BitstringStatusList") {
|
|
1207
|
+
const revocationParts = revocationService.serviceEndpoint.split(",");
|
|
1208
|
+
if (revocationParts.length === 2) {
|
|
1209
|
+
const compressedRevocationBytes = Converter.base64UrlToBytes(revocationParts[1]);
|
|
1210
|
+
const decompressed = await Compression.decompress(compressedRevocationBytes, CompressionType.Gzip);
|
|
1211
|
+
const bitString = BitString.fromBits(decompressed, EntityStorageIdentityConnector._REVOCATION_BITS_SIZE);
|
|
1212
|
+
return bitString.getBit(revocationIndex);
|
|
1146
1213
|
}
|
|
1147
1214
|
}
|
|
1148
1215
|
}
|
|
@@ -1159,12 +1226,14 @@ export class EntityStorageIdentityConnector {
|
|
|
1159
1226
|
const stringifiedDocument = JsonHelper.canonicalize(didDocument);
|
|
1160
1227
|
const docBytes = Converter.utf8ToBytes(stringifiedDocument);
|
|
1161
1228
|
const signature = await this._vaultConnector.sign(EntityStorageIdentityConnector.buildVaultKey(didDocument.id, "did"), docBytes);
|
|
1162
|
-
|
|
1229
|
+
const identityDocument = {
|
|
1163
1230
|
id: didDocument.id,
|
|
1164
1231
|
document: didDocument,
|
|
1165
1232
|
signature: Converter.bytesToBase64(signature),
|
|
1166
1233
|
controller
|
|
1167
|
-
}
|
|
1234
|
+
};
|
|
1235
|
+
await this._didDocumentEntityStorage.set(identityDocument);
|
|
1236
|
+
this._didResolutionCache?.set(this.ownDidCacheKey(didDocument.id), ObjectHelper.clone(identityDocument));
|
|
1168
1237
|
}
|
|
1169
1238
|
}
|
|
1170
1239
|
//# sourceMappingURL=entityStorageIdentityConnector.js.map
|