@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.
Files changed (32) hide show
  1. package/dist/es/entityStorageIdentityConnector.js +207 -139
  2. package/dist/es/entityStorageIdentityConnector.js.map +1 -1
  3. package/dist/es/entityStorageIdentityProfileConnector.js +1 -1
  4. package/dist/es/entityStorageIdentityProfileConnector.js.map +1 -1
  5. package/dist/es/entityStorageIdentityResolverConnector.js +59 -2
  6. package/dist/es/entityStorageIdentityResolverConnector.js.map +1 -1
  7. package/dist/es/index.js +2 -0
  8. package/dist/es/index.js.map +1 -1
  9. package/dist/es/models/IEntityStorageIdentityConnectorConfig.js +4 -0
  10. package/dist/es/models/IEntityStorageIdentityConnectorConfig.js.map +1 -0
  11. package/dist/es/models/IEntityStorageIdentityConnectorConstructorOptions.js.map +1 -1
  12. package/dist/es/models/IEntityStorageIdentityResolverConnectorConfig.js +4 -0
  13. package/dist/es/models/IEntityStorageIdentityResolverConnectorConfig.js.map +1 -0
  14. package/dist/es/models/IEntityStorageIdentityResolverConnectorConstructorOptions.js +0 -2
  15. package/dist/es/models/IEntityStorageIdentityResolverConnectorConstructorOptions.js.map +1 -1
  16. package/dist/types/entityStorageIdentityConnector.d.ts +11 -1
  17. package/dist/types/entityStorageIdentityResolverConnector.d.ts +9 -1
  18. package/dist/types/index.d.ts +2 -0
  19. package/dist/types/models/IEntityStorageIdentityConnectorConfig.d.ts +26 -0
  20. package/dist/types/models/IEntityStorageIdentityConnectorConstructorOptions.d.ts +5 -0
  21. package/dist/types/models/IEntityStorageIdentityResolverConnectorConfig.d.ts +23 -0
  22. package/dist/types/models/IEntityStorageIdentityResolverConnectorConstructorOptions.d.ts +5 -0
  23. package/docs/changelog.md +69 -0
  24. package/docs/reference/classes/EntityStorageIdentityConnector.md +36 -1
  25. package/docs/reference/classes/EntityStorageIdentityResolverConnector.md +29 -1
  26. package/docs/reference/index.md +2 -0
  27. package/docs/reference/interfaces/IEntityStorageIdentityConnectorConfig.md +46 -0
  28. package/docs/reference/interfaces/IEntityStorageIdentityConnectorConstructorOptions.md +12 -0
  29. package/docs/reference/interfaces/IEntityStorageIdentityResolverConnectorConfig.md +43 -0
  30. package/docs/reference/interfaces/IEntityStorageIdentityResolverConnectorConstructorOptions.md +8 -0
  31. package/locales/en.json +1 -0
  32. 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._didDocumentEntityStorage.get(documentId);
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._didDocumentEntityStorage.get(idParts.id);
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._didDocumentEntityStorage.get(documentId);
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._didDocumentEntityStorage.get(idParts.id);
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._didDocumentEntityStorage.get(documentId);
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._didDocumentEntityStorage.get(documentId);
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._didDocumentEntityStorage.get(idParts.id);
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 (!Is.empty(proofContext)) {
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 credentialVerified = await this.verifyProof(JsonLdHelper.toNodeObject(doc), ArrayHelper.fromObjectOrArray(proof)[0]);
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: false,
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
- const methods = this.getAllMethods(issuerDidDocument);
631
- const methodAndArray = methods.find(m => {
632
- if (Is.string(m.method)) {
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
- const credentialStatus = verifiableCredential.credentialStatus;
669
- let revoked = false;
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._didDocumentEntityStorage.get(issuerDocumentId);
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._didDocumentEntityStorage.get(issuerDocumentId);
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._didDocumentEntityStorage.get(idParts.id);
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
- return { revoked: false, verifiablePresentation: doc };
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 = true;
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
- else {
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 didIdentityDocument = await this._didDocumentEntityStorage.get(idParts.id);
999
- if (Is.undefined(didIdentityDocument)) {
1000
- throw new NotFoundError(EntityStorageIdentityConnector.CLASS_NAME, "documentNotFound", idParts.id);
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
- if (Is.stringValue(revocationBitmapIndex)) {
1133
- const revocationIndex = Coerce.number(revocationBitmapIndex);
1134
- if (Is.number(revocationIndex)) {
1135
- const revocationService = document.service?.find(s => s.id.endsWith("#revocation"));
1136
- if (revocationService &&
1137
- Is.string(revocationService.serviceEndpoint) &&
1138
- revocationService.type === "BitstringStatusList") {
1139
- const revocationParts = revocationService.serviceEndpoint.split(",");
1140
- if (revocationParts.length === 2) {
1141
- const compressedRevocationBytes = Converter.base64UrlToBytes(revocationParts[1]);
1142
- const decompressed = await Compression.decompress(compressedRevocationBytes, CompressionType.Gzip);
1143
- const bitString = BitString.fromBits(decompressed, EntityStorageIdentityConnector._REVOCATION_BITS_SIZE);
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
- await this._didDocumentEntityStorage.set({
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