diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/oid4vci/service/OIDCCredentialIssuerService.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/oid4vci/service/OIDCCredentialIssuerService.scala index d54c21dafa..18a66b69b5 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/oid4vci/service/OIDCCredentialIssuerService.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/oid4vci/service/OIDCCredentialIssuerService.scala @@ -24,6 +24,7 @@ import org.hyperledger.identus.pollux.vc.jwt.{ W3cCredentialPayload, * } +import org.hyperledger.identus.pollux.vc.jwt.DID.* import org.hyperledger.identus.shared.models.* import zio.* @@ -193,7 +194,7 @@ case class OIDCCredentialIssuerServiceImpl( `type` = Set( "VerifiableCredential" ) ++ credentialDefinition.`type`, // TODO: This information should come from Schema registry by record.schemaId - issuer = issuerDid, + issuer = Left(issuerDid.value), issuanceDate = Instant.now(), maybeExpirationDate = None, // TODO: Add expiration date maybeCredentialSchema = None, // TODO: Add schema from schema registry diff --git a/cloud-agent/service/server/src/test/scala/org/hyperledger/identus/verification/controller/VcVerificationControllerImplSpec.scala b/cloud-agent/service/server/src/test/scala/org/hyperledger/identus/verification/controller/VcVerificationControllerImplSpec.scala index 38b7f1385c..935834c65e 100644 --- a/cloud-agent/service/server/src/test/scala/org/hyperledger/identus/verification/controller/VcVerificationControllerImplSpec.scala +++ b/cloud-agent/service/server/src/test/scala/org/hyperledger/identus/verification/controller/VcVerificationControllerImplSpec.scala @@ -8,6 +8,7 @@ import org.hyperledger.identus.castor.core.service.MockDIDService import org.hyperledger.identus.iam.authentication.AuthenticatorWithAuthZ import org.hyperledger.identus.pollux.vc.jwt.* import org.hyperledger.identus.pollux.vc.jwt.CredentialPayload.Implicits.* +import org.hyperledger.identus.pollux.vc.jwt.DID.* import org.hyperledger.identus.verification.controller.http.* import sttp.client3.{basicRequest, DeserializationException, Response, UriContext} import sttp.client3.ziojson.* @@ -36,7 +37,7 @@ object VcVerificationControllerImplSpec extends ZIOSpecDefault with VcVerificati `@context` = Set("https://www.w3.org/2018/credentials/v1", "https://www.w3.org/2018/credentials/examples/v1"), maybeId = Some("http://example.edu/credentials/3732"), `type` = Set("VerifiableCredential", "UniversityDegreeCredential"), - issuer = issuer.did, + issuer = Left(issuer.did.value), issuanceDate = Instant.parse("2010-01-01T00:00:00Z"), maybeExpirationDate = Some(Instant.parse("2010-01-12T00:00:00Z")), maybeValidFrom = Some(Instant.parse("2010-01-12T00:00:00Z")), 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 ea153627e7..5f7ae5df19 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,6 +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.pollux.vc.jwt.DID.* import org.hyperledger.identus.shared.crypto.{Ed25519KeyPair, Ed25519PublicKey, Secp256k1KeyPair} import org.hyperledger.identus.shared.http.{DataUrlResolver, GenericUriResolver} import org.hyperledger.identus.shared.models.* @@ -1134,7 +1135,7 @@ class CredentialServiceImpl( maybeId = None, `type` = Set("VerifiableCredential"), // TODO: This information should come from Schema registry by record.schemaId - issuer = jwtIssuer.did, + issuer = Left(jwtIssuer.did.value), issuanceDate = issuanceDate, maybeExpirationDate = record.validityPeriod.map(sec => issuanceDate.plusSeconds(sec.toLong)), maybeCredentialSchema = diff --git a/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/service/verification/VcVerificationServiceImplSpec.scala b/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/service/verification/VcVerificationServiceImplSpec.scala index b85ab949b3..a4ed502eb2 100644 --- a/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/service/verification/VcVerificationServiceImplSpec.scala +++ b/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/service/verification/VcVerificationServiceImplSpec.scala @@ -7,6 +7,7 @@ import org.hyperledger.identus.castor.core.service.MockDIDService import org.hyperledger.identus.pollux.core.service.ResourceURIDereferencerImpl import org.hyperledger.identus.pollux.vc.jwt.* import org.hyperledger.identus.pollux.vc.jwt.CredentialPayload.Implicits.* +import org.hyperledger.identus.pollux.vc.jwt.DID.* import org.hyperledger.identus.shared.models.{WalletAccessContext, WalletId} import zio.* import zio.test.* @@ -27,7 +28,7 @@ object VcVerificationServiceImplSpec extends ZIOSpecDefault with VcVerificationS Set("https://www.w3.org/2018/credentials/v1", "https://www.w3.org/2018/credentials/examples/v1"), maybeId = Some("http://example.edu/credentials/3732"), `type` = Set("VerifiableCredential", "UniversityDegreeCredential"), - issuer = issuer.did, + issuer = Left(issuer.did.value), issuanceDate = Instant.parse("2010-01-01T00:00:00Z"), maybeExpirationDate = Some(Instant.parse("2010-01-12T00:00:00Z")), maybeValidFrom = Some(Instant.parse("2010-01-12T00:00:00Z")), @@ -93,7 +94,7 @@ object VcVerificationServiceImplSpec extends ZIOSpecDefault with VcVerificationS Set("https://www.w3.org/2018/credentials/v1", "https://www.w3.org/2018/credentials/examples/v1"), maybeId = Some("http://example.edu/credentials/3732"), `type` = Set("VerifiableCredential", "UniversityDegreeCredential"), - issuer = issuer.did, + issuer = Left(issuer.did.value), issuanceDate = Instant.parse("2010-01-01T00:00:00Z"), maybeExpirationDate = Some(Instant.parse("2010-01-12T00:00:00Z")), maybeValidFrom = Some(Instant.parse("2010-01-12T00:00:00Z")), @@ -158,7 +159,7 @@ object VcVerificationServiceImplSpec extends ZIOSpecDefault with VcVerificationS Set("https://www.w3.org/2018/credentials/v1", "https://www.w3.org/2018/credentials/examples/v1"), maybeId = Some("http://example.edu/credentials/3732"), `type` = Set("VerifiableCredential", "UniversityDegreeCredential"), - issuer = issuer.did, + issuer = Left(issuer.did.value), issuanceDate = Instant.parse("2010-01-01T00:00:00Z"), maybeExpirationDate = Some(Instant.parse("2010-01-12T00:00:00Z")), maybeValidFrom = Some(Instant.parse("2010-01-12T00:00:00Z")), @@ -223,7 +224,7 @@ object VcVerificationServiceImplSpec extends ZIOSpecDefault with VcVerificationS Set("https://www.w3.org/2018/credentials/v1", "https://www.w3.org/2018/credentials/examples/v1"), maybeId = Some("http://example.edu/credentials/3732"), `type` = Set("VerifiableCredential", "UniversityDegreeCredential"), - issuer = issuer.did, + issuer = Left(issuer.did.value), issuanceDate = Instant.parse("2010-01-01T00:00:00Z"), maybeExpirationDate = Some(Instant.parse("2010-01-12T00:00:00Z")), maybeValidFrom = Some(Instant.parse("2010-01-12T00:00:00Z")), @@ -293,7 +294,7 @@ object VcVerificationServiceImplSpec extends ZIOSpecDefault with VcVerificationS Set("https://www.w3.org/2018/credentials/v1", "https://www.w3.org/2018/credentials/examples/v1"), maybeId = Some("http://example.edu/credentials/3732"), `type` = Set("VerifiableCredential", "UniversityDegreeCredential"), - issuer = issuer.did, + issuer = Left(issuer.did.value), issuanceDate = Instant.parse("2010-01-01T00:00:00Z"), maybeExpirationDate = Some(Instant.parse("2010-01-12T00:00:00Z")), maybeValidFrom = Some(Instant.parse("2010-01-12T00:00:00Z")), @@ -363,7 +364,7 @@ object VcVerificationServiceImplSpec extends ZIOSpecDefault with VcVerificationS Set("https://www.w3.org/2018/credentials/v1", "https://www.w3.org/2018/credentials/examples/v1"), maybeId = Some("http://example.edu/credentials/3732"), `type` = Set("VerifiableCredential", "UniversityDegreeCredential"), - issuer = issuer.did, + issuer = Left(issuer.did.value), issuanceDate = Instant.parse("2010-01-01T00:00:00Z"), maybeExpirationDate = Some(Instant.parse("2010-01-12T00:00:00Z")), maybeValidFrom = Some(Instant.parse("2010-01-12T00:00:00Z")), @@ -429,7 +430,7 @@ object VcVerificationServiceImplSpec extends ZIOSpecDefault with VcVerificationS Set("https://www.w3.org/2018/credentials/v1", "https://www.w3.org/2018/credentials/examples/v1"), maybeId = Some("http://example.edu/credentials/3732"), `type` = Set("VerifiableCredential", "UniversityDegreeCredential"), - issuer = issuer.did, + issuer = Left(issuer.did.value), issuanceDate = Instant.parse("2010-01-01T00:00:00Z"), maybeExpirationDate = Some(Instant.parse("2010-01-12T00:00:00Z")), maybeValidFrom = Some(Instant.parse("2010-01-12T00:00:00Z")), @@ -495,7 +496,7 @@ object VcVerificationServiceImplSpec extends ZIOSpecDefault with VcVerificationS Set("https://www.w3.org/2018/credentials/v1", "https://www.w3.org/2018/credentials/examples/v1"), maybeId = Some("http://example.edu/credentials/3732"), `type` = Set("VerifiableCredential", "UniversityDegreeCredential"), - issuer = issuer.did, + issuer = Left(issuer.did.value), issuanceDate = Instant.parse("2010-01-01T00:00:00Z"), maybeExpirationDate = Some(Instant.parse("2010-01-12T00:00:00Z")), maybeValidFrom = Some(Instant.parse("2010-01-12T00:00:00Z")), @@ -561,7 +562,7 @@ object VcVerificationServiceImplSpec extends ZIOSpecDefault with VcVerificationS Set("https://www.w3.org/2018/credentials/v1", "https://www.w3.org/2018/credentials/examples/v1"), maybeId = Some("http://example.edu/credentials/3732"), `type` = Set("VerifiableCredential", "UniversityDegreeCredential"), - issuer = issuer.did, + issuer = Left(issuer.did.value), issuanceDate = Instant.parse("2010-01-01T00:00:00Z"), maybeExpirationDate = Some(Instant.parse("2010-01-12T00:00:00Z")), maybeValidFrom = Some(Instant.parse("2010-01-12T00:00:00Z")), diff --git a/pollux/vc-jwt/src/main/scala/org/hyperledger/identus/pollux/vc/jwt/VerifiableCredentialPayload.scala b/pollux/vc-jwt/src/main/scala/org/hyperledger/identus/pollux/vc/jwt/VerifiableCredentialPayload.scala index e464b5ab70..8c42a2225c 100644 --- a/pollux/vc-jwt/src/main/scala/org/hyperledger/identus/pollux/vc/jwt/VerifiableCredentialPayload.scala +++ b/pollux/vc-jwt/src/main/scala/org/hyperledger/identus/pollux/vc/jwt/VerifiableCredentialPayload.scala @@ -64,6 +64,8 @@ case class CredentialSchema( `type`: String ) +case class CredentialIssuer(id: String) + sealed trait CredentialPayload { def maybeSub: Option[String] @@ -83,7 +85,7 @@ sealed trait CredentialPayload { def maybeValidUntil: Option[Instant] - def iss: String + def issuer: Either[String, CredentialIssuer] def maybeCredentialStatus: Option[CredentialStatus] @@ -99,7 +101,7 @@ sealed trait CredentialPayload { def toJwtCredentialPayload: JwtCredentialPayload = JwtCredentialPayload( - iss = iss, + iss = issuer.fold(identity, _.id), maybeSub = maybeSub, vc = JwtVc( `@context` = `@context`, @@ -111,7 +113,8 @@ sealed trait CredentialPayload { maybeEvidence = maybeEvidence, maybeTermsOfUse = maybeTermsOfUse, maybeValidFrom = maybeValidFrom, - maybeValidUntil = maybeValidUntil + maybeValidUntil = maybeValidUntil, + maybeIssuer = Some(issuer), ), nbf = nbf, aud = aud, @@ -124,7 +127,7 @@ sealed trait CredentialPayload { `@context` = `@context`, maybeId = maybeJti, `type` = `type`, - issuer = DID(iss), + issuer = issuer, issuanceDate = nbf, maybeExpirationDate = maybeExp, maybeCredentialSchema = maybeCredentialSchema, @@ -146,6 +149,7 @@ case class JwtVc( credentialSubject: Json, maybeValidFrom: Option[Instant], maybeValidUntil: Option[Instant], + maybeIssuer: Option[Either[String, CredentialIssuer]], maybeCredentialStatus: Option[CredentialStatus], maybeRefreshService: Option[RefreshService], maybeEvidence: Option[Json], @@ -153,7 +157,7 @@ case class JwtVc( ) case class JwtCredentialPayload( - override val iss: String, + iss: String, override val maybeSub: Option[String], vc: JwtVc, override val nbf: Instant, @@ -171,13 +175,14 @@ case class JwtCredentialPayload( override val credentialSubject = vc.credentialSubject override val maybeValidFrom = vc.maybeValidFrom override val maybeValidUntil = vc.maybeValidUntil + override val issuer = vc.maybeIssuer.getOrElse(Left(iss)) } case class W3cCredentialPayload( override val `@context`: Set[String], override val `type`: Set[String], maybeId: Option[String], - issuer: DID, + issuer: Either[String, CredentialIssuer], issuanceDate: Instant, maybeExpirationDate: Option[Instant], override val maybeCredentialSchema: Option[CredentialSchema], @@ -194,7 +199,6 @@ case class W3cCredentialPayload( override val maybeJti = maybeId override val nbf = issuanceDate override val maybeExp = maybeExpirationDate - override val iss = issuer.value } object CredentialPayload { @@ -222,6 +226,13 @@ object CredentialPayload { ("type", credentialSchema.`type`.asJson) ) + implicit val credentialIssuerEncoder: Encoder[CredentialIssuer] = + (credentialIssuer: CredentialIssuer) => + Json + .obj( + ("id", credentialIssuer.id.asJson) + ) + implicit val credentialStatusPurposeEncoder: Encoder[StatusPurpose] = (a: StatusPurpose) => a.toString.asJson implicit val credentialStatusEncoder: Encoder[CredentialStatus] = @@ -235,6 +246,14 @@ object CredentialPayload { ("statusListCredential", credentialStatus.statusListCredential.asJson) ) + implicit val eitherStringOrCredentialIssuerEncoder: Encoder[Either[String, CredentialIssuer]] = { + case Left(value) => Json.fromString(value) + case Right(issuer) => issuer.asJson + } + + implicit val eitherStringOrCredentialIssuerDecoder: Decoder[Either[String, CredentialIssuer]] = + Decoder[String].map(Left(_)).or(Decoder[CredentialIssuer].map(Right(_))) + implicit val w3cCredentialPayloadEncoder: Encoder[W3cCredentialPayload] = (w3cCredentialPayload: W3cCredentialPayload) => Json @@ -272,7 +291,8 @@ object CredentialPayload { ("evidence", jwtVc.maybeEvidence.asJson), ("termsOfUse", jwtVc.maybeTermsOfUse.asJson), ("validFrom", jwtVc.maybeValidFrom.asJson), - ("validUntil", jwtVc.maybeValidUntil.asJson) + ("validUntil", jwtVc.maybeValidUntil.asJson), + ("issuer", jwtVc.maybeIssuer.asJson) ) .deepDropNullValues .dropEmptyValues @@ -324,6 +344,14 @@ object CredentialPayload { CredentialSchema(id = id, `type` = `type`) } + implicit val credentialIssuerDecoder: Decoder[CredentialIssuer] = + (c: HCursor) => + for { + id <- c.downField("id").as[String] + } yield { + CredentialIssuer(id = id) + } + implicit val credentialStatusPurposeDecoder: Decoder[StatusPurpose] = (c: HCursor) => Decoder.decodeString(c).flatMap { str => Try(StatusPurpose.valueOf(str)).toEither.leftMap { _ => @@ -361,7 +389,7 @@ object CredentialPayload { .as[Set[String]] .orElse(c.downField("type").as[String].map(Set(_))) maybeId <- c.downField("id").as[Option[String]] - issuer <- c.downField("issuer").as[String] + issuer <- c.downField("issuer").as[Either[String, CredentialIssuer]] issuanceDate <- c.downField("issuanceDate").as[Instant] maybeExpirationDate <- c.downField("expirationDate").as[Option[Instant]] maybeValidFrom <- c.downField("validFrom").as[Option[Instant]] @@ -377,7 +405,7 @@ object CredentialPayload { `@context` = `@context`, `type` = `type`, maybeId = maybeId, - issuer = DID(issuer), + issuer = issuer, issuanceDate = issuanceDate, maybeExpirationDate = maybeExpirationDate, maybeValidFrom = maybeValidFrom, @@ -411,6 +439,7 @@ object CredentialPayload { maybeTermsOfUse <- c.downField("termsOfUse").as[Option[Json]] maybeValidFrom <- c.downField("validFrom").as[Option[Instant]] maybeValidUntil <- c.downField("validUntil").as[Option[Instant]] + maybeIssuer <- c.downField("issuer").as[Option[Either[String, CredentialIssuer]]] } yield { JwtVc( `@context` = `@context`, @@ -423,6 +452,7 @@ object CredentialPayload { maybeTermsOfUse = maybeTermsOfUse, maybeValidFrom = maybeValidFrom, maybeValidUntil = maybeValidUntil, + maybeIssuer = maybeIssuer ) } @@ -853,7 +883,7 @@ object W3CCredential { )(didResolver: DidResolver): IO[String, Validation[String, Unit]] = { JWTVerification.validateEncodedJwt(payload.proof.jwt, proofPurpose)(didResolver: DidResolver)(claim => Validation.fromEither(decode[W3cCredentialPayload](claim).left.map(_.toString)) - )(_.issuer.value) + )(_.issuer.fold(identity, _.id)) } def verifyDates(w3cPayload: W3cVerifiableCredentialPayload, leeway: TemporalAmount)(implicit diff --git a/pollux/vc-jwt/src/main/scala/org/hyperledger/identus/pollux/vc/jwt/revocation/VCStatusList2021.scala b/pollux/vc-jwt/src/main/scala/org/hyperledger/identus/pollux/vc/jwt/revocation/VCStatusList2021.scala index 9146320c44..4e580e2fbd 100644 --- a/pollux/vc-jwt/src/main/scala/org/hyperledger/identus/pollux/vc/jwt/revocation/VCStatusList2021.scala +++ b/pollux/vc-jwt/src/main/scala/org/hyperledger/identus/pollux/vc/jwt/revocation/VCStatusList2021.scala @@ -4,6 +4,7 @@ import io.circe.{Json, JsonObject} import io.circe.syntax.* import org.hyperledger.identus.pollux.vc.jwt.* import org.hyperledger.identus.pollux.vc.jwt.revocation.VCStatusList2021Error.{DecodingError, EncodingError} +import org.hyperledger.identus.pollux.vc.jwt.DID.* import zio.* import java.time.Instant @@ -61,7 +62,7 @@ object VCStatusList2021 { ), maybeId = Some(vcId), `type` = Set("VerifiableCredential", "StatusList2021Credential"), - issuer = jwtIssuer.did, + issuer = Left(jwtIssuer.did.value), issuanceDate = Instant.now, maybeExpirationDate = None, maybeCredentialSchema = None, diff --git a/pollux/vc-jwt/src/test/scala/org/hyperledger/identus/pollux/vc/jwt/JWTVerificationTest.scala b/pollux/vc-jwt/src/test/scala/org/hyperledger/identus/pollux/vc/jwt/JWTVerificationTest.scala index 3f05de0c35..eae806f15b 100644 --- a/pollux/vc-jwt/src/test/scala/org/hyperledger/identus/pollux/vc/jwt/JWTVerificationTest.scala +++ b/pollux/vc-jwt/src/test/scala/org/hyperledger/identus/pollux/vc/jwt/JWTVerificationTest.scala @@ -80,7 +80,8 @@ object JWTVerificationTest extends ZIOSpecDefault { maybeEvidence = None, maybeTermsOfUse = None, maybeValidFrom = Some(validFrom), - maybeValidUntil = Some(validUntil) + maybeValidUntil = Some(validUntil), + maybeIssuer = Some(Left(issuer.issuer.did.value)) ), nbf = jwtCredentialNbf, // ISSUANCE DATE aud = Set.empty,