From 56200cfae6f62b823a74e67eb060face2ee3ecbc Mon Sep 17 00:00:00 2001 From: Fabio Pinheiro Date: Wed, 18 Sep 2024 07:55:11 +0100 Subject: [PATCH] feat: Add KID to the credential-offers API - ATL-7704 (#1320) Signed-off-by: FabioPinheiro Co-authored-by: Yurii Shynbuiev --- .../castor/core/model/ProtoModelHelper.scala | 10 ++- .../castor/core/model/did/PublicKey.scala | 5 +- .../castor/core/model/error/package.scala | 4 +- .../core/util/DIDOperationValidator.scala | 19 ++--- .../model/did/w3c/W3CModelHelperSpec.scala | 23 +++--- .../castor/core/service/MockDIDService.scala | 5 +- .../core/util/DIDOperationValidatorSpec.scala | 49 +++++------ .../identus/castor/core/util/GenUtils.scala | 5 +- .../server/jobs/BackgroundJobsHelper.scala | 46 ++++++----- .../server/jobs/PresentBackgroundJobs.scala | 81 +++++++++---------- .../agent/server/jobs/StatusListJobs.scala | 7 +- .../controller/DIDCommControllerError.scala | 6 +- .../controller/IssueControllerImpl.scala | 2 + .../CreateIssueCredentialRecordRequest.scala | 15 ++++ .../controller/IssueControllerImplSpec.scala | 2 + .../agent/walletapi/model/KeyManagement.scala | 8 +- .../model/error/DIDSecretStorageError.scala | 2 +- .../walletapi/service/ManagedDIDService.scala | 3 +- .../service/ManagedDIDServiceImpl.scala | 9 ++- .../service/handler/DIDCreateHandler.scala | 7 +- .../service/handler/DIDUpdateHandler.scala | 9 ++- .../service/handler/PublicationHandler.scala | 3 +- .../sql/JdbcDIDNonSecretStorage.scala | 9 ++- .../walletapi/sql/JdbcDIDSecretStorage.scala | 17 ++-- .../storage/DIDNonSecretStorage.scala | 5 +- .../walletapi/storage/DIDSecretStorage.scala | 9 ++- .../agent/walletapi/util/KeyResolver.scala | 5 +- .../walletapi/util/OperationFactory.scala | 19 ++--- .../vault/VaultDIDSecretStorage.scala | 17 ++-- .../service/ManagedDIDServiceSpec.scala | 35 ++++---- .../service/MockManagedDIDService.scala | 5 +- .../storage/DIDSecretStorageSpec.scala | 52 ++++++------ .../storage/MockDIDNonSecretStorage.scala | 9 ++- .../walletapi/storage/StorageSpecHelper.scala | 3 +- .../walletapi/util/OperationFactorySpec.scala | 21 ++--- .../model/error/CredentialServiceError.scala | 3 +- .../core/service/CredentialService.scala | 2 + .../core/service/CredentialServiceImpl.scala | 14 +++- .../service/CredentialServiceNotifier.scala | 4 + .../service/CredentialServiceSpecHelper.scala | 1 + .../core/service/MockCredentialService.scala | 2 + .../identus/shared/models/KeyId.scala | 4 + .../identus/shared/crypto/Apollo.scala | 1 + 43 files changed, 322 insertions(+), 235 deletions(-) diff --git a/castor/src/main/scala/org/hyperledger/identus/castor/core/model/ProtoModelHelper.scala b/castor/src/main/scala/org/hyperledger/identus/castor/core/model/ProtoModelHelper.scala index f2ef3f156d..cd12aeeb4b 100644 --- a/castor/src/main/scala/org/hyperledger/identus/castor/core/model/ProtoModelHelper.scala +++ b/castor/src/main/scala/org/hyperledger/identus/castor/core/model/ProtoModelHelper.scala @@ -24,8 +24,10 @@ import org.hyperledger.identus.castor.core.model.did.{ UpdateDIDAction, VerificationRelationship } +import org.hyperledger.identus.castor.core.model.did.ServiceEndpoint.value import org.hyperledger.identus.castor.core.model.did.ServiceEndpoint.UriOrJsonEndpoint import org.hyperledger.identus.shared.models.Base64UrlString +import org.hyperledger.identus.shared.models.KeyId import org.hyperledger.identus.shared.utils.Traverse.* import zio.* @@ -122,7 +124,7 @@ private[castor] trait ProtoModelHelper { extension (publicKey: PublicKey) { def toProto: node_models.PublicKey = { node_models.PublicKey( - id = publicKey.id, + id = publicKey.id.value, usage = publicKey.purpose match { case VerificationRelationship.Authentication => node_models.KeyUsage.AUTHENTICATION_KEY case VerificationRelationship.AssertionMethod => node_models.KeyUsage.ISSUING_KEY @@ -140,7 +142,7 @@ private[castor] trait ProtoModelHelper { extension (internalPublicKey: InternalPublicKey) { def toProto: node_models.PublicKey = { node_models.PublicKey( - id = internalPublicKey.id, + id = internalPublicKey.id.value, usage = internalPublicKey.purpose match { case InternalKeyPurpose.Master => node_models.KeyUsage.MASTER_KEY case InternalKeyPurpose.Revocation => node_models.KeyUsage.REVOCATION_KEY @@ -313,13 +315,13 @@ private[castor] trait ProtoModelHelper { } yield purpose match { case purpose: VerificationRelationship => PublicKey( - id = publicKey.id, + id = KeyId(publicKey.id), purpose = purpose, publicKeyData = keyData ) case purpose: InternalKeyPurpose => InternalPublicKey( - id = publicKey.id, + id = KeyId(publicKey.id), purpose = purpose, publicKeyData = keyData ) diff --git a/castor/src/main/scala/org/hyperledger/identus/castor/core/model/did/PublicKey.scala b/castor/src/main/scala/org/hyperledger/identus/castor/core/model/did/PublicKey.scala index 496b71abb3..da27a541c2 100644 --- a/castor/src/main/scala/org/hyperledger/identus/castor/core/model/did/PublicKey.scala +++ b/castor/src/main/scala/org/hyperledger/identus/castor/core/model/did/PublicKey.scala @@ -1,9 +1,10 @@ package org.hyperledger.identus.castor.core.model.did import org.hyperledger.identus.shared.models.Base64UrlString +import org.hyperledger.identus.shared.models.KeyId final case class PublicKey( - id: String, + id: KeyId, purpose: VerificationRelationship, publicKeyData: PublicKeyData ) @@ -14,7 +15,7 @@ enum InternalKeyPurpose { } final case class InternalPublicKey( - id: String, + id: KeyId, purpose: InternalKeyPurpose, publicKeyData: PublicKeyData ) diff --git a/castor/src/main/scala/org/hyperledger/identus/castor/core/model/error/package.scala b/castor/src/main/scala/org/hyperledger/identus/castor/core/model/error/package.scala index 001db37e68..ddad1ad8f2 100644 --- a/castor/src/main/scala/org/hyperledger/identus/castor/core/model/error/package.scala +++ b/castor/src/main/scala/org/hyperledger/identus/castor/core/model/error/package.scala @@ -1,5 +1,7 @@ package org.hyperledger.identus.castor.core.model +import org.hyperledger.identus.shared.models.KeyId + package object error { sealed trait DIDOperationError @@ -27,7 +29,7 @@ package object error { final case class TooManyDidPublicKeyAccess(limit: Int, access: Option[Int]) extends OperationValidationError final case class TooManyDidServiceAccess(limit: Int, access: Option[Int]) extends OperationValidationError final case class InvalidArgument(msg: String) extends OperationValidationError - final case class InvalidMasterKeyData(ids: Seq[String]) extends OperationValidationError + final case class InvalidMasterKeyData(ids: Seq[KeyId]) extends OperationValidationError } } diff --git a/castor/src/main/scala/org/hyperledger/identus/castor/core/util/DIDOperationValidator.scala b/castor/src/main/scala/org/hyperledger/identus/castor/core/util/DIDOperationValidator.scala index 8de4e44269..01dca95443 100644 --- a/castor/src/main/scala/org/hyperledger/identus/castor/core/util/DIDOperationValidator.scala +++ b/castor/src/main/scala/org/hyperledger/identus/castor/core/util/DIDOperationValidator.scala @@ -5,6 +5,7 @@ import org.hyperledger.identus.castor.core.model.error.OperationValidationError import org.hyperledger.identus.castor.core.util.DIDOperationValidator.Config import org.hyperledger.identus.castor.core.util.Prelude.* import org.hyperledger.identus.shared.crypto.Apollo +import org.hyperledger.identus.shared.models.KeyId import zio.* import scala.collection.immutable.ArraySeq @@ -70,14 +71,14 @@ private object CreateOperationValidator extends BaseOperationValidator { else Left(OperationValidationError.InvalidArgument("create operation must contain at least 1 master key")) } - private def extractKeyIds(operation: PrismDIDOperation.Create): Seq[String] = operation.publicKeys.map { + private def extractKeyIds(operation: PrismDIDOperation.Create): Seq[KeyId] = operation.publicKeys.map { case PublicKey(id, _, _) => id case InternalPublicKey(id, _, _) => id } private def extractKeyData( operation: PrismDIDOperation.Create - ): Seq[(String, VerificationRelationship | InternalKeyPurpose, PublicKeyData)] = + ): Seq[(KeyId, VerificationRelationship | InternalKeyPurpose, PublicKeyData)] = operation.publicKeys.map { case PublicKey(id, purpose, data) => (id, purpose, data) case InternalPublicKey(id, purpose, data) => (id, purpose, data) @@ -139,15 +140,15 @@ private object UpdateOperationValidator extends BaseOperationValidator { ) } - private def extractKeyIds(operation: PrismDIDOperation.Update): Seq[String] = operation.actions.collect { + private def extractKeyIds(operation: PrismDIDOperation.Update): Seq[KeyId] = operation.actions.collect { case UpdateDIDAction.AddKey(pk) => pk.id case UpdateDIDAction.AddInternalKey(pk) => pk.id - case UpdateDIDAction.RemoveKey(id) => id + case UpdateDIDAction.RemoveKey(id) => KeyId(id) } private def extractKeyData( operation: PrismDIDOperation.Update - ): Seq[(String, VerificationRelationship | InternalKeyPurpose, PublicKeyData)] = + ): Seq[(KeyId, VerificationRelationship | InternalKeyPurpose, PublicKeyData)] = operation.actions.collect { case UpdateDIDAction.AddKey(pk) => (pk.id, pk.purpose, pk.publicKeyData) case UpdateDIDAction.AddInternalKey(pk) => (pk.id, pk.purpose, pk.publicKeyData) @@ -182,12 +183,12 @@ private object DeactivateOperationValidator extends BaseOperationValidator { private trait BaseOperationValidator { - type KeyIdExtractor[T] = T => Seq[String] + type KeyIdExtractor[T] = T => Seq[KeyId] type ServiceIdExtractor[T] = T => Seq[String] type ServiceTypeExtractor[T] = T => Seq[(String, ServiceType)] type ServiceEndpointExtractor[T] = T => Seq[(String, ServiceEndpoint)] type ContextExtractor[T] = T => Seq[Seq[String]] - type KeyDataExtractor[T] = T => Seq[(String, VerificationRelationship | InternalKeyPurpose, PublicKeyData)] + type KeyDataExtractor[T] = T => Seq[(KeyId, VerificationRelationship | InternalKeyPurpose, PublicKeyData)] protected def validateMaxPublicKeysAccess[T <: PrismDIDOperation]( config: Config @@ -266,7 +267,7 @@ private trait BaseOperationValidator { keyIdExtractor: KeyIdExtractor[T] ): Either[OperationValidationError, Unit] = { val ids = keyIdExtractor(operation) - val invalidIds = ids.filterNot(UriUtils.isValidUriFragment) + val invalidIds = ids.map(_.value).filterNot(UriUtils.isValidUriFragment) if (invalidIds.isEmpty) Right(()) else Left( @@ -289,7 +290,7 @@ private trait BaseOperationValidator { config: Config )(operation: T, keyIdExtractor: KeyIdExtractor[T]): Either[OperationValidationError, Unit] = { val ids = keyIdExtractor(operation) - val invalidIds = ids.filter(_.length > config.maxIdSize) + val invalidIds = ids.filter(_.value.length > config.maxIdSize) if (invalidIds.isEmpty) Right(()) else Left( diff --git a/castor/src/test/scala/org/hyperledger/identus/castor/core/model/did/w3c/W3CModelHelperSpec.scala b/castor/src/test/scala/org/hyperledger/identus/castor/core/model/did/w3c/W3CModelHelperSpec.scala index eaac4b58d2..f1dc30b34a 100644 --- a/castor/src/test/scala/org/hyperledger/identus/castor/core/model/did/w3c/W3CModelHelperSpec.scala +++ b/castor/src/test/scala/org/hyperledger/identus/castor/core/model/did/w3c/W3CModelHelperSpec.scala @@ -9,6 +9,7 @@ import org.hyperledger.identus.castor.core.model.did.{ VerificationRelationship } import org.hyperledger.identus.castor.core.util.GenUtils +import org.hyperledger.identus.shared.models.KeyId import zio.* import zio.test.* import zio.test.Assertion.* @@ -17,13 +18,13 @@ object W3CModelHelperSpec extends ZIOSpecDefault { import W3CModelHelper.* - private def generateInternalPublicKey(id: String, purpose: InternalKeyPurpose = InternalKeyPurpose.Master) = + private def generateInternalPublicKey(id: KeyId, purpose: InternalKeyPurpose = InternalKeyPurpose.Master) = GenUtils.internalPublicKey .map(_.copy(id = id, purpose = purpose)) .runCollectN(1) .map(_.head) - private def generatePublicKey(id: String, purpose: VerificationRelationship) = + private def generatePublicKey(id: KeyId, purpose: VerificationRelationship) = GenUtils.publicKey.map(_.copy(id = id, purpose = purpose)).runCollectN(1).map(_.head) private def generateService(id: String) = @@ -31,8 +32,8 @@ object W3CModelHelperSpec extends ZIOSpecDefault { private def generateDIDData( did: CanonicalPrismDID, - masterKeyId: String = "master-0", - keyIds: Seq[(String, VerificationRelationship)] = Seq.empty, + masterKeyId: KeyId = KeyId("master-0"), + keyIds: Seq[(KeyId, VerificationRelationship)] = Seq.empty, serviceIds: Seq[String] = Seq.empty, context: Seq[String] = Seq.empty ) = @@ -49,11 +50,11 @@ object W3CModelHelperSpec extends ZIOSpecDefault { didData <- generateDIDData( did = did, keyIds = Seq( - "auth-0" -> VerificationRelationship.Authentication, - "iss-0" -> VerificationRelationship.AssertionMethod, - "comm-0" -> VerificationRelationship.KeyAgreement, - "capinv-0" -> VerificationRelationship.CapabilityInvocation, - "capdel-0" -> VerificationRelationship.CapabilityDelegation + KeyId("auth-0") -> VerificationRelationship.Authentication, + KeyId("iss-0") -> VerificationRelationship.AssertionMethod, + KeyId("comm-0") -> VerificationRelationship.KeyAgreement, + KeyId("capinv-0") -> VerificationRelationship.CapabilityInvocation, + KeyId("capdel-0") -> VerificationRelationship.CapabilityDelegation ), serviceIds = Seq("service-0") ) @@ -92,7 +93,7 @@ object W3CModelHelperSpec extends ZIOSpecDefault { longFormDID = PrismDID.buildLongFormFromOperation(PrismDIDOperation.Create(Nil, Nil, Nil)) didData <- generateDIDData( did = did, - keyIds = Seq("auth-0" -> VerificationRelationship.Authentication) + keyIds = Seq(KeyId("auth-0") -> VerificationRelationship.Authentication) ) didDoc = didData.toW3C(longFormDID) } yield assert(didDoc.id)(equalTo(longFormDID.toString)) && @@ -104,7 +105,7 @@ object W3CModelHelperSpec extends ZIOSpecDefault { did <- ZIO.fromEither(PrismDID.buildCanonicalFromSuffix("0" * 64)) didData <- generateDIDData( did = did, - keyIds = Seq("auth-0" -> VerificationRelationship.Authentication), + keyIds = Seq(KeyId("auth-0") -> VerificationRelationship.Authentication), serviceIds = Seq("service-0"), context = Seq("user-defined-context") ) diff --git a/castor/src/test/scala/org/hyperledger/identus/castor/core/service/MockDIDService.scala b/castor/src/test/scala/org/hyperledger/identus/castor/core/service/MockDIDService.scala index f3d4b8fd40..ae128f47ed 100644 --- a/castor/src/test/scala/org/hyperledger/identus/castor/core/service/MockDIDService.scala +++ b/castor/src/test/scala/org/hyperledger/identus/castor/core/service/MockDIDService.scala @@ -4,6 +4,7 @@ import org.hyperledger.identus.castor.core.model.did.* import org.hyperledger.identus.castor.core.model.error import org.hyperledger.identus.shared.crypto.{Apollo, Secp256k1KeyPair} import org.hyperledger.identus.shared.models.Base64UrlString +import org.hyperledger.identus.shared.models.KeyId import zio.{mock, IO, URLayer, ZIO, ZLayer} import zio.mock.{Expectation, Mock, Proxy} import zio.test.Assertion @@ -47,7 +48,7 @@ object MockDIDService extends Mock[DIDService] { val createOperation = PrismDIDOperation.Create( publicKeys = Seq( InternalPublicKey( - id = "master-0", + id = KeyId("master-0"), purpose = InternalKeyPurpose.Master, publicKeyData = PublicKeyData.ECCompressedKeyData( crv = EllipticCurve.SECP256K1, @@ -55,7 +56,7 @@ object MockDIDService extends Mock[DIDService] { ) ), PublicKey( - id = "key-0", + id = KeyId("key-0"), purpose = verificationRelationship, publicKeyData = PublicKeyData.ECCompressedKeyData( crv = EllipticCurve.SECP256K1, diff --git a/castor/src/test/scala/org/hyperledger/identus/castor/core/util/DIDOperationValidatorSpec.scala b/castor/src/test/scala/org/hyperledger/identus/castor/core/util/DIDOperationValidatorSpec.scala index 6e2eee7396..72957513b0 100644 --- a/castor/src/test/scala/org/hyperledger/identus/castor/core/util/DIDOperationValidatorSpec.scala +++ b/castor/src/test/scala/org/hyperledger/identus/castor/core/util/DIDOperationValidatorSpec.scala @@ -4,6 +4,7 @@ import org.hyperledger.identus.castor.core.model.did.* import org.hyperledger.identus.castor.core.model.error.OperationValidationError import org.hyperledger.identus.castor.core.util.DIDOperationValidator.Config import org.hyperledger.identus.shared.models.Base64UrlString +import org.hyperledger.identus.shared.models.KeyId import zio.* import zio.test.* import zio.test.Assertion.* @@ -95,7 +96,7 @@ object DIDOperationValidatorSpec extends ZIOSpecDefault { publicKeys: Seq[PublicKey] = Nil, internalKeys: Seq[InternalPublicKey] = Seq( InternalPublicKey( - id = "master0", + id = KeyId("master0"), purpose = InternalKeyPurpose.Master, publicKeyData = publicKeyData ) @@ -111,8 +112,8 @@ object DIDOperationValidatorSpec extends ZIOSpecDefault { test("validates a Create operation successfully when using the provided ZLayer") { val operation = PrismDIDOperation.Create( publicKeys = Seq( - PublicKey("key1", VerificationRelationship.Authentication, publicKeyData), - InternalPublicKey("master0", InternalKeyPurpose.Master, publicKeyData) + PublicKey(KeyId("key1"), VerificationRelationship.Authentication, publicKeyData), + InternalPublicKey(KeyId("master0"), InternalKeyPurpose.Master, publicKeyData) ), services = Seq( Service("service1", ServiceType.Single("LinkedDomains"), "http://example.com/") @@ -130,14 +131,14 @@ object DIDOperationValidatorSpec extends ZIOSpecDefault { test("reject CreateOperation on too many DID publicKey access") { val publicKeys = (1 to 10).map(i => PublicKey( - id = s"key$i", + id = KeyId(s"key$i"), purpose = VerificationRelationship.Authentication, publicKeyData = publicKeyData ) ) val internalKeys = (1 to 10).map(i => InternalPublicKey( - id = s"master$i", + id = KeyId(s"master$i"), purpose = InternalKeyPurpose.Master, publicKeyData = publicKeyData ) @@ -150,14 +151,14 @@ object DIDOperationValidatorSpec extends ZIOSpecDefault { test("reject CreateOperation on duplicated DID public key id") { val publicKeys = (1 to 10).map(i => PublicKey( - id = s"key$i", + id = KeyId(s"key$i"), purpose = VerificationRelationship.Authentication, publicKeyData = publicKeyData ) ) val internalKeys = Seq( InternalPublicKey( - id = s"key1", + id = KeyId(s"key1"), purpose = InternalKeyPurpose.Master, publicKeyData = publicKeyData ) @@ -196,7 +197,7 @@ object DIDOperationValidatorSpec extends ZIOSpecDefault { test("reject CreateOperation on invalid key-id") { val publicKeys = (1 to 2).map(i => PublicKey( - id = s"key $i", + id = KeyId(s"key $i"), purpose = VerificationRelationship.Authentication, publicKeyData = publicKeyData ) @@ -221,7 +222,7 @@ object DIDOperationValidatorSpec extends ZIOSpecDefault { }, test("reject CreateOperation on too long key-id") { val publicKey = PublicKey( - id = s"key-${"0" * 100}", + id = KeyId(s"key-${"0" * 100}"), purpose = VerificationRelationship.Authentication, publicKeyData = publicKeyData ) @@ -313,12 +314,12 @@ object DIDOperationValidatorSpec extends ZIOSpecDefault { val op = createPrismDIDOperation( publicKeys = Seq( PublicKey( - id = "key-0", + id = KeyId("key-0"), purpose = VerificationRelationship.Authentication, publicKeyData = publicKeyData ), PublicKey( - id = "key-1", + id = KeyId("key-1"), purpose = VerificationRelationship.AssertionMethod, publicKeyData = publicKeyData ) @@ -344,7 +345,7 @@ object DIDOperationValidatorSpec extends ZIOSpecDefault { val op = createPrismDIDOperation( internalKeys = Seq( InternalPublicKey( - id = "master0", + id = KeyId("master0"), purpose = InternalKeyPurpose.Master, publicKeyData = PublicKeyData.ECKeyData( crv = EllipticCurve.ED25519, @@ -353,7 +354,7 @@ object DIDOperationValidatorSpec extends ZIOSpecDefault { ) ), InternalPublicKey( - id = "master1", + id = KeyId("master1"), purpose = InternalKeyPurpose.Master, publicKeyData = PublicKeyData.ECKeyData( crv = EllipticCurve.SECP256K1, @@ -364,7 +365,7 @@ object DIDOperationValidatorSpec extends ZIOSpecDefault { ) ) assert(DIDOperationValidator(Config.default).validate(op))( - isLeft(equalTo(OperationValidationError.InvalidMasterKeyData(Seq("master0", "master1")))) + isLeft(equalTo(OperationValidationError.InvalidMasterKeyData(Seq(KeyId("master0"), KeyId("master1"))))) ) } ).provideLayer(testLayer) @@ -400,8 +401,10 @@ object DIDOperationValidatorSpec extends ZIOSpecDefault { test("accept valid UpdateOperation") { val op = updatePrismDIDOperation( Seq( - UpdateDIDAction.AddKey(PublicKey("key0", VerificationRelationship.Authentication, publicKeyData)), - UpdateDIDAction.AddInternalKey(InternalPublicKey("master0", InternalKeyPurpose.Master, publicKeyData)), + UpdateDIDAction.AddKey(PublicKey(KeyId("key0"), VerificationRelationship.Authentication, publicKeyData)), + UpdateDIDAction.AddInternalKey( + InternalPublicKey(KeyId("master0"), InternalKeyPurpose.Master, publicKeyData) + ), UpdateDIDAction.RemoveKey("key0"), UpdateDIDAction.AddService( Service("service0", ServiceType.Single("LinkedDomains"), "http://example.com/") @@ -422,7 +425,7 @@ object DIDOperationValidatorSpec extends ZIOSpecDefault { val addKeyActions = (1 to 10).map(i => UpdateDIDAction.AddKey( PublicKey( - id = s"key$i", + id = KeyId(s"key$i"), purpose = VerificationRelationship.Authentication, publicKeyData = publicKeyData ) @@ -431,7 +434,7 @@ object DIDOperationValidatorSpec extends ZIOSpecDefault { val addInternalKeyActions = (1 to 10).map(i => UpdateDIDAction.AddInternalKey( InternalPublicKey( - id = s"master$i", + id = KeyId(s"master$i"), purpose = InternalKeyPurpose.Master, publicKeyData = publicKeyData ) @@ -469,7 +472,7 @@ object DIDOperationValidatorSpec extends ZIOSpecDefault { test("reject UpdateOperation on invalid key-id") { val action1 = UpdateDIDAction.AddKey( PublicKey( - id = "key 1", + id = KeyId("key 1"), purpose = VerificationRelationship.Authentication, publicKeyData = publicKeyData ) @@ -497,7 +500,7 @@ object DIDOperationValidatorSpec extends ZIOSpecDefault { test("reject UpdateOperation on too long key-id") { val action1 = UpdateDIDAction.AddKey( PublicKey( - id = s"key-${"0" * 100}", + id = KeyId(s"key-${"0" * 100}"), purpose = VerificationRelationship.Authentication, publicKeyData = publicKeyData ) @@ -591,7 +594,7 @@ object DIDOperationValidatorSpec extends ZIOSpecDefault { test("reject UpdateOperation when master key is not a secp256k1 key") { val action1 = UpdateDIDAction.AddInternalKey( InternalPublicKey( - id = "master0", + id = KeyId("master0"), purpose = InternalKeyPurpose.Master, publicKeyData = PublicKeyData.ECKeyData( crv = EllipticCurve.ED25519, @@ -602,7 +605,7 @@ object DIDOperationValidatorSpec extends ZIOSpecDefault { ) val action2 = UpdateDIDAction.AddInternalKey( InternalPublicKey( - id = "master1", + id = KeyId("master1"), purpose = InternalKeyPurpose.Master, publicKeyData = PublicKeyData.ECKeyData( crv = EllipticCurve.SECP256K1, @@ -613,7 +616,7 @@ object DIDOperationValidatorSpec extends ZIOSpecDefault { ) val op = updatePrismDIDOperation(Seq(action1, action2)) assert(DIDOperationValidator(Config.default).validate(op))( - isLeft(equalTo(OperationValidationError.InvalidMasterKeyData(Seq("master0", "master1")))) + isLeft(equalTo(OperationValidationError.InvalidMasterKeyData(Seq(KeyId("master0"), KeyId("master1"))))) ) } ) diff --git a/castor/src/test/scala/org/hyperledger/identus/castor/core/util/GenUtils.scala b/castor/src/test/scala/org/hyperledger/identus/castor/core/util/GenUtils.scala index 2b33c0f3b6..29622b3b70 100644 --- a/castor/src/test/scala/org/hyperledger/identus/castor/core/util/GenUtils.scala +++ b/castor/src/test/scala/org/hyperledger/identus/castor/core/util/GenUtils.scala @@ -5,6 +5,7 @@ import org.hyperledger.identus.castor.core.model.did.* import org.hyperledger.identus.castor.core.model.did.ServiceEndpoint.{UriOrJsonEndpoint, UriValue} import org.hyperledger.identus.shared.crypto.Apollo import org.hyperledger.identus.shared.models.Base64UrlString +import org.hyperledger.identus.shared.models.KeyId import zio.* import zio.test.Gen @@ -41,14 +42,14 @@ object GenUtils { id <- uriFragment purpose <- Gen.fromIterable(VerificationRelationship.values) keyData <- publicKeyData - } yield PublicKey(id, purpose, keyData) + } yield PublicKey(KeyId(id), purpose, keyData) val internalPublicKey: Gen[Any, InternalPublicKey] = for { id <- uriFragment purpose <- Gen.fromIterable(InternalKeyPurpose.values) keyData <- publicKeyData - } yield InternalPublicKey(id, purpose, keyData) + } yield InternalPublicKey(KeyId(id), purpose, keyData) val service: Gen[Any, Service] = for { diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/jobs/BackgroundJobsHelper.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/jobs/BackgroundJobsHelper.scala index 2f046f99f0..1708ca1517 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/jobs/BackgroundJobsHelper.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/jobs/BackgroundJobsHelper.scala @@ -59,9 +59,10 @@ trait BackgroundJobsHelper { } yield longFormPrismDID } - def createJwtIssuer( + def createJwtVcIssuer( jwtIssuerDID: PrismDID, - verificationRelationship: VerificationRelationship + verificationRelationship: VerificationRelationship, + kidIssuer: Option[KeyId], ): ZIO[ DIDService & ManagedDIDService & WalletAccessContext, BackgroundJobError | GetManagedDIDError | DIDResolutionError, @@ -71,19 +72,23 @@ trait BackgroundJobsHelper { managedDIDService <- ZIO.service[ManagedDIDService] didService <- ZIO.service[DIDService] // Automatically infer keyId to use by resolving DID and choose the corresponding VerificationRelationship - issuingKeyId <- didService - .resolveDID(jwtIssuerDID) - .someOrFail(BackgroundJobError.InvalidState(s"Issuing DID resolution result is not found")) - .map { case (_, didData) => - didData.publicKeys - .find(pk => pk.purpose == verificationRelationship && pk.publicKeyData.crv == EllipticCurve.SECP256K1) - .map(_.id) - } - .someOrFail( - BackgroundJobError.InvalidState( - s"Issuing DID doesn't have a key in ${verificationRelationship.name} to use: $jwtIssuerDID" + + // FIXME kidIssuer + issuingKeyId <- + // kidIssuer.orElse + didService + .resolveDID(jwtIssuerDID) + .someOrFail(BackgroundJobError.InvalidState(s"Issuing DID resolution result is not found")) + .map { case (_, didData) => + didData.publicKeys + .find(pk => pk.purpose == verificationRelationship && pk.publicKeyData.crv == EllipticCurve.SECP256K1) + .map(_.id) + } + .someOrFail( + BackgroundJobError.InvalidState( + s"Issuing DID doesn't have a key in ${verificationRelationship.name} to use: $jwtIssuerDID" + ) ) - ) jwtIssuer <- managedDIDService .findDIDKeyPair(jwtIssuerDID.asCanonical, issuingKeyId) .flatMap { @@ -93,9 +98,12 @@ trait BackgroundJobsHelper { .InvalidState(s"Issuer key-pair does not exist in the wallet: ${jwtIssuerDID.toString}#$issuingKeyId") ) case Some(Ed25519KeyPair(publicKey, privateKey)) => - ZIO.fail( - BackgroundJobError.InvalidState( - s"Issuer key-pair '$issuingKeyId' is of the type Ed25519. It's not supported by this feature in this version" + ZIO.succeed( + JwtIssuer( + jwtIssuerDID.did, + // org.hyperledger.identus.castor.core.model.did.DID.fromStringUnsafe(jwtIssuerDID.toString), + EdSigner(Ed25519KeyPair(publicKey, privateKey), Some(issuingKeyId)), + publicKey.toJava ) ) case Some(X25519KeyPair(publicKey, privateKey)) => @@ -108,7 +116,7 @@ trait BackgroundJobsHelper { ZIO.succeed( JwtIssuer( jwtIssuerDID.did, - ES256KSigner(privateKey.toJavaPrivateKey), + ES256KSigner(privateKey.toJavaPrivateKey, Some(issuingKeyId)), publicKey.toJavaPublicKey ) ) @@ -161,7 +169,7 @@ trait BackgroundJobsHelper { .map { case (_, didData) => didData.publicKeys .find(pk => - pk.id == keyId.value + pk.id == keyId && pk.purpose == verificationRelationship && pk.publicKeyData.crv == EllipticCurve.ED25519 ) .map(_.id) diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/jobs/PresentBackgroundJobs.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/jobs/PresentBackgroundJobs.scala index 85106e084f..297602f425 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/jobs/PresentBackgroundJobs.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/jobs/PresentBackgroundJobs.scala @@ -639,14 +639,15 @@ object PresentBackgroundJobs extends BackgroundJobsHelper { ] = { val result = credentialFormat match { - case CredentialFormat.JWT => handleJWT(id, credentialsToUse, requestPresentation) - case CredentialFormat.SDJWT => handleSDJWT(id, credentialsToUse, requestPresentation) + case CredentialFormat.JWT => handle_JWT_VC(id, credentialsToUse, requestPresentation) + case CredentialFormat.SDJWT => handle_SD_JWT_VC(id, credentialsToUse, requestPresentation) case CredentialFormat.AnonCreds => handleAnoncred(id, maybeCredentialsToUseJson, requestPresentation) } result @@ metric } - private def handleJWT( + /** prover presentation pending to generated flow */ + private def handle_JWT_VC( id: DidCommID, credentialsToUse: Option[List[String]], requestPresentation: RequestPresentation @@ -654,40 +655,36 @@ object PresentBackgroundJobs extends BackgroundJobsHelper { CredentialService & DIDService & COMMON_RESOURCES, ERROR, Unit - ] = { - val proverPresentationPendingToGeneratedFlow = for { - walletAccessContext <- ZIO - .fromOption(requestPresentation.to) - .flatMap(buildWalletAccessContextLayer) - .mapError(_ => PresentationError.RequestPresentationMissingField(id.value, "recipient")) - _ <- for { - presentationService <- ZIO.service[PresentationService] - prover <- createPrismDIDIssuerFromPresentationCredentials(id, credentialsToUse.getOrElse(Nil)) - .provideSomeLayer(ZLayer.succeed(walletAccessContext)) - presentation <- - for { - presentationPayload <- - presentationService - .createJwtPresentationPayloadFromRecord( - id, - prover, - Instant.now() - ) - .provideSomeLayer(ZLayer.succeed(walletAccessContext)) - signedJwtPresentation = JwtPresentation.toEncodedJwt( - presentationPayload.toW3CPresentationPayload, - prover - ) - presentation <- createPresentation(id, requestPresentation, signedJwtPresentation) - } yield presentation - _ <- presentationService - .markPresentationGenerated(id, presentation) - .provideSomeLayer(ZLayer.succeed(walletAccessContext)) - } yield () + ] = for { + walletAccessContext <- ZIO + .fromOption(requestPresentation.to) + .flatMap(buildWalletAccessContextLayer) + .mapError(_ => PresentationError.RequestPresentationMissingField(id.value, "recipient")) + _ <- for { + presentationService <- ZIO.service[PresentationService] + prover <- createPrismDIDIssuerFromPresentationCredentials(id, credentialsToUse.getOrElse(Nil)) + .provideSomeLayer(ZLayer.succeed(walletAccessContext)) + presentation <- + for { + presentationPayload <- + presentationService + .createJwtPresentationPayloadFromRecord( + id, + prover, + Instant.now() + ) + .provideSomeLayer(ZLayer.succeed(walletAccessContext)) + signedJwtPresentation = JwtPresentation.toEncodedJwt( + presentationPayload.toW3CPresentationPayload, + prover + ) + presentation <- createPresentation(id, requestPresentation, signedJwtPresentation) + } yield presentation + _ <- presentationService + .markPresentationGenerated(id, presentation) + .provideSomeLayer(ZLayer.succeed(walletAccessContext)) } yield () - - proverPresentationPendingToGeneratedFlow - } + } yield () private def createPresentation( id: DidCommID, @@ -719,7 +716,7 @@ object PresentBackgroundJobs extends BackgroundJobsHelper { ) } - private def handleSDJWT( + private def handle_SD_JWT_VC( id: DidCommID, credentialsToUse: Option[List[String]], requestPresentation: RequestPresentation @@ -810,16 +807,18 @@ object PresentBackgroundJobs extends BackgroundJobsHelper { credentialRecordUuid <- ZIO .attempt(DidCommID(credentialRecordId)) .mapError(_ => PresentationError.NotValidDidCommID(credentialRecordId)) - vcSubjectId <- credentialService + issueCredentialRecord <- credentialService .findById(credentialRecordUuid) .someOrFail(CredentialServiceError.RecordNotFound(credentialRecordUuid)) - .map(_.subjectId) - .someOrElseZIO(ZIO.dieMessage(s"VC SubjectId not found in credential record: $credentialRecordUuid")) + vcSubjectId <- issueCredentialRecord.subjectId match + case None => ZIO.dieMessage(s"VC SubjectId not found in credential record: $credentialRecordUuid") + case Some(value) => ZIO.succeed(value) proverDID <- ZIO .fromEither(PrismDID.fromString(vcSubjectId)) .mapError(e => CredentialServiceError.UnsupportedDidFormat(vcSubjectId)) longFormPrismDID <- getLongForm(proverDID, true) - jwtIssuer <- createJwtIssuer(longFormPrismDID, VerificationRelationship.Authentication) + mKidIssuer = issueCredentialRecord.keyId + jwtIssuer <- createJwtVcIssuer(longFormPrismDID, VerificationRelationship.Authentication, mKidIssuer) } yield jwtIssuer } diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/jobs/StatusListJobs.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/jobs/StatusListJobs.scala index 7ba57aa3fb..d57cccb079 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/jobs/StatusListJobs.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/jobs/StatusListJobs.scala @@ -6,6 +6,7 @@ import org.hyperledger.identus.mercury.* import org.hyperledger.identus.mercury.protocol.revocationnotificaiton.RevocationNotification import org.hyperledger.identus.pollux.core.service.{CredentialService, CredentialStatusListService} import org.hyperledger.identus.pollux.vc.jwt.revocation.{VCStatusList2021, VCStatusList2021Error} +import org.hyperledger.identus.shared.models.* import org.hyperledger.identus.shared.models.WalletAccessContext import org.hyperledger.identus.shared.utils.DurationOps.toMetricsSeconds import zio.* @@ -30,7 +31,11 @@ object StatusListJobs extends BackgroundJobsHelper { vcStatusListCredJson <- ZIO .fromEither(io.circe.parser.parse(vcStatusListCredString)) .mapError(_.underlying) - issuer <- createJwtIssuer(statusListWithCreds.issuer, VerificationRelationship.AssertionMethod) + issuer <- createJwtVcIssuer( + statusListWithCreds.issuer, + VerificationRelationship.AssertionMethod, + None + ) vcStatusListCred <- VCStatusList2021 .decodeFromJson(vcStatusListCredJson, issuer) .mapError(x => new Throwable(x.msg)) diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/didcomm/controller/DIDCommControllerError.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/didcomm/controller/DIDCommControllerError.scala index 2c46434de9..1bc1a644d4 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/didcomm/controller/DIDCommControllerError.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/didcomm/controller/DIDCommControllerError.scala @@ -2,6 +2,7 @@ package org.hyperledger.identus.didcomm.controller import org.hyperledger.identus.mercury.model.DidId import org.hyperledger.identus.shared.models.{Failure, StatusCode} +import org.hyperledger.identus.shared.models.KeyId sealed trait DIDCommControllerError extends Failure { override def namespace = "DIDCommControllerError" @@ -34,8 +35,9 @@ object DIDCommControllerError { override def userFacingMessage: String = s"The Peer DID was not found in this agent: ${did.value}" } - final case class PeerDIDKeyNotFoundError(did: DidId, keyId: String) extends DIDCommControllerError { + final case class PeerDIDKeyNotFoundError(did: DidId, keyId: KeyId) extends DIDCommControllerError { override def statusCode: StatusCode = StatusCode.UnprocessableContent - override def userFacingMessage: String = s"The Peer DID does not contain the required key: DID=$did, keyId=$keyId" + override def userFacingMessage: String = + s"The Peer DID does not contain the required key: DID=${did.value}, keyId=${keyId.value}" } } diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/issue/controller/IssueControllerImpl.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/issue/controller/IssueControllerImpl.scala index 48a5a5619d..587b3376da 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/issue/controller/IssueControllerImpl.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/issue/controller/IssueControllerImpl.scala @@ -67,6 +67,7 @@ class IssueControllerImpl( .createJWTIssueCredentialRecord( pairwiseIssuerDID = offerContext.pairwiseIssuerDID, pairwiseHolderDID = offerContext.pairwiseHolderDID, + kidIssuer = request.issuingKid, thid = DidCommID(), maybeSchemaId = request.schemaId, claims = jsonClaims, @@ -91,6 +92,7 @@ class IssueControllerImpl( .createSDJWTIssueCredentialRecord( pairwiseIssuerDID = offerContext.pairwiseIssuerDID, pairwiseHolderDID = offerContext.pairwiseHolderDID, + kidIssuer = request.issuingKid, thid = DidCommID(), maybeSchemaId = request.schemaId, claims = jsonClaims, diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/issue/controller/http/CreateIssueCredentialRecordRequest.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/issue/controller/http/CreateIssueCredentialRecordRequest.scala index 930d8e36f2..405b302409 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/issue/controller/http/CreateIssueCredentialRecordRequest.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/issue/controller/http/CreateIssueCredentialRecordRequest.scala @@ -2,6 +2,7 @@ package org.hyperledger.identus.issue.controller.http import org.hyperledger.identus.api.http.Annotation import org.hyperledger.identus.issue.controller.http.CreateIssueCredentialRecordRequest.annotations +import org.hyperledger.identus.shared.models.KeyId import sttp.tapir.{Schema, Validator} import sttp.tapir.json.zio.schemaForZioJsonValue import sttp.tapir.Schema.annotations.{description, encodedExample} @@ -48,6 +49,9 @@ final case class CreateIssueCredentialRecordRequest( @description(annotations.issuingDID.description) @encodedExample(annotations.issuingDID.example) issuingDID: Option[String], + @description(annotations.issuingKid.description) + @encodedExample(annotations.issuingKid.example) + issuingKid: Option[KeyId], @description(annotations.connectionId.description) @encodedExample(annotations.connectionId.example) connectionId: Option[UUID], @@ -133,6 +137,16 @@ object CreateIssueCredentialRecordRequest { example = Some("did:prism:3bb0505d13fcb04d28a48234edb27b0d4e6d7e18a81e2c1abab58f3bbc21ce6f") ) + object issuingKid + extends Annotation[Option[String]]( + description = """ + |Specified the key ID (kid) of the DID, it will be used to sign credential. + |User should specify just the partial identifier of the key. The full id of the kid MUST be "#" + |Note the cryto algorithm used with depend type of the key. + |""".stripMargin, + example = Some("kid1") // TODO 20240902 get the defualt name of the key we generete. + ) + object connectionId extends Annotation[Option[UUID]]( description = """ @@ -170,6 +184,7 @@ object CreateIssueCredentialRecordRequest { given decoder: JsonDecoder[CreateIssueCredentialRecordRequest] = DeriveJsonDecoder.gen[CreateIssueCredentialRecordRequest] + given schemaJson: Schema[KeyId] = Schema.schemaForString.map[KeyId](v => Some(KeyId(v)))(KeyId.value) given schema: Schema[CreateIssueCredentialRecordRequest] = Schema.derived } diff --git a/cloud-agent/service/server/src/test/scala/org/hyperledger/identus/issue/controller/IssueControllerImplSpec.scala b/cloud-agent/service/server/src/test/scala/org/hyperledger/identus/issue/controller/IssueControllerImplSpec.scala index 75327b35b7..edcb5f1411 100644 --- a/cloud-agent/service/server/src/test/scala/org/hyperledger/identus/issue/controller/IssueControllerImplSpec.scala +++ b/cloud-agent/service/server/src/test/scala/org/hyperledger/identus/issue/controller/IssueControllerImplSpec.scala @@ -22,6 +22,7 @@ import org.hyperledger.identus.mercury.protocol.invitation.v2.Invitation import org.hyperledger.identus.pollux.core.model.{CredentialFormat, DidCommID, IssueCredentialRecord} import org.hyperledger.identus.pollux.core.model.IssueCredentialRecord.{ProtocolState, Role} import org.hyperledger.identus.pollux.core.service.MockCredentialService +import org.hyperledger.identus.shared.models.KeyId import org.hyperledger.identus.shared.models.WalletId import sttp.client3.{basicRequest, DeserializationException, UriContext} import sttp.client3.ziojson.* @@ -56,6 +57,7 @@ object IssueControllerImplSpec extends ZIOSpecDefault with IssueControllerTestTo issuingDID = Some( "did:prism:332518729a7b7805f73a788e0944802527911901d9b7c16152281be9bc62d944:CosBCogBEkkKFW15LWtleS1hdXRoZW50aWNhdGlvbhAESi4KCXNlY3AyNTZrMRIhAuYoRIefsLhkvYwHz8gDtkG2b0kaZTDOLj_SExWX1fOXEjsKB21hc3RlcjAQAUouCglzZWNwMjU2azESIQLOzab8f0ibt1P0zdMfoWDQTSlPc8_tkV9Jk5BBsXB8fA" ), + issuingKid = Some(KeyId("some_kid_id")), connectionId = Some(UUID.fromString("123e4567-e89b-12d3-a456-426614174000")) ) private val issueCredentialRecord = IssueCredentialRecord( diff --git a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/model/KeyManagement.scala b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/model/KeyManagement.scala index d3aa382dc2..f4a84785f8 100644 --- a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/model/KeyManagement.scala +++ b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/model/KeyManagement.scala @@ -161,15 +161,15 @@ final case class ManagedDIDRandKeyMeta( } private[walletapi] final case class CreateDIDKey( - hdKeys: Map[String, ManagedDIDHdKeyPath], - randKeys: Map[String, ManagedDIDRandKeyPair] + hdKeys: Map[String, ManagedDIDHdKeyPath], // TODO use type KeyId + randKeys: Map[String, ManagedDIDRandKeyPair] // TODO use type KeyId ) { def randKeyMeta: Map[String, ManagedDIDRandKeyMeta] = randKeys.map { case (k, v) => k -> v.meta } } private[walletapi] final case class UpdateDIDKey( - hdKeys: Map[String, ManagedDIDHdKeyPath], - randKeys: Map[String, ManagedDIDRandKeyPair], + hdKeys: Map[String, ManagedDIDHdKeyPath], // TODO use type KeyId + randKeys: Map[String, ManagedDIDRandKeyPair], // TODO use type KeyId counter: HdKeyIndexCounter ) { def randKeyMeta: Map[String, ManagedDIDRandKeyMeta] = randKeys.map { case (k, v) => k -> v.meta } diff --git a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/model/error/DIDSecretStorageError.scala b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/model/error/DIDSecretStorageError.scala index 9112f841c0..f8e4f75569 100644 --- a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/model/error/DIDSecretStorageError.scala +++ b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/model/error/DIDSecretStorageError.scala @@ -11,7 +11,7 @@ sealed trait DIDSecretStorageError( } object DIDSecretStorageError { - case class KeyNotFoundError(didId: DidId, keyId: String) + case class KeyNotFoundError(didId: DidId, keyId: KeyId) extends DIDSecretStorageError( StatusCode.NotFound, s"The not found: keyId='$keyId', didId='$didId'" diff --git a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/service/ManagedDIDService.scala b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/service/ManagedDIDService.scala index e8cba848c1..c233844df1 100644 --- a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/service/ManagedDIDService.scala +++ b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/service/ManagedDIDService.scala @@ -7,6 +7,7 @@ import org.hyperledger.identus.castor.core.model.did.* import org.hyperledger.identus.mercury.model.* import org.hyperledger.identus.mercury.PeerDID import org.hyperledger.identus.shared.crypto.{Ed25519KeyPair, Secp256k1KeyPair, X25519KeyPair} +import org.hyperledger.identus.shared.models.KeyId import org.hyperledger.identus.shared.models.WalletAccessContext import zio.* @@ -23,7 +24,7 @@ trait ManagedDIDService { def findDIDKeyPair( did: CanonicalPrismDID, - keyId: String + keyId: KeyId ): URIO[WalletAccessContext, Option[Secp256k1KeyPair | Ed25519KeyPair | X25519KeyPair]] def getManagedDIDState(did: CanonicalPrismDID): ZIO[WalletAccessContext, GetManagedDIDError, Option[ManagedDIDState]] diff --git a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/service/ManagedDIDServiceImpl.scala b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/service/ManagedDIDServiceImpl.scala index b021c28d33..c93806c040 100644 --- a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/service/ManagedDIDServiceImpl.scala +++ b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/service/ManagedDIDServiceImpl.scala @@ -14,6 +14,7 @@ import org.hyperledger.identus.castor.core.util.DIDOperationValidator import org.hyperledger.identus.mercury.model.DidId import org.hyperledger.identus.mercury.PeerDID import org.hyperledger.identus.shared.crypto.{Apollo, Ed25519KeyPair, Secp256k1KeyPair, X25519KeyPair} +import org.hyperledger.identus.shared.models.KeyId import org.hyperledger.identus.shared.models.WalletAccessContext import zio.* @@ -33,13 +34,13 @@ class ManagedDIDServiceImpl private[walletapi] ( createDIDSem: Semaphore ) extends ManagedDIDService { - private val AGREEMENT_KEY_ID = "agreement" - private val AUTHENTICATION_KEY_ID = "authentication" + private val AGREEMENT_KEY_ID = KeyId("agreement") + private val AUTHENTICATION_KEY_ID = KeyId("authentication") private val keyResolver = KeyResolver(apollo, nonSecretStorage, secretStorage, walletSecretStorage) private val publicationHandler = PublicationHandler(didService, keyResolver)(DEFAULT_MASTER_KEY_ID) private val didCreateHandler = - DIDCreateHandler(apollo, nonSecretStorage, secretStorage, walletSecretStorage)(DEFAULT_MASTER_KEY_ID) + DIDCreateHandler(apollo, nonSecretStorage, secretStorage, walletSecretStorage)(KeyId(DEFAULT_MASTER_KEY_ID)) private val didUpdateHandler = DIDUpdateHandler(apollo, nonSecretStorage, secretStorage, walletSecretStorage, publicationHandler) @@ -56,7 +57,7 @@ class ManagedDIDServiceImpl private[walletapi] ( override def findDIDKeyPair( did: CanonicalPrismDID, - keyId: String + keyId: KeyId ): URIO[WalletAccessContext, Option[Secp256k1KeyPair | Ed25519KeyPair | X25519KeyPair]] = nonSecretStorage .getManagedDIDState(did) diff --git a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/service/handler/DIDCreateHandler.scala b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/service/handler/DIDCreateHandler.scala index 1c2f239b55..479b34ed2b 100644 --- a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/service/handler/DIDCreateHandler.scala +++ b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/service/handler/DIDCreateHandler.scala @@ -12,6 +12,7 @@ import org.hyperledger.identus.agent.walletapi.storage.{DIDNonSecretStorage, DID import org.hyperledger.identus.agent.walletapi.util.OperationFactory import org.hyperledger.identus.castor.core.model.did.PrismDIDOperation import org.hyperledger.identus.shared.crypto.{Apollo, Ed25519KeyPair, X25519KeyPair} +import org.hyperledger.identus.shared.models.KeyId import org.hyperledger.identus.shared.models.WalletAccessContext import zio.* @@ -21,7 +22,7 @@ private[walletapi] class DIDCreateHandler( secretStorage: DIDSecretStorage, walletSecretStorage: WalletSecretStorage, )( - masterKeyId: String + masterKeyId: KeyId ) { def materialize( didTemplate: ManagedDIDTemplate @@ -64,8 +65,8 @@ private[walletapi] class DIDCreateMaterialImpl(nonSecretStorage: DIDNonSecretSto .mapError(CreateManagedDIDError.WalletStorageError.apply) _ <- ZIO.foreach(keys.randKeys.toList) { case (keyId, key) => key.keyPair match { - case kp: Ed25519KeyPair => secretStorage.insertPrismDIDKeyPair(did, keyId, operationHash, kp) - case kp: X25519KeyPair => secretStorage.insertPrismDIDKeyPair(did, keyId, operationHash, kp) + case kp: Ed25519KeyPair => secretStorage.insertPrismDIDKeyPair(did, KeyId(keyId), operationHash, kp) + case kp: X25519KeyPair => secretStorage.insertPrismDIDKeyPair(did, KeyId(keyId), operationHash, kp) } } } yield () diff --git a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/service/handler/DIDUpdateHandler.scala b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/service/handler/DIDUpdateHandler.scala index de517075dd..b93510bc69 100644 --- a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/service/handler/DIDUpdateHandler.scala +++ b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/service/handler/DIDUpdateHandler.scala @@ -19,6 +19,7 @@ import org.hyperledger.identus.castor.core.model.did.{ } import org.hyperledger.identus.castor.core.model.did.PrismDIDOperation.Update import org.hyperledger.identus.shared.crypto.{Apollo, Ed25519KeyPair, X25519KeyPair} +import org.hyperledger.identus.shared.models.KeyId import org.hyperledger.identus.shared.models.WalletAccessContext import zio.* @@ -103,16 +104,16 @@ private class HdKeyUpdateMaterial(nonSecretStorage: DIDNonSecretStorage, secretS for { _ <- ZIO.foreach(keys.hdKeys.toList) { case (keyId, keyPath) => val meta = ManagedDIDKeyMeta.HD(keyPath) - nonSecretStorage.insertKeyMeta(did, keyId, meta, operationHash) + nonSecretStorage.insertKeyMeta(did, KeyId(keyId), meta, operationHash) } _ <- ZIO.foreach(keys.randKeyMeta.toList) { case (keyId, rand) => val meta = ManagedDIDKeyMeta.Rand(rand) - nonSecretStorage.insertKeyMeta(did, keyId, meta, operationHash) + nonSecretStorage.insertKeyMeta(did, KeyId(keyId), meta, operationHash) } _ <- ZIO.foreach(keys.randKeys.toList) { case (keyId, key) => key.keyPair match { - case kp: Ed25519KeyPair => secretStorage.insertPrismDIDKeyPair(did, keyId, operationHash, kp) - case kp: X25519KeyPair => secretStorage.insertPrismDIDKeyPair(did, keyId, operationHash, kp) + case kp: Ed25519KeyPair => secretStorage.insertPrismDIDKeyPair(did, KeyId(keyId), operationHash, kp) + case kp: X25519KeyPair => secretStorage.insertPrismDIDKeyPair(did, KeyId(keyId), operationHash, kp) } } } yield () diff --git a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/service/handler/PublicationHandler.scala b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/service/handler/PublicationHandler.scala index 41014a3c22..1807e9efd9 100644 --- a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/service/handler/PublicationHandler.scala +++ b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/service/handler/PublicationHandler.scala @@ -11,6 +11,7 @@ import org.hyperledger.identus.castor.core.model.did.{ import org.hyperledger.identus.castor.core.model.error.DIDOperationError import org.hyperledger.identus.castor.core.service.DIDService import org.hyperledger.identus.shared.crypto.Secp256k1KeyPair +import org.hyperledger.identus.shared.models.KeyId import org.hyperledger.identus.shared.models.WalletAccessContext import zio.* @@ -25,7 +26,7 @@ class PublicationHandler(didService: DIDService, keyResolver: KeyResolver)(maste for { masterKeyPair <- keyResolver - .getKey(state.did, masterKeyId) + .getKey(state.did, KeyId(masterKeyId)) .someOrFail(Exception("master-key must exists in the wallet for signing DID operation and submit to Node")) .collect(Exception("master-key must be secp256k1 key")) { case keyPair: Secp256k1KeyPair => keyPair } .orDie diff --git a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/sql/JdbcDIDNonSecretStorage.scala b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/sql/JdbcDIDNonSecretStorage.scala index 4bc6154035..85cada9985 100644 --- a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/sql/JdbcDIDNonSecretStorage.scala +++ b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/sql/JdbcDIDNonSecretStorage.scala @@ -17,6 +17,7 @@ import org.hyperledger.identus.shared.db.ContextAwareTask import org.hyperledger.identus.shared.db.Implicits.* import org.hyperledger.identus.shared.db.Implicits.given import org.hyperledger.identus.shared.models.{WalletAccessContext, WalletId} +import org.hyperledger.identus.shared.models.KeyId import zio.* import zio.interop.catz.* @@ -199,7 +200,7 @@ class JdbcDIDNonSecretStorage(xa: Transactor[ContextAwareTask], xb: Transactor[T override def getKeyMeta( did: PrismDID, - keyId: String + keyId: KeyId ): RIO[WalletAccessContext, Option[(ManagedDIDKeyMeta, Array[Byte])]] = { val status: ScheduledDIDOperationStatus = ScheduledDIDOperationStatus.Confirmed val cxnIO = @@ -216,7 +217,7 @@ class JdbcDIDNonSecretStorage(xa: Transactor[ContextAwareTask], xb: Transactor[T | LEFT JOIN public.prism_did_update_lineage ul ON hd.operation_hash = ul.operation_hash | WHERE | hd.did = $did - | AND hd.key_id = $keyId + | AND hd.key_id = ${keyId.value} | AND (ul.status = $status OR (ul.status IS NULL AND hd.operation_hash = sha256(ws.atala_operation_content))) """.stripMargin .query[ @@ -245,7 +246,7 @@ class JdbcDIDNonSecretStorage(xa: Transactor[ContextAwareTask], xb: Transactor[T override def insertKeyMeta( did: PrismDID, - keyId: String, + keyId: KeyId, meta: ManagedDIDKeyMeta, operationHash: Array[Byte] ): RIO[WalletAccessContext, Unit] = { @@ -258,7 +259,7 @@ class JdbcDIDNonSecretStorage(xa: Transactor[ContextAwareTask], xb: Transactor[T | VALUES | ( | $did, - | $keyId, + | ${keyId.value}, | ${keyUsage}, | ${keyIndex}, | $now, diff --git a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/sql/JdbcDIDSecretStorage.scala b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/sql/JdbcDIDSecretStorage.scala index 0ab551a07d..76f9e2650f 100644 --- a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/sql/JdbcDIDSecretStorage.scala +++ b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/sql/JdbcDIDSecretStorage.scala @@ -10,6 +10,7 @@ import org.hyperledger.identus.mercury.model.DidId import org.hyperledger.identus.shared.crypto.jwk.{FromJWK, JWK} import org.hyperledger.identus.shared.db.ContextAwareTask import org.hyperledger.identus.shared.db.Implicits.* +import org.hyperledger.identus.shared.models.KeyId import org.hyperledger.identus.shared.models.WalletAccessContext import zio.* @@ -28,13 +29,13 @@ class JdbcDIDSecretStorage(xa: Transactor[ContextAwareTask]) extends DIDSecretSt given didIdPut: Put[DidId] = Put[String].contramap(_.value) - override def getKey(did: DidId, keyId: String): RIO[WalletAccessContext, Option[OctetKeyPair]] = { + override def getKey(did: DidId, keyId: KeyId): RIO[WalletAccessContext, Option[OctetKeyPair]] = { val cxnIO = sql""" | SELECT key_pair | FROM public.peer_did_rand_key | WHERE | did = $did - | AND key_id = $keyId + | AND key_id = ${keyId.value} """.stripMargin .query[OctetKeyPair] .option @@ -42,7 +43,7 @@ class JdbcDIDSecretStorage(xa: Transactor[ContextAwareTask]) extends DIDSecretSt cxnIO.transactWallet(xa) } - override def insertKey(did: DidId, keyId: String, keyPair: OctetKeyPair): RIO[WalletAccessContext, Int] = { + override def insertKey(did: DidId, keyId: KeyId, keyPair: OctetKeyPair): RIO[WalletAccessContext, Int] = { val cxnIO = (now: InstantAsBigInt) => sql""" | INSERT INTO public.peer_did_rand_key( | did, @@ -52,7 +53,7 @@ class JdbcDIDSecretStorage(xa: Transactor[ContextAwareTask]) extends DIDSecretSt | ) values ( | ${did}, | ${now}, - | ${keyId}, + | ${keyId.value}, | ${keyPair} | ) """.stripMargin.update @@ -65,7 +66,7 @@ class JdbcDIDSecretStorage(xa: Transactor[ContextAwareTask]) extends DIDSecretSt override def insertPrismDIDKeyPair[K]( did: PrismDID, - keyId: String, + keyId: KeyId, operationHash: Array[Byte], keyPair: K )(using c: Conversion[K, JWK]): URIO[WalletAccessContext, Unit] = { @@ -80,7 +81,7 @@ class JdbcDIDSecretStorage(xa: Transactor[ContextAwareTask]) extends DIDSecretSt | ) values ( | ${did}, | ${now}, - | ${keyId}, + | ${keyId.value}, | ${operationHash}, | ${jwk} | ) @@ -92,7 +93,7 @@ class JdbcDIDSecretStorage(xa: Transactor[ContextAwareTask]) extends DIDSecretSt } yield () } - override def getPrismDIDKeyPair[K](did: PrismDID, keyId: String, operationHash: Array[Byte])(using + override def getPrismDIDKeyPair[K](did: PrismDID, keyId: KeyId, operationHash: Array[Byte])(using c: FromJWK[K] ): URIO[WalletAccessContext, Option[K]] = { val cxnIO = sql""" @@ -101,7 +102,7 @@ class JdbcDIDSecretStorage(xa: Transactor[ContextAwareTask]) extends DIDSecretSt | WHERE | did = $did | AND operation_hash = $operationHash - | AND key_id = $keyId + | AND key_id = ${keyId.value} """.stripMargin .query[JWK] .option diff --git a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/storage/DIDNonSecretStorage.scala b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/storage/DIDNonSecretStorage.scala index 8527cff127..e1f84efb4b 100644 --- a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/storage/DIDNonSecretStorage.scala +++ b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/storage/DIDNonSecretStorage.scala @@ -4,6 +4,7 @@ import org.hyperledger.identus.agent.walletapi.model.* import org.hyperledger.identus.castor.core.model.did.{PrismDID, ScheduledDIDOperationStatus} import org.hyperledger.identus.mercury.model.DidId import org.hyperledger.identus.shared.models.{WalletAccessContext, WalletId} +import org.hyperledger.identus.shared.models.KeyId import zio.* trait DIDNonSecretStorage { @@ -24,11 +25,11 @@ trait DIDNonSecretStorage { def getHdKeyCounter(did: PrismDID): RIO[WalletAccessContext, Option[HdKeyIndexCounter]] /** Return a tuple of key metadata and the operation hash */ - def getKeyMeta(did: PrismDID, keyId: String): RIO[WalletAccessContext, Option[(ManagedDIDKeyMeta, Array[Byte])]] + def getKeyMeta(did: PrismDID, keyId: KeyId): RIO[WalletAccessContext, Option[(ManagedDIDKeyMeta, Array[Byte])]] def insertKeyMeta( did: PrismDID, - keyId: String, + keyId: KeyId, meta: ManagedDIDKeyMeta, operationHash: Array[Byte] ): RIO[WalletAccessContext, Unit] diff --git a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/storage/DIDSecretStorage.scala b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/storage/DIDSecretStorage.scala index a0becbea46..4726e5a015 100644 --- a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/storage/DIDSecretStorage.scala +++ b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/storage/DIDSecretStorage.scala @@ -4,22 +4,23 @@ import com.nimbusds.jose.jwk.OctetKeyPair import org.hyperledger.identus.castor.core.model.did.PrismDID import org.hyperledger.identus.mercury.model.DidId import org.hyperledger.identus.shared.crypto.jwk.{FromJWK, JWK} +import org.hyperledger.identus.shared.models.KeyId import org.hyperledger.identus.shared.models.WalletAccessContext import zio.* /** A simple single-user DID key storage */ trait DIDSecretStorage { - def insertKey(did: DidId, keyId: String, keyPair: OctetKeyPair): RIO[WalletAccessContext, Int] - def getKey(did: DidId, keyId: String): RIO[WalletAccessContext, Option[OctetKeyPair]] + def insertKey(did: DidId, keyId: KeyId, keyPair: OctetKeyPair): RIO[WalletAccessContext, Int] + def getKey(did: DidId, keyId: KeyId): RIO[WalletAccessContext, Option[OctetKeyPair]] def insertPrismDIDKeyPair[K]( did: PrismDID, - keyId: String, + keyId: KeyId, operationHash: Array[Byte], keyPair: K )(using c: Conversion[K, JWK]): URIO[WalletAccessContext, Unit] - def getPrismDIDKeyPair[K](did: PrismDID, keyId: String, operationHash: Array[Byte])(using + def getPrismDIDKeyPair[K](did: PrismDID, keyId: KeyId, operationHash: Array[Byte])(using c: FromJWK[K] ): URIO[WalletAccessContext, Option[K]] } diff --git a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/util/KeyResolver.scala b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/util/KeyResolver.scala index 81a5fa8035..6460af34f5 100644 --- a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/util/KeyResolver.scala +++ b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/util/KeyResolver.scala @@ -9,6 +9,7 @@ import org.hyperledger.identus.agent.walletapi.model.{ import org.hyperledger.identus.agent.walletapi.storage.{DIDNonSecretStorage, DIDSecretStorage, WalletSecretStorage} import org.hyperledger.identus.castor.core.model.did.{EllipticCurve, PrismDID} import org.hyperledger.identus.shared.crypto.{Apollo, Ed25519KeyPair, Secp256k1KeyPair, X25519KeyPair} +import org.hyperledger.identus.shared.models.KeyId import org.hyperledger.identus.shared.models.WalletAccessContext import zio.* @@ -20,7 +21,7 @@ class KeyResolver( ) { def getKey( did: PrismDID, - keyId: String + keyId: KeyId, ): RIO[WalletAccessContext, Option[Secp256k1KeyPair | Ed25519KeyPair | X25519KeyPair]] = nonSecretStorage.getKeyMeta(did, keyId).flatMap { case None => ZIO.none @@ -36,7 +37,7 @@ class KeyResolver( private def getRandKey( did: PrismDID, - keyId: String, + keyId: KeyId, meta: ManagedDIDRandKeyMeta, operationHash: Array[Byte] ): RIO[WalletAccessContext, Option[Ed25519KeyPair | X25519KeyPair]] = { diff --git a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/util/OperationFactory.scala b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/util/OperationFactory.scala index 5eb2d4614c..acabbb413d 100644 --- a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/util/OperationFactory.scala +++ b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/util/OperationFactory.scala @@ -11,6 +11,7 @@ import org.hyperledger.identus.shared.crypto.{ X25519PublicKey } import org.hyperledger.identus.shared.models.Base64UrlString +import org.hyperledger.identus.shared.models.KeyId import zio.* import scala.collection.immutable.ArraySeq @@ -41,7 +42,7 @@ class OperationFactory(apollo: Apollo) { * The index of the DID to be used for the key derivation */ def makeCreateOperation( - masterKeyId: String, + masterKeyId: KeyId, seed: Array[Byte] )( didIndex: Int, @@ -66,9 +67,9 @@ class OperationFactory(apollo: Apollo) { context = didTemplate.contexts ) keys = CreateDIDKey( - hdKeys = hdKeysWithCounter._1.map(i => i.publicKey.id -> i.path).toMap ++ - Map(masterKeyOutcome.publicKey.id -> masterKeyOutcome.path), - randKeys = randKeys.map(i => i.publicKey.id -> i.key).toMap, + hdKeys = hdKeysWithCounter._1.map(i => i.publicKey.id.value -> i.path).toMap ++ + Map(masterKeyOutcome.publicKey.id.value -> masterKeyOutcome.path), + randKeys = randKeys.map(i => i.publicKey.id.value -> i.key).toMap, ) } yield operation -> keys } @@ -110,8 +111,8 @@ class OperationFactory(apollo: Apollo) { } keys = actionWithKey.collect { case (UpdateManagedDIDAction.AddKey(_), Some(secret)) => secret } (randKeys, hdKeys) = keys.partitionMap { - case (pk, hdPath: ManagedDIDHdKeyPath) => Right(pk.id -> hdPath) - case (pk, keyPair: ManagedDIDRandKeyPair) => Left(pk.id -> keyPair) + case (pk, hdPath: ManagedDIDHdKeyPath) => Right(pk.id.value -> hdPath) + case (pk, keyPair: ManagedDIDRandKeyPair) => Left(pk.id.value -> keyPair) } operation = PrismDIDOperation.Update( did = did, @@ -163,7 +164,7 @@ class OperationFactory(apollo: Apollo) { toPublicKeyData(kp.publicKey) -> kp } KeyGenerationOutcome( - publicKey = PublicKey(template.id, template.purpose, publicKeyData), + publicKey = PublicKey(KeyId(template.id), template.purpose, publicKeyData), key = ManagedDIDRandKeyPair(template.purpose, keyPair) ) }.orDie @@ -177,12 +178,12 @@ class OperationFactory(apollo: Apollo) { val keyPath = keyCounter.path(purpose) for { keyPair <- deriveSecp256k1KeyPair(seed, keyPath) - publicKey = PublicKey(template.id, purpose, toPublicKeyData(keyPair.publicKey)) + publicKey = PublicKey(KeyId(template.id), purpose, toPublicKeyData(keyPair.publicKey)) } yield KeyDerivationOutcome(publicKey, keyPath, keyCounter.next(purpose)) } private def deriveInternalPublicKey(seed: Array[Byte])( - id: String, + id: KeyId, purpose: InternalKeyPurpose, keyCounter: HdKeyIndexCounter ): UIO[KeyDerivationOutcome[InternalPublicKey]] = { diff --git a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/vault/VaultDIDSecretStorage.scala b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/vault/VaultDIDSecretStorage.scala index c40c6f4b57..1cf98aa9a1 100644 --- a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/vault/VaultDIDSecretStorage.scala +++ b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/vault/VaultDIDSecretStorage.scala @@ -7,13 +7,14 @@ import org.hyperledger.identus.mercury.model.DidId import org.hyperledger.identus.shared.crypto.jwk.{FromJWK, JWK} import org.hyperledger.identus.shared.crypto.Sha256Hash import org.hyperledger.identus.shared.models.{HexString, WalletAccessContext, WalletId} +import org.hyperledger.identus.shared.models.KeyId import zio.* import java.nio.charset.StandardCharsets class VaultDIDSecretStorage(vaultKV: VaultKVClient, useSemanticPath: Boolean) extends DIDSecretStorage { - override def insertKey(did: DidId, keyId: String, keyPair: OctetKeyPair): RIO[WalletAccessContext, Int] = { + override def insertKey(did: DidId, keyId: KeyId, keyPair: OctetKeyPair): RIO[WalletAccessContext, Int] = { for { walletId <- ZIO.serviceWith[WalletAccessContext](_.walletId) (path, metadata) = peerDidKeyPath(walletId)(did, keyId) @@ -25,7 +26,7 @@ class VaultDIDSecretStorage(vaultKV: VaultKVClient, useSemanticPath: Boolean) ex } yield 1 } - override def getKey(did: DidId, keyId: String): RIO[WalletAccessContext, Option[OctetKeyPair]] = { + override def getKey(did: DidId, keyId: KeyId): RIO[WalletAccessContext, Option[OctetKeyPair]] = { for { walletId <- ZIO.serviceWith[WalletAccessContext](_.walletId) (path, _) = peerDidKeyPath(walletId)(did, keyId) @@ -35,7 +36,7 @@ class VaultDIDSecretStorage(vaultKV: VaultKVClient, useSemanticPath: Boolean) ex override def insertPrismDIDKeyPair[K]( did: PrismDID, - keyId: String, + keyId: KeyId, operationHash: Array[Byte], keyPair: K )(using c: Conversion[K, JWK]): URIO[WalletAccessContext, Unit] = { @@ -51,7 +52,7 @@ class VaultDIDSecretStorage(vaultKV: VaultKVClient, useSemanticPath: Boolean) ex } yield () }.orDie - override def getPrismDIDKeyPair[K](did: PrismDID, keyId: String, operationHash: Array[Byte])(using + override def getPrismDIDKeyPair[K](did: PrismDID, keyId: KeyId, operationHash: Array[Byte])(using c: FromJWK[K] ): URIO[WalletAccessContext, Option[K]] = { for { @@ -65,9 +66,9 @@ class VaultDIDSecretStorage(vaultKV: VaultKVClient, useSemanticPath: Boolean) ex }.orDie /** @return A tuple of secret path and a secret custom_metadata */ - private def peerDidKeyPath(walletId: WalletId)(did: DidId, keyId: String): (String, Map[String, String]) = { + private def peerDidKeyPath(walletId: WalletId)(did: DidId, keyId: KeyId): (String, Map[String, String]) = { val basePath = s"${walletBasePath(walletId)}/dids/peer" - val relativePath = s"${did.value}/keys/$keyId" + val relativePath = s"${did.value}/keys/${keyId.value}" if (useSemanticPath) { s"$basePath/$relativePath" -> Map.empty } else { @@ -79,9 +80,9 @@ class VaultDIDSecretStorage(vaultKV: VaultKVClient, useSemanticPath: Boolean) ex /** @return A tuple of secret path and a secret custom_metadata */ private def prismDIDKeyPath( walletId: WalletId - )(did: PrismDID, keyId: String, operationHash: Array[Byte]): (String, Map[String, String]) = { + )(did: PrismDID, keyId: KeyId, operationHash: Array[Byte]): (String, Map[String, String]) = { val basePath = s"${walletBasePath(walletId)}/dids/prism" - val relativePath = s"${did.asCanonical}/keys/$keyId/${HexString.fromByteArray(operationHash)}" + val relativePath = s"${did.asCanonical}/keys/${keyId.value}/${HexString.fromByteArray(operationHash)}" if (useSemanticPath) { s"$basePath/$relativePath" -> Map.empty } else { diff --git a/cloud-agent/service/wallet-api/src/test/scala/org/hyperledger/identus/agent/walletapi/service/ManagedDIDServiceSpec.scala b/cloud-agent/service/wallet-api/src/test/scala/org/hyperledger/identus/agent/walletapi/service/ManagedDIDServiceSpec.scala index 4ab90ff8e1..8448ae4131 100644 --- a/cloud-agent/service/wallet-api/src/test/scala/org/hyperledger/identus/agent/walletapi/service/ManagedDIDServiceSpec.scala +++ b/cloud-agent/service/wallet-api/src/test/scala/org/hyperledger/identus/agent/walletapi/service/ManagedDIDServiceSpec.scala @@ -16,6 +16,7 @@ import org.hyperledger.identus.castor.core.service.DIDService import org.hyperledger.identus.castor.core.util.DIDOperationValidator import org.hyperledger.identus.shared.crypto.{ApolloSpecHelper, Ed25519KeyPair, Secp256k1KeyPair, X25519KeyPair} import org.hyperledger.identus.shared.models.{WalletAccessContext, WalletAdministrationContext} +import org.hyperledger.identus.shared.models.KeyId import org.hyperledger.identus.sharedtest.containers.PostgresTestContainerSupport import org.hyperledger.identus.test.container.{DBTestUtils, VaultTestContainerSupport} import zio.* @@ -246,16 +247,16 @@ object ManagedDIDServiceSpec for { svc <- ZIO.service[ManagedDIDService] did <- svc.createAndStoreDID(template).map(_.asCanonical) - masterKey <- svc.nonSecretStorage.getKeyMeta(did, ManagedDIDService.DEFAULT_MASTER_KEY_ID).some.map(_._1) - key1 <- svc.nonSecretStorage.getKeyMeta(did, "key1").some.map(_._1) - key2 <- svc.nonSecretStorage.getKeyMeta(did, "key2").some.map(_._1) - key3 <- svc.nonSecretStorage.getKeyMeta(did, "key3").some.map(_._1) - key4 <- svc.nonSecretStorage.getKeyMeta(did, "key4").some.map(_._1) - masterKeyPair <- svc.findDIDKeyPair(did, ManagedDIDService.DEFAULT_MASTER_KEY_ID).some - key1KeyPair <- svc.findDIDKeyPair(did, "key1").some - key2KeyPair <- svc.findDIDKeyPair(did, "key2").some - key3KeyPair <- svc.findDIDKeyPair(did, "key3").some - key4KeyPair <- svc.findDIDKeyPair(did, "key4").some + masterKey <- svc.nonSecretStorage.getKeyMeta(did, KeyId(ManagedDIDService.DEFAULT_MASTER_KEY_ID)).some.map(_._1) + key1 <- svc.nonSecretStorage.getKeyMeta(did, KeyId("key1")).some.map(_._1) + key2 <- svc.nonSecretStorage.getKeyMeta(did, KeyId("key2")).some.map(_._1) + key3 <- svc.nonSecretStorage.getKeyMeta(did, KeyId("key3")).some.map(_._1) + key4 <- svc.nonSecretStorage.getKeyMeta(did, KeyId("key4")).some.map(_._1) + masterKeyPair <- svc.findDIDKeyPair(did, KeyId(ManagedDIDService.DEFAULT_MASTER_KEY_ID)).some + key1KeyPair <- svc.findDIDKeyPair(did, KeyId("key1")).some + key2KeyPair <- svc.findDIDKeyPair(did, KeyId("key2")).some + key3KeyPair <- svc.findDIDKeyPair(did, KeyId("key3")).some + key4KeyPair <- svc.findDIDKeyPair(did, KeyId("key4")).some } yield assert(masterKey)(isSubtype[ManagedDIDKeyMeta.HD](anything)) && assert(key1)(isSubtype[ManagedDIDKeyMeta.HD](anything)) && assert(key2)(isSubtype[ManagedDIDKeyMeta.HD](anything)) && @@ -425,9 +426,9 @@ object ManagedDIDServiceSpec ) _ <- svc.updateManagedDID(did, actions) _ <- svc.syncUnconfirmedUpdateOperations - key1KeyPair <- svc.findDIDKeyPair(did, "key-1").some - key2KeyPair <- svc.findDIDKeyPair(did, "key-2").some - key3KeyPair <- svc.findDIDKeyPair(did, "key-3").some + key1KeyPair <- svc.findDIDKeyPair(did, KeyId("key-1")).some + key2KeyPair <- svc.findDIDKeyPair(did, KeyId("key-2")).some + key3KeyPair <- svc.findDIDKeyPair(did, KeyId("key-3")).some } yield assert(key1KeyPair)(isSubtype[Secp256k1KeyPair](anything)) && assert(key2KeyPair)(isSubtype[Ed25519KeyPair](anything)) && assert(key3KeyPair)(isSubtype[X25519KeyPair](anything)) @@ -449,13 +450,13 @@ object ManagedDIDServiceSpec _ <- svc.updateManagedDID(did, actions) // 1st update _ <- testDIDSvc.setOperationStatus(ScheduledDIDOperationStatus.Confirmed) _ <- svc.syncUnconfirmedUpdateOperations - key1KeyPair1 <- svc.findDIDKeyPair(did, "key-1").some - key2KeyPair1 <- svc.findDIDKeyPair(did, "key-2").some + key1KeyPair1 <- svc.findDIDKeyPair(did, KeyId("key-1")).some + key2KeyPair1 <- svc.findDIDKeyPair(did, KeyId("key-2")).some _ <- svc.updateManagedDID(did, actions) // 2nd update _ <- testDIDSvc.setOperationStatus(ScheduledDIDOperationStatus.Rejected) _ <- svc.syncUnconfirmedUpdateOperations - key1KeyPair2 <- svc.findDIDKeyPair(did, "key-1").some - key2KeyPair2 <- svc.findDIDKeyPair(did, "key-2").some + key1KeyPair2 <- svc.findDIDKeyPair(did, KeyId("key-1")).some + key2KeyPair2 <- svc.findDIDKeyPair(did, KeyId("key-2")).some } yield assert(key1KeyPair1)(isSubtype[Secp256k1KeyPair](anything)) && assert(key2KeyPair1)(isSubtype[Ed25519KeyPair](anything)) && // 2nd update with rejected status does not update the key pair diff --git a/cloud-agent/service/wallet-api/src/test/scala/org/hyperledger/identus/agent/walletapi/service/MockManagedDIDService.scala b/cloud-agent/service/wallet-api/src/test/scala/org/hyperledger/identus/agent/walletapi/service/MockManagedDIDService.scala index 98c2d01969..2e37e35faf 100644 --- a/cloud-agent/service/wallet-api/src/test/scala/org/hyperledger/identus/agent/walletapi/service/MockManagedDIDService.scala +++ b/cloud-agent/service/wallet-api/src/test/scala/org/hyperledger/identus/agent/walletapi/service/MockManagedDIDService.scala @@ -12,6 +12,7 @@ import org.hyperledger.identus.castor.core.model.did.{ import org.hyperledger.identus.mercury.model.DidId import org.hyperledger.identus.mercury.PeerDID import org.hyperledger.identus.shared.crypto.{Ed25519KeyPair, Secp256k1KeyPair, X25519KeyPair} +import org.hyperledger.identus.shared.models.KeyId import zio.* import zio.mock.* import zio.test.Assertion @@ -20,7 +21,7 @@ object MockManagedDIDService extends Mock[ManagedDIDService] { object GetManagedDIDState extends Effect[CanonicalPrismDID, GetManagedDIDError, Option[ManagedDIDState]] object FindDIDKeyPair - extends Effect[(CanonicalPrismDID, String), Nothing, Option[Secp256k1KeyPair | Ed25519KeyPair | X25519KeyPair]] + extends Effect[(CanonicalPrismDID, KeyId), Nothing, Option[Secp256k1KeyPair | Ed25519KeyPair | X25519KeyPair]] override val compose: URLayer[mock.Proxy, ManagedDIDService] = ZLayer { @@ -35,7 +36,7 @@ object MockManagedDIDService extends Mock[ManagedDIDService] { override def findDIDKeyPair( did: CanonicalPrismDID, - keyId: String + keyId: KeyId ): UIO[Option[Secp256k1KeyPair | Ed25519KeyPair | X25519KeyPair]] = proxy(FindDIDKeyPair, (did, keyId)) diff --git a/cloud-agent/service/wallet-api/src/test/scala/org/hyperledger/identus/agent/walletapi/storage/DIDSecretStorageSpec.scala b/cloud-agent/service/wallet-api/src/test/scala/org/hyperledger/identus/agent/walletapi/storage/DIDSecretStorageSpec.scala index 60986e44cf..0ff4217449 100644 --- a/cloud-agent/service/wallet-api/src/test/scala/org/hyperledger/identus/agent/walletapi/storage/DIDSecretStorageSpec.scala +++ b/cloud-agent/service/wallet-api/src/test/scala/org/hyperledger/identus/agent/walletapi/storage/DIDSecretStorageSpec.scala @@ -13,6 +13,7 @@ import org.hyperledger.identus.castor.core.model.did.PrismDIDOperation import org.hyperledger.identus.mercury.PeerDID import org.hyperledger.identus.shared.crypto.{Apollo, ApolloSpecHelper, Ed25519KeyPair, X25519KeyPair} import org.hyperledger.identus.shared.models.{WalletAccessContext, WalletAdministrationContext} +import org.hyperledger.identus.shared.models.KeyId import org.hyperledger.identus.sharedtest.containers.PostgresTestContainerSupport import org.hyperledger.identus.test.container.{DBTestUtils, VaultTestContainerSupport} import zio.* @@ -90,10 +91,10 @@ object DIDSecretStorageSpec secretStorage <- ZIO.service[DIDSecretStorage] peerDID = PeerDID.makePeerDid() _ <- nonSecretStorage.createPeerDIDRecord(peerDID.did) - n1 <- secretStorage.insertKey(peerDID.did, "agreement", peerDID.jwkForKeyAgreement) - n2 <- secretStorage.insertKey(peerDID.did, "authentication", peerDID.jwkForKeyAuthentication) - key1 <- secretStorage.getKey(peerDID.did, "agreement") - key2 <- secretStorage.getKey(peerDID.did, "authentication") + n1 <- secretStorage.insertKey(peerDID.did, KeyId("agreement"), peerDID.jwkForKeyAgreement) + n2 <- secretStorage.insertKey(peerDID.did, KeyId("authentication"), peerDID.jwkForKeyAuthentication) + key1 <- secretStorage.getKey(peerDID.did, KeyId("agreement")) + key2 <- secretStorage.getKey(peerDID.did, KeyId("authentication")) } yield assert(n1)(equalTo(1)) && assert(n2)(equalTo(1)) && assert(key1)(isSome(equalTo(peerDID.jwkForKeyAgreement))) && @@ -105,11 +106,11 @@ object DIDSecretStorageSpec secretStorage <- ZIO.service[DIDSecretStorage] peerDID = PeerDID.makePeerDid() _ <- nonSecretStorage.createPeerDIDRecord(peerDID.did) - n1 <- secretStorage.insertKey(peerDID.did, "agreement", peerDID.jwkForKeyAgreement) + n1 <- secretStorage.insertKey(peerDID.did, KeyId("agreement"), peerDID.jwkForKeyAgreement) exit <- secretStorage - .insertKey(peerDID.did, "agreement", peerDID.jwkForKeyAuthentication) + .insertKey(peerDID.did, KeyId("agreement"), peerDID.jwkForKeyAuthentication) .exit - key1 <- secretStorage.getKey(peerDID.did, "agreement") + key1 <- secretStorage.getKey(peerDID.did, KeyId("agreement")) } yield assert(n1)(equalTo(1)) && assert(exit)(fails(anything)) && assert(key1)(isSome(equalTo(peerDID.jwkForKeyAgreement))) @@ -118,7 +119,7 @@ object DIDSecretStorageSpec for { secretStorage <- ZIO.service[DIDSecretStorage] peerDID = PeerDID.makePeerDid() - key1 <- secretStorage.getKey(peerDID.did, "agreement") + key1 <- secretStorage.getKey(peerDID.did, KeyId("agreement")) } yield assert(key1)(isNone) }, test("insert with long DID does not fail") { @@ -127,8 +128,8 @@ object DIDSecretStorageSpec secretStorage <- ZIO.service[DIDSecretStorage] peerDID = PeerDID.makePeerDid(serviceEndpoint = Some("http://localhost/" + ("a" * 100))) _ <- nonSecretStorage.createPeerDIDRecord(peerDID.did) - _ <- secretStorage.insertKey(peerDID.did, "agreement", peerDID.jwkForKeyAgreement) - _ <- secretStorage.insertKey(peerDID.did, "authentication", peerDID.jwkForKeyAuthentication) + _ <- secretStorage.insertKey(peerDID.did, KeyId("agreement"), peerDID.jwkForKeyAgreement) + _ <- secretStorage.insertKey(peerDID.did, KeyId("authentication"), peerDID.jwkForKeyAuthentication) } yield assertCompletes }, test("insert and get the same key for Prism KeyPair") { @@ -141,13 +142,13 @@ object DIDSecretStorageSpec did = createOperation.did state = ManagedDIDState(createOperation, 0, PublicationState.Created()) _ <- nonSecretStorage.insertManagedDID(did, state, Map.empty, Map.empty) - _ <- secretStorage.insertPrismDIDKeyPair(did, "key-1", createOperation.toAtalaOperationHash, key1) - _ <- secretStorage.insertPrismDIDKeyPair(did, "key-2", createOperation.toAtalaOperationHash, key2) + _ <- secretStorage.insertPrismDIDKeyPair(did, KeyId("key-1"), createOperation.toAtalaOperationHash, key1) + _ <- secretStorage.insertPrismDIDKeyPair(did, KeyId("key-2"), createOperation.toAtalaOperationHash, key2) getKey1 <- secretStorage - .getPrismDIDKeyPair[Ed25519KeyPair](did, "key-1", createOperation.toAtalaOperationHash) + .getPrismDIDKeyPair[Ed25519KeyPair](did, KeyId("key-1"), createOperation.toAtalaOperationHash) .some getKey2 <- secretStorage - .getPrismDIDKeyPair[X25519KeyPair](did, "key-2", createOperation.toAtalaOperationHash) + .getPrismDIDKeyPair[X25519KeyPair](did, KeyId("key-2"), createOperation.toAtalaOperationHash) .some } yield assert(key1)(equalTo(getKey1)) && assert(key2)(equalTo(getKey2)) @@ -162,10 +163,12 @@ object DIDSecretStorageSpec did = createOperation.did state = ManagedDIDState(createOperation, 0, PublicationState.Created()) _ <- nonSecretStorage.insertManagedDID(did, state, Map.empty, Map.empty) - _ <- secretStorage.insertPrismDIDKeyPair(did, "key-1", createOperation.toAtalaOperationHash, key1) - exit <- secretStorage.insertPrismDIDKeyPair(did, "key-1", createOperation.toAtalaOperationHash, key2).exit + _ <- secretStorage.insertPrismDIDKeyPair(did, KeyId("key-1"), createOperation.toAtalaOperationHash, key1) + exit <- secretStorage + .insertPrismDIDKeyPair(did, KeyId("key-1"), createOperation.toAtalaOperationHash, key2) + .exit getKey1 <- secretStorage - .getPrismDIDKeyPair[Ed25519KeyPair](did, "key-1", createOperation.toAtalaOperationHash) + .getPrismDIDKeyPair[Ed25519KeyPair](did, KeyId("key-1"), createOperation.toAtalaOperationHash) .some } yield assert(key1)(equalTo(getKey1)) && assert(exit)(dies(anything)) }, @@ -174,7 +177,8 @@ object DIDSecretStorageSpec secretStorage <- ZIO.service[DIDSecretStorage] createOperation = PrismDIDOperation.Create(Nil, Nil, Nil) did = createOperation.did - key1 <- secretStorage.getPrismDIDKeyPair[Ed25519KeyPair](did, "key-1", createOperation.toAtalaOperationHash) + key1 <- secretStorage + .getPrismDIDKeyPair[Ed25519KeyPair](did, KeyId("key-1"), createOperation.toAtalaOperationHash) } yield assert(key1)(isNone) }, ).globalWallet @@ -193,7 +197,7 @@ object DIDSecretStorageSpec .createPeerDIDRecord(peerDID1.did) .provide(ZLayer.succeed(WalletAccessContext(walletId1))) _ <- secretStorage - .insertKey(peerDID1.did, "key-1", peerDID1.jwkForKeyAgreement) + .insertKey(peerDID1.did, KeyId("key-1"), peerDID1.jwkForKeyAgreement) .provide(ZLayer.succeed(WalletAccessContext(walletId1))) // wallet2 setup peerDID2 = PeerDID.makePeerDid() @@ -201,20 +205,20 @@ object DIDSecretStorageSpec .createPeerDIDRecord(peerDID2.did) .provide(ZLayer.succeed(WalletAccessContext(walletId2))) _ <- secretStorage - .insertKey(peerDID2.did, "key-1", peerDID2.jwkForKeyAgreement) + .insertKey(peerDID2.did, KeyId("key-1"), peerDID2.jwkForKeyAgreement) .provide(ZLayer.succeed(WalletAccessContext(walletId2))) // assertions ownWallet1 <- secretStorage - .getKey(peerDID1.did, "key-1") + .getKey(peerDID1.did, KeyId("key-1")) .provide(ZLayer.succeed(WalletAccessContext(walletId1))) ownWallet2 <- secretStorage - .getKey(peerDID2.did, "key-1") + .getKey(peerDID2.did, KeyId("key-1")) .provide(ZLayer.succeed(WalletAccessContext(walletId2))) crossWallet1 <- secretStorage - .getKey(peerDID1.did, "key-1") + .getKey(peerDID1.did, KeyId("key-1")) .provide(ZLayer.succeed(WalletAccessContext(walletId2))) crossWallet2 <- secretStorage - .getKey(peerDID2.did, "key-1") + .getKey(peerDID2.did, KeyId("key-1")) .provide(ZLayer.succeed(WalletAccessContext(walletId1))) } yield assert(ownWallet1)(isSome(equalTo(peerDID1.jwkForKeyAgreement))) && assert(ownWallet2)(isSome(equalTo(peerDID2.jwkForKeyAgreement))) && diff --git a/cloud-agent/service/wallet-api/src/test/scala/org/hyperledger/identus/agent/walletapi/storage/MockDIDNonSecretStorage.scala b/cloud-agent/service/wallet-api/src/test/scala/org/hyperledger/identus/agent/walletapi/storage/MockDIDNonSecretStorage.scala index 004660d9ec..e20c1a245a 100644 --- a/cloud-agent/service/wallet-api/src/test/scala/org/hyperledger/identus/agent/walletapi/storage/MockDIDNonSecretStorage.scala +++ b/cloud-agent/service/wallet-api/src/test/scala/org/hyperledger/identus/agent/walletapi/storage/MockDIDNonSecretStorage.scala @@ -4,6 +4,7 @@ import org.hyperledger.identus.agent.walletapi.model.* import org.hyperledger.identus.castor.core.model.did.{PrismDID, ScheduledDIDOperationStatus} import org.hyperledger.identus.mercury.model.DidId import org.hyperledger.identus.shared.models.{WalletAccessContext, WalletId} +import org.hyperledger.identus.shared.models.KeyId import zio.* import zio.mock.{Expectation, Mock, Proxy} import zio.test.Assertion.equalTo @@ -54,13 +55,13 @@ case class MockDIDNonSecretStorage(proxy: Proxy) extends DIDNonSecretStorage { override def getKeyMeta( did: PrismDID, - keyId: String + keyId: KeyId ): RIO[WalletAccessContext, Option[(ManagedDIDKeyMeta, Array[Byte])]] = proxy(MockDIDNonSecretStorage.GetKeyMeta, (did, keyId)) override def insertKeyMeta( did: PrismDID, - keyId: String, + keyId: KeyId, meta: ManagedDIDKeyMeta, operationHash: Array[Byte] ): RIO[WalletAccessContext, Unit] = @@ -90,8 +91,8 @@ object MockDIDNonSecretStorage extends Mock[DIDNonSecretStorage] { object UpdateManagedDID extends Effect[(PrismDID, ManagedDIDStatePatch), Throwable, Unit] object GetMaxDIDIndex extends Effect[Unit, Throwable, Option[Int]] object GetHdKeyCounter extends Effect[PrismDID, Throwable, Option[HdKeyIndexCounter]] - object GetKeyMeta extends Effect[(PrismDID, String), Throwable, Option[(ManagedDIDKeyMeta, Array[Byte])]] - object InsertHdKeyMeta extends Effect[(PrismDID, String, ManagedDIDKeyMeta, Array[Byte]), Throwable, Unit] + object GetKeyMeta extends Effect[(PrismDID, KeyId), Throwable, Option[(ManagedDIDKeyMeta, Array[Byte])]] + object InsertHdKeyMeta extends Effect[(PrismDID, KeyId, ManagedDIDKeyMeta, Array[Byte]), Throwable, Unit] object ListHdKeyPath extends Effect[PrismDID, Throwable, Seq[(String, ArraySeq[Byte], ManagedDIDHdKeyPath)]] object ListManagedDID extends Effect[(Option[Int], Option[Int]), Throwable, (Seq[(PrismDID, ManagedDIDState)], Int)] diff --git a/cloud-agent/service/wallet-api/src/test/scala/org/hyperledger/identus/agent/walletapi/storage/StorageSpecHelper.scala b/cloud-agent/service/wallet-api/src/test/scala/org/hyperledger/identus/agent/walletapi/storage/StorageSpecHelper.scala index 9fe8e79427..ef6a3b05e4 100644 --- a/cloud-agent/service/wallet-api/src/test/scala/org/hyperledger/identus/agent/walletapi/storage/StorageSpecHelper.scala +++ b/cloud-agent/service/wallet-api/src/test/scala/org/hyperledger/identus/agent/walletapi/storage/StorageSpecHelper.scala @@ -19,6 +19,7 @@ import org.hyperledger.identus.castor.core.model.did.{ } import org.hyperledger.identus.shared.crypto.ApolloSpecHelper import org.hyperledger.identus.shared.models.{WalletAccessContext, WalletAdministrationContext} +import org.hyperledger.identus.shared.models.KeyId import zio.* import zio.test.* @@ -44,7 +45,7 @@ trait StorageSpecHelper extends ApolloSpecHelper { protected def generateKeyPair() = apollo.secp256k1.generateKeyPair protected def generateCreateOperation(keyIds: Seq[String], didIndex: Int) = - OperationFactory(apollo).makeCreateOperation("master0", Array.fill(64)(0))( + OperationFactory(apollo).makeCreateOperation(KeyId("master0"), Array.fill(64)(0))( didIndex, ManagedDIDTemplate( publicKeys = diff --git a/cloud-agent/service/wallet-api/src/test/scala/org/hyperledger/identus/agent/walletapi/util/OperationFactorySpec.scala b/cloud-agent/service/wallet-api/src/test/scala/org/hyperledger/identus/agent/walletapi/util/OperationFactorySpec.scala index 1e0642fe8f..8131d80f18 100644 --- a/cloud-agent/service/wallet-api/src/test/scala/org/hyperledger/identus/agent/walletapi/util/OperationFactorySpec.scala +++ b/cloud-agent/service/wallet-api/src/test/scala/org/hyperledger/identus/agent/walletapi/util/OperationFactorySpec.scala @@ -4,6 +4,7 @@ import org.hyperledger.identus.agent.walletapi.model.* import org.hyperledger.identus.castor.core.model.did.* import org.hyperledger.identus.shared.crypto.{ApolloSpecHelper, Ed25519KeyPair, X25519KeyPair} import org.hyperledger.identus.shared.models.HexString +import org.hyperledger.identus.shared.models.KeyId import zio.* import zio.test.* import zio.test.Assertion.* @@ -31,9 +32,9 @@ object OperationFactorySpec extends ZIOSpecDefault, ApolloSpecHelper { test("make CrateOperation from same seed is deterministic") { val didTemplate = ManagedDIDTemplate(Nil, Nil, Nil) for { - result1 <- operationFactory.makeCreateOperation("master0", seed)(0, didTemplate) + result1 <- operationFactory.makeCreateOperation(KeyId("master0"), seed)(0, didTemplate) (op1, hdKey1) = result1 - result2 <- operationFactory.makeCreateOperation("master0", seed)(0, didTemplate) + result2 <- operationFactory.makeCreateOperation(KeyId("master0"), seed)(0, didTemplate) (op2, hdKey2) = result2 } yield assert(op1)(equalTo(op2)) && assert(hdKey1)(equalTo(hdKey2)) @@ -41,7 +42,7 @@ object OperationFactorySpec extends ZIOSpecDefault, ApolloSpecHelper { test("make CreateOperation must contain 1 master key") { val didTemplate = ManagedDIDTemplate(Nil, Nil, Nil) for { - result <- operationFactory.makeCreateOperation("master-0", seed)(0, didTemplate) + result <- operationFactory.makeCreateOperation(KeyId("master-0"), seed)(0, didTemplate) (op, hdKey) = result pk = op.publicKeys.head.asInstanceOf[InternalPublicKey] } yield assert(op.publicKeys)(hasSize(equalTo(1))) && @@ -59,7 +60,7 @@ object OperationFactorySpec extends ZIOSpecDefault, ApolloSpecHelper { Nil ) for { - result <- operationFactory.makeCreateOperation("master-0", seed)(0, didTemplate) + result <- operationFactory.makeCreateOperation(KeyId("master-0"), seed)(0, didTemplate) (op, keys) = result } yield assert(op.publicKeys.length)(equalTo(4)) && assert(keys.hdKeys.size)(equalTo(4)) && @@ -80,26 +81,26 @@ object OperationFactorySpec extends ZIOSpecDefault, ApolloSpecHelper { Nil ) for { - result <- operationFactory.makeCreateOperation("master-0", seed)(0, didTemplate) + result <- operationFactory.makeCreateOperation(KeyId("master-0"), seed)(0, didTemplate) (op, keys) = result publicKeyData = op.publicKeys.map { case PublicKey(id, _, publicKeyData) => id -> publicKeyData case InternalPublicKey(id, _, publicKeyData) => id -> publicKeyData }.toMap } yield assert(publicKeyData.size)(equalTo(4)) && - assert(publicKeyData.get("auth-0").get)( + assert(publicKeyData.get(KeyId("auth-0")).get)( isSubtype[PublicKeyData.ECCompressedKeyData]( hasField[PublicKeyData.ECCompressedKeyData, Int]("data", _.data.toByteArray.length, equalTo(33)) && hasField("crv", _.crv, equalTo(EllipticCurve.SECP256K1)) ) ) && - assert(publicKeyData.get("auth-1").get)( + assert(publicKeyData.get(KeyId("auth-1")).get)( isSubtype[PublicKeyData.ECCompressedKeyData]( hasField[PublicKeyData.ECCompressedKeyData, Int]("data", _.data.toByteArray.length, equalTo(32)) && hasField("crv", _.crv, equalTo(EllipticCurve.ED25519)) ) ) && - assert(publicKeyData.get("comm-0").get)( + assert(publicKeyData.get(KeyId("comm-0")).get)( isSubtype[PublicKeyData.ECCompressedKeyData]( hasField[PublicKeyData.ECCompressedKeyData, Int]("data", _.data.toByteArray.length, equalTo(32)) && hasField("crv", _.crv, equalTo(EllipticCurve.X25519)) @@ -212,13 +213,13 @@ object OperationFactorySpec extends ZIOSpecDefault, ApolloSpecHelper { assert(keys.randKeys.get("comm-42").get.keyPair)(isSubtype[X25519KeyPair](anything)) && // operation is correct assert(op.actions)(hasSize(equalTo(2))) && - assert(addKeyActions.find(_.id == "auth-42").get.publicKeyData)( + assert(addKeyActions.find(_.id == KeyId("auth-42")).get.publicKeyData)( isSubtype[PublicKeyData.ECCompressedKeyData]( hasField[PublicKeyData.ECCompressedKeyData, Int]("data", _.data.toByteArray.length, equalTo(32)) && hasField("crv", _.crv, equalTo(EllipticCurve.ED25519)) ) ) && - assert(addKeyActions.find(_.id == "comm-42").get.publicKeyData)( + assert(addKeyActions.find(_.id == KeyId("comm-42")).get.publicKeyData)( isSubtype[PublicKeyData.ECCompressedKeyData]( hasField[PublicKeyData.ECCompressedKeyData, Int]("data", _.data.toByteArray.length, equalTo(32)) && hasField("crv", _.crv, equalTo(EllipticCurve.X25519)) diff --git a/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/model/error/CredentialServiceError.scala b/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/model/error/CredentialServiceError.scala index 2c5950dfbc..09d221ed25 100644 --- a/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/model/error/CredentialServiceError.scala +++ b/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/model/error/CredentialServiceError.scala @@ -5,6 +5,7 @@ import org.hyperledger.identus.castor.core.model.did.{PrismDID, VerificationRela import org.hyperledger.identus.pollux.core.model.DidCommID import org.hyperledger.identus.pollux.core.model.IssueCredentialRecord.ProtocolState import org.hyperledger.identus.shared.models.{Failure, StatusCode} +import org.hyperledger.identus.shared.models.KeyId import java.util.UUID @@ -97,7 +98,7 @@ object CredentialServiceError { s"The requested DID does not exist in the wallet: did=${did.toString}" ) - final case class KeyPairNotFoundInWallet(did: PrismDID, keyId: String, algo: String) + final case class KeyPairNotFoundInWallet(did: PrismDID, keyId: KeyId, algo: String) extends CredentialServiceError( StatusCode.NotFound, s"The requested key pair does not exist in the wallet: did=${did.toString}, keyId=$keyId, algo=$algo" diff --git a/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/CredentialService.scala b/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/CredentialService.scala index 166047ceba..e1341a3dbb 100644 --- a/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/CredentialService.scala +++ b/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/CredentialService.scala @@ -25,6 +25,7 @@ trait CredentialService { def createJWTIssueCredentialRecord( pairwiseIssuerDID: DidId, pairwiseHolderDID: Option[DidId], + kidIssuer: Option[KeyId], thid: DidCommID, maybeSchemaId: Option[String], claims: io.circe.Json, @@ -40,6 +41,7 @@ trait CredentialService { def createSDJWTIssueCredentialRecord( pairwiseIssuerDID: DidId, pairwiseHolderDID: Option[DidId], + kidIssuer: Option[KeyId], thid: DidCommID, maybeSchemaId: Option[String], claims: io.circe.Json, diff --git a/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/CredentialServiceImpl.scala b/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/CredentialServiceImpl.scala index d83b8390ec..ec86a7fa87 100644 --- a/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/CredentialServiceImpl.scala +++ b/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/CredentialServiceImpl.scala @@ -26,7 +26,7 @@ import org.hyperledger.identus.pollux.core.repository.{CredentialRepository, Cre import org.hyperledger.identus.pollux.prex.{ClaimFormat, Jwt, PresentationDefinition} import org.hyperledger.identus.pollux.sdjwt.* import org.hyperledger.identus.pollux.vc.jwt.{Issuer as JwtIssuer, *} -import org.hyperledger.identus.shared.crypto.{Ed25519KeyPair, Ed25519PublicKey, Secp256k1KeyPair} +import org.hyperledger.identus.shared.crypto.{Ed25519KeyPair, Secp256k1KeyPair} import org.hyperledger.identus.shared.http.{DataUrlResolver, GenericUriResolver} import org.hyperledger.identus.shared.models.* import org.hyperledger.identus.shared.utils.aspects.CustomMetricsAspect @@ -125,6 +125,7 @@ class CredentialServiceImpl( private def createIssueCredentialRecord( pairwiseIssuerDID: DidId, + kidIssuer: Option[KeyId], thid: DidCommID, schemaUri: Option[String], validityPeriod: Option[Double], @@ -167,7 +168,7 @@ class CredentialServiceImpl( invitation = invitation, role = IssueCredentialRecord.Role.Issuer, subjectId = None, - keyId = None, + keyId = kidIssuer, validityPeriod = validityPeriod, automaticIssuance = automaticIssuance, protocolState = invitation.fold(IssueCredentialRecord.ProtocolState.OfferPending)(_ => @@ -193,6 +194,7 @@ class CredentialServiceImpl( override def createJWTIssueCredentialRecord( pairwiseIssuerDID: DidId, pairwiseHolderDID: Option[DidId], + kidIssuer: Option[KeyId], thid: DidCommID, maybeSchemaId: Option[String], claims: Json, @@ -219,6 +221,7 @@ class CredentialServiceImpl( ) record <- createIssueCredentialRecord( pairwiseIssuerDID = pairwiseIssuerDID, + kidIssuer = kidIssuer, thid = thid, schemaUri = maybeSchemaId, validityPeriod = validityPeriod, @@ -239,6 +242,7 @@ class CredentialServiceImpl( override def createSDJWTIssueCredentialRecord( pairwiseIssuerDID: DidId, pairwiseHolderDID: Option[DidId], + kidIssuer: Option[KeyId], thid: DidCommID, maybeSchemaId: Option[String], claims: io.circe.Json, @@ -265,6 +269,7 @@ class CredentialServiceImpl( ) record <- createIssueCredentialRecord( pairwiseIssuerDID = pairwiseIssuerDID, + kidIssuer = kidIssuer, thid = thid, schemaUri = maybeSchemaId, validityPeriod = validityPeriod, @@ -313,6 +318,7 @@ class CredentialServiceImpl( ) record <- createIssueCredentialRecord( pairwiseIssuerDID = pairwiseIssuerDID, + kidIssuer = None, thid = thid, schemaUri = Some(credentialDefinition.schemaId), validityPeriod = validityPeriod, @@ -532,7 +538,7 @@ class CredentialServiceImpl( did: PrismDID, verificationRelationship: VerificationRelationship, ellipticCurve: EllipticCurve - ): UIO[String] = { + ): UIO[KeyId] = { for { maybeDidData <- didService .resolveDID(did) @@ -612,7 +618,7 @@ class CredentialServiceImpl( JwtIssuer( jwtIssuerDID.did, EdSigner(ed25519keyPair, keyId), - Ed25519PublicKey.toJavaEd25519PublicKey(ed25519keyPair.publicKey.getEncoded) + ed25519keyPair.publicKey.toJava ) } } diff --git a/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/CredentialServiceNotifier.scala b/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/CredentialServiceNotifier.scala index d2dd0e0c5a..5046688d45 100644 --- a/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/CredentialServiceNotifier.scala +++ b/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/CredentialServiceNotifier.scala @@ -26,6 +26,7 @@ class CredentialServiceNotifier( override def createJWTIssueCredentialRecord( pairwiseIssuerDID: DidId, pairwiseHolderDID: Option[DidId], + kidIssuer: Option[KeyId], thid: DidCommID, maybeSchemaId: Option[String], claims: Json, @@ -41,6 +42,7 @@ class CredentialServiceNotifier( svc.createJWTIssueCredentialRecord( pairwiseIssuerDID, pairwiseHolderDID, + kidIssuer, thid, maybeSchemaId, claims, @@ -57,6 +59,7 @@ class CredentialServiceNotifier( override def createSDJWTIssueCredentialRecord( pairwiseIssuerDID: DidId, pairwiseHolderDID: Option[DidId], + kidIssuer: Option[KeyId], thid: DidCommID, maybeSchemaId: Option[String], claims: io.circe.Json, @@ -72,6 +75,7 @@ class CredentialServiceNotifier( svc.createSDJWTIssueCredentialRecord( pairwiseIssuerDID, pairwiseHolderDID, + kidIssuer, thid, maybeSchemaId, claims, diff --git a/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/service/CredentialServiceSpecHelper.scala b/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/service/CredentialServiceSpecHelper.scala index e8d9fcf907..4f61c5e123 100644 --- a/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/service/CredentialServiceSpecHelper.scala +++ b/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/service/CredentialServiceSpecHelper.scala @@ -128,6 +128,7 @@ trait CredentialServiceSpecHelper { record <- svc.createJWTIssueCredentialRecord( pairwiseIssuerDID = pairwiseIssuerDID, pairwiseHolderDID = pairwiseHolderDID, + kidIssuer = None, thid = thid, maybeSchemaId = maybeSchemaId, claims = claims, diff --git a/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/service/MockCredentialService.scala b/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/service/MockCredentialService.scala index a33dc064f7..41b7b472bc 100644 --- a/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/service/MockCredentialService.scala +++ b/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/service/MockCredentialService.scala @@ -128,6 +128,7 @@ object MockCredentialService extends Mock[CredentialService] { override def createJWTIssueCredentialRecord( pairwiseIssuerDID: DidId, pairwiseHolderDID: Option[DidId], + kidIssuer: Option[KeyId], thid: DidCommID, maybeSchemaId: Option[String], claims: Json, @@ -158,6 +159,7 @@ object MockCredentialService extends Mock[CredentialService] { override def createSDJWTIssueCredentialRecord( pairwiseIssuerDID: DidId, pairwiseHolderDID: Option[DidId], + kidIssuer: Option[KeyId], thid: DidCommID, maybeSchemaId: Option[String], claims: Json, diff --git a/shared/core/src/main/scala/org/hyperledger/identus/shared/models/KeyId.scala b/shared/core/src/main/scala/org/hyperledger/identus/shared/models/KeyId.scala index 67f686971f..f6cbc60efe 100644 --- a/shared/core/src/main/scala/org/hyperledger/identus/shared/models/KeyId.scala +++ b/shared/core/src/main/scala/org/hyperledger/identus/shared/models/KeyId.scala @@ -1,6 +1,10 @@ package org.hyperledger.identus.shared.models +import zio.json._ + opaque type KeyId = String object KeyId: def apply(value: String): KeyId = value extension (id: KeyId) def value: String = id + given decoder: JsonDecoder[KeyId] = JsonDecoder.string.map(KeyId(_)) + given encoder: JsonEncoder[KeyId] = JsonEncoder.string.contramap[KeyId](_.value) diff --git a/shared/crypto/src/main/scala/org/hyperledger/identus/shared/crypto/Apollo.scala b/shared/crypto/src/main/scala/org/hyperledger/identus/shared/crypto/Apollo.scala index ccade3471b..b7e674c212 100644 --- a/shared/crypto/src/main/scala/org/hyperledger/identus/shared/crypto/Apollo.scala +++ b/shared/crypto/src/main/scala/org/hyperledger/identus/shared/crypto/Apollo.scala @@ -163,6 +163,7 @@ trait Ed25519PublicKey extends PublicKey, Verifiable { case _ => false } + def toJava = Ed25519PublicKey.toJavaEd25519PublicKey(this.getEncoded) } trait Ed25519PrivateKey extends PrivateKey, Signable { type Pub = Ed25519PublicKey