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