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 fa66430eca..e416ad3c8c 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 @@ -840,25 +840,25 @@ object PresentBackgroundJobs extends BackgroundJobsHelper { val base64Decoded = new String(java.util.Base64.getDecoder.decode(data)) val verifiedClaims = for { sdJwtPresentationPayload <- ZIO.fromEither(base64Decoded.fromJson[SdJwtPresentationPayload]) - iss <- ZIO.fromEither(sdJwtPresentationPayload.presentation.iss) + iss <- ZIO.fromEither(sdJwtPresentationPayload.iss) ed25519PublicKey <- resolveToEd25519PublicKey(iss) - verifiedClaims = SDJWT.getVerifiedClaims( + ret = SDJWT.getVerifiedClaims( IssuerPublicKey(ed25519PublicKey), - sdJwtPresentationPayload.presentation, - sdJwtPresentationPayload.claimsToDisclose.toJson + sdJwtPresentationPayload, ) - _ <- ZIO.logInfo(s"ClaimsValidationResult: $verifiedClaims") - _ <- ZIO.logInfo(s"ClaimsValidationResult: ${sdJwtPresentationPayload.claimsToDisclose}") - result: SDJWT.ClaimsValidationResult = - verifiedClaims match { - case validClaims: SDJWT.ValidClaims => - validClaims.verifyDiscoseClaims( - sdJwtPresentationPayload.claimsToDisclose.asObject.getOrElse(Json.Obj()) - ) - case validAnyMatch: SDJWT.ValidAnyMatch.type => validAnyMatch - case invalid: SDJWT.Invalid => invalid - } - } yield result + _ <- ZIO.logInfo(s"ClaimsValidationResult: $ret") + // FIXME REMOVE cleanup + // _ <- ZIO.logInfo(s"ClaimsValidationResult: ${sdJwtPresentationPayload.claimsToDisclose}") + // result: SDJWT.ClaimsValidationResult = + // verifiedClaims match { + // case validClaims: SDJWT.ValidClaims => + // validClaims.claims // This is all claims + // // TODO + // // .verifyDiscoseClaims(sdJwtPresentationPayload.claimsToDisclose.asObject.getOrElse(Json.Obj())) + // case validAnyMatch: SDJWT.ValidAnyMatch.type => validAnyMatch + // case invalid: SDJWT.Invalid => invalid + // } + } yield ret verifiedClaims .mapError(error => UnexpectedError( diff --git a/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/model/presentation/SdJwtPresentationPayload.scala b/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/model/presentation/SdJwtPresentationPayload.scala index ea834776f1..58959a66a8 100644 --- a/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/model/presentation/SdJwtPresentationPayload.scala +++ b/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/model/presentation/SdJwtPresentationPayload.scala @@ -1,16 +1,18 @@ package org.hyperledger.identus.pollux.core.model.presentation -import org.hyperledger.identus.pollux.sdjwt.PresentationJson +import org.hyperledger.identus.pollux.sdjwt.PresentationCompact import zio.json.* -case class SdJwtPresentationPayload( - claimsToDisclose: ast.Json.Obj, - presentation: PresentationJson, - options: Option[Options] -) -object SdJwtPresentationPayload { - given JsonDecoder[Options] = DeriveJsonDecoder.gen[Options] - given JsonEncoder[Options] = DeriveJsonEncoder.gen[Options] - given JsonDecoder[SdJwtPresentationPayload] = DeriveJsonDecoder.gen[SdJwtPresentationPayload] - given JsonEncoder[SdJwtPresentationPayload] = DeriveJsonEncoder.gen[SdJwtPresentationPayload] -} +//FIXME +type SdJwtPresentationPayload = PresentationCompact +// case class SdJwtPresentationPayload( +// claimsToDisclose: ast.Json.Obj, +// presentation: PresentationCompact, +// options: Option[Options] +// ) +// object SdJwtPresentationPayload { +// given JsonDecoder[Options] = DeriveJsonDecoder.gen[Options] +// given JsonEncoder[Options] = DeriveJsonEncoder.gen[Options] +// given JsonDecoder[SdJwtPresentationPayload] = DeriveJsonDecoder.gen[SdJwtPresentationPayload] +// given JsonEncoder[SdJwtPresentationPayload] = DeriveJsonEncoder.gen[SdJwtPresentationPayload] +// } 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 7f77f74fc2..c23c9cfb08 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 @@ -1381,7 +1381,7 @@ private class CredentialServiceImpl( fromDID = issue.from, toDID = issue.to, thid = issue.thid, - credentials = Seq(IssueCredentialIssuedFormat.SDJWT -> credential.value.getBytes) + credentials = Seq(IssueCredentialIssuedFormat.SDJWT -> credential.compact.getBytes) ) record <- markCredentialGenerated(record, issueCredential) } yield record diff --git a/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/PresentationServiceImpl.scala b/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/PresentationServiceImpl.scala index 7ffb6a1326..163acbf6af 100644 --- a/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/PresentationServiceImpl.scala +++ b/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/PresentationServiceImpl.scala @@ -16,7 +16,7 @@ import org.hyperledger.identus.pollux.core.model.presentation.{SdJwtPresentation import org.hyperledger.identus.pollux.core.model.schema.`type`.anoncred.AnoncredSchemaSerDesV1 import org.hyperledger.identus.pollux.core.repository.{CredentialRepository, PresentationRepository} import org.hyperledger.identus.pollux.core.service.serdes.* -import org.hyperledger.identus.pollux.sdjwt.{CredentialJson, PresentationJson, SDJWT} +import org.hyperledger.identus.pollux.sdjwt.{CredentialCompact, PresentationCompact, SDJWT} import org.hyperledger.identus.pollux.vc.jwt.* import org.hyperledger.identus.shared.models.WalletAccessContext import org.hyperledger.identus.shared.utils.aspects.CustomMetricsAspect @@ -145,19 +145,13 @@ private class PresentationServiceImpl( ) ) - presentationJson <- createSDJwtPresentationPayloadFromCredential( + presentationCompact <- createSDJwtPresentationPayloadFromCredential( issuedCredentials, sdJwtClaimsToDisclose, requestPresentation, prover ) - presentationPayload <- ZIO.succeed( - SdJwtPresentationPayload( - claimsToDisclose = sdJwtClaimsToDisclose, - presentation = presentationJson, - options = None - ) - ) + presentationPayload <- ZIO.succeed(presentationCompact) } yield presentationPayload } @@ -514,14 +508,16 @@ private class PresentationServiceImpl( claimsToDisclose: SdJwtCredentialToDisclose, requestPresentation: RequestPresentation, prover: Issuer - ): IO[PresentationError, PresentationJson] = { + ): IO[PresentationError, PresentationCompact] = { val verifiableCredentials: Either[ PresentationError.PresentationDecodingError, - Seq[CredentialJson] + Seq[CredentialCompact] ] = issuedCredentials.map { signedCredential => decode[org.hyperledger.identus.mercury.model.Base64](signedCredential) - .flatMap(x => Right(CredentialJson(new String(java.util.Base64.getDecoder.decode(x.base64))))) + .flatMap(x => + Right(CredentialCompact.unsafeFromCompact(new String(java.util.Base64.getDecoder.decode(x.base64)))) + ) .left .map(err => PresentationDecodingError(s"JsonData decoding error: $err")) }.sequence diff --git a/pollux/sd-jwt/src/main/scala/org/hyperledger/identus/pollux/sdjwt/CompactFormat.scala b/pollux/sd-jwt/src/main/scala/org/hyperledger/identus/pollux/sdjwt/CompactFormat.scala new file mode 100644 index 0000000000..89bc18def5 --- /dev/null +++ b/pollux/sd-jwt/src/main/scala/org/hyperledger/identus/pollux/sdjwt/CompactFormat.scala @@ -0,0 +1,95 @@ +package org.hyperledger.identus.pollux.sdjwt + +import zio.json.* + +type Header = String +type Payload = String +type Signature = String +type Disclosure = String +type KBJWT = (String, String, String) + +case class CredentialCompact( + override val jwtHeader: Header, + override val jwtPayload: Payload, + override val jwtSignature: Signature, + override val disclosures: Seq[Disclosure], +) extends ModelsExtensionMethods { + override def kbJWT: Option[KBJWT] = None +} + +object CredentialCompact { + // given encoder: JsonEncoder[CredentialCompact] = DeriveJsonEncoder.gen[CredentialCompact] + // given decoder: JsonDecoder[CredentialCompact] = DeriveJsonDecoder.gen[CredentialCompact] + given decoder: JsonDecoder[CredentialCompact] = JsonDecoder.string.map(CredentialCompact.unsafeFromCompact(_)) + given encoder: JsonEncoder[CredentialCompact] = JsonEncoder.string.contramap[CredentialCompact](_.compact) + + // /** + // * // ~~~...~~ + // * + // * @param value + // * @return + // */ + def unsafeFromCompact(str: String): CredentialCompact = { + import scala.util.matching.Regex + // var str = "hhh.ppp.sss~a~b~xxxx" + val patternCompact = new Regex("(^[\\w-]*)\\.([\\w-]*)\\.([\\w-]*)((?:~(?:[\\w-]*))*)~$") + val patternDisclosure = new Regex("(~([\\w]+))") + + val patternCompact(h, p, s, disclosuresStr) = str: @unchecked + CredentialCompact( + jwtHeader = h, + jwtPayload = p, + jwtSignature = s, + disclosures = patternDisclosure.findAllIn(disclosuresStr).toSeq.map(_.drop(1)), + ) + } +} + +case class PresentationCompact( + override val jwtHeader: Header, + override val jwtPayload: Payload, + override val jwtSignature: Signature, + override val disclosures: Seq[Disclosure], + override val kbJWT: Option[KBJWT] +) extends ModelsExtensionMethods + +object PresentationCompact { + // given encoder: JsonEncoder[PresentationCompact] = DeriveJsonEncoder.gen[PresentationCompact] + // given decoder: JsonDecoder[PresentationCompact] = DeriveJsonDecoder.gen[PresentationCompact] + given decoder: JsonDecoder[PresentationCompact] = JsonDecoder.string.map(PresentationCompact.unsafeFromCompact(_)) + given encoder: JsonEncoder[PresentationCompact] = JsonEncoder.string.contramap[PresentationCompact](_.compact) + + // /** + // * // ~~~...~~ + // * + // * @param value + // * @return + // */ + def unsafeFromCompact(str: String): PresentationCompact = { + import scala.util.matching.Regex + // var str = "hhh.ppp.sss~a~b~xxxx" + val patternCompact = new Regex( + "^([\\w-]*)\\.([\\w-]*)\\.([\\w-]*)((?:~(?:[\\w-]*))*)~(?:([\\w-]*)\\.([\\w-]*)\\.([\\w-]*))?$" + ) + val patternDisclosure = new Regex("(~([\\w]+))") + + val patternCompact(h, p, s, disclosuresStr, kb_h, kb_p, kb_s) = str: @unchecked + + val kbJWT: Option[KBJWT] = ( + Option(kb_h).filterNot(_.isBlank()), + Option(kb_p).filterNot(_.isBlank()), + Option(kb_s).filterNot(_.isBlank()) + ) match + case (None, None, None) => None + case (Some(h), Some(p), Some(s)) => Some((h, p, s)) + case _ => None // FIXME error + + PresentationCompact( + jwtHeader = h, + jwtPayload = p, + jwtSignature = s, + disclosures = patternDisclosure.findAllIn(disclosuresStr).toSeq.map(_.drop(1)), + kbJWT = kbJWT, + ) + } +} diff --git a/pollux/sd-jwt/src/main/scala/org/hyperledger/identus/pollux/sdjwt/Models.scala b/pollux/sd-jwt/src/main/scala/org/hyperledger/identus/pollux/sdjwt/Models.scala index 9181d77177..b7bfee0375 100644 --- a/pollux/sd-jwt/src/main/scala/org/hyperledger/identus/pollux/sdjwt/Models.scala +++ b/pollux/sd-jwt/src/main/scala/org/hyperledger/identus/pollux/sdjwt/Models.scala @@ -87,35 +87,3 @@ object HolderPrivateKey { fromEdPem(data) } } - -opaque type CredentialJson = String -object CredentialJson { - given decoder: JsonDecoder[CredentialJson] = JsonDecoder.string.map(CredentialJson(_)) - given encoder: JsonEncoder[CredentialJson] = JsonEncoder.string.contramap[CredentialJson](_.value) - - def apply(value: String): CredentialJson = value - extension (c: CredentialJson) - def value: String = c - def payload: Either[String, String] = ModelsExtensionMethods.payload(c) - def iss: Either[String, String] = ModelsExtensionMethods.iss(c) - def sub: Either[String, String] = ModelsExtensionMethods.sub(c) - def iat: Either[String, BigDecimal] = ModelsExtensionMethods.iat(c) - def exp: Either[String, BigDecimal] = ModelsExtensionMethods.exp(c) - -} - -opaque type PresentationJson = String -object PresentationJson { - given decoder: JsonDecoder[PresentationJson] = JsonDecoder.string.map(PresentationJson(_)) - given encoder: JsonEncoder[PresentationJson] = JsonEncoder.string.contramap[PresentationJson](_.value) - - def apply(value: String): PresentationJson = value - extension (c: PresentationJson) - def value: String = c - def payload: Either[String, String] = ModelsExtensionMethods.payload(c) - def iss: Either[String, String] = ModelsExtensionMethods.iss(c) - def sub: Either[String, String] = ModelsExtensionMethods.sub(c) - def iat: Either[String, BigDecimal] = ModelsExtensionMethods.iat(c) - def exp: Either[String, BigDecimal] = ModelsExtensionMethods.exp(c) - -} diff --git a/pollux/sd-jwt/src/main/scala/org/hyperledger/identus/pollux/sdjwt/ModelsExtensionMethods.scala b/pollux/sd-jwt/src/main/scala/org/hyperledger/identus/pollux/sdjwt/ModelsExtensionMethods.scala index 230c745aa9..a32237aa20 100644 --- a/pollux/sd-jwt/src/main/scala/org/hyperledger/identus/pollux/sdjwt/ModelsExtensionMethods.scala +++ b/pollux/sd-jwt/src/main/scala/org/hyperledger/identus/pollux/sdjwt/ModelsExtensionMethods.scala @@ -4,67 +4,58 @@ import zio.json.* import java.util.Base64 -private[sdjwt] object ModelsExtensionMethods { - extension (c: String) { - private def asJsonObject: Either[String, ast.Json.Obj] = c +private[sdjwt] trait ModelsExtensionMethods { + + def jwtHeader: Header + def jwtPayload: Payload + def jwtSignature: Signature + def disclosures: Seq[Disclosure] + def kbJWT: Option[KBJWT] + + def compact: String = + jwtHeader + "." + jwtPayload + "." + jwtSignature + + disclosures.map("~" + _).mkString("") + // note the case where disclosures is empty + "~" + kbJWT.map(e => e._1 + "." + e._2 + "." + e._3).getOrElse("") + + def payloadAsJsonObj: Either[String, zio.json.ast.Json.Obj] = + new String(java.util.Base64.getUrlDecoder.decode(jwtPayload)) .fromJson[ast.Json] .map(_.asObject) .flatMap { - case None => Left("PresentationJson must the a Json Object") + case None => Left("The payload in PresentationCompact must the a Json Object") case Some(jsonObj) => Right(jsonObj) } - def payload: Either[String, String] = - asJsonObject.flatMap { - _.get("payload") match - case None => Left("PresentationJson must have the field 'payload'") - case Some(ast.Json.Str(payload)) => - Right( - String( - Base64.getDecoder().decode(payload) // TODO make it safe - ) - ) - case Some(_) => Left("PresentationJson must have the field 'payload' as a Base64 String") - } - private def payloadAsJsonObj: Either[String, zio.json.ast.Json.Obj] = - payload.flatMap { - _.fromJson[ast.Json] - .map(_.asObject) - .flatMap { - case None => Left("The payload in PresentationJson must the a Json Object") - case Some(jsonObj) => Right(jsonObj) - } - } - def iss: Either[String, String] = - payloadAsJsonObj.flatMap { - _.get("iss") match - case None => Left("The payload in PresentationJson must have the field 'iss'") - case Some(ast.Json.Str(iss)) => Right(iss) - case Some(_) => Left("PresentationJson must have the field 'iss' as a String") - } + def iss: Either[String, String] = + payloadAsJsonObj.flatMap { + _.get("iss") match + case None => Left("The payload in PresentationCompact must have the field 'iss'") + case Some(ast.Json.Str(iss)) => Right(iss) + case Some(_) => Left("PresentationCompact must have the field 'iss' as a String") + } - def sub: Either[String, String] = - payloadAsJsonObj.flatMap { - _.get("sub") match - case None => Left("The payload in PresentationJson must have the field 'sub'") - case Some(ast.Json.Str(sub)) => Right(sub) - case Some(_) => Left("PresentationJson must have the field 'sub' as a String") - } + def sub: Either[String, String] = + payloadAsJsonObj.flatMap { + _.get("sub") match + case None => Left("The payload in PresentationCompact must have the field 'sub'") + case Some(ast.Json.Str(sub)) => Right(sub) + case Some(_) => Left("PresentationCompact must have the field 'sub' as a String") + } - def iat: Either[String, BigDecimal] = - payloadAsJsonObj.flatMap { - _.get("iat") match - case None => Left("The payload in PresentationJson must have the field 'iat'") - case Some(ast.Json.Num(iat)) => Right(iat) - case Some(_) => Left("PresentationJson must have the field 'iat' as a Num") - } + def iat: Either[String, BigDecimal] = + payloadAsJsonObj.flatMap { + _.get("iat") match + case None => Left("The payload in PresentationCompact must have the field 'iat'") + case Some(ast.Json.Num(iat)) => Right(iat) + case Some(_) => Left("PresentationCompact must have the field 'iat' as a Num") + } + + def exp: Either[String, BigDecimal] = + payloadAsJsonObj.flatMap { + _.get("exp") match + case None => Left("The payload in PresentationCompact must have the field 'exp'") + case Some(ast.Json.Num(exp)) => Right(exp) + case Some(_) => Left("PresentationCompact must have the field 'exp' as a Num") + } - def exp: Either[String, BigDecimal] = - payloadAsJsonObj.flatMap { - _.get("exp") match - case None => Left("The payload in PresentationJson must have the field 'exp'") - case Some(ast.Json.Num(exp)) => Right(exp) - case Some(_) => Left("PresentationJson must have the field 'exp' as a Num") - } - } } diff --git a/pollux/sd-jwt/src/main/scala/org/hyperledger/identus/pollux/sdjwt/SDJWT.scala b/pollux/sd-jwt/src/main/scala/org/hyperledger/identus/pollux/sdjwt/SDJWT.scala index ca0124a8a5..75ae840455 100644 --- a/pollux/sd-jwt/src/main/scala/org/hyperledger/identus/pollux/sdjwt/SDJWT.scala +++ b/pollux/sd-jwt/src/main/scala/org/hyperledger/identus/pollux/sdjwt/SDJWT.scala @@ -33,6 +33,7 @@ object SDJWT { } sealed trait Invalid extends ClaimsValidationResult case object InvalidSignature extends Invalid { def error = "Fail due to invalid input: InvalidSignature" } + case object InvalidToken extends Invalid { def error = "Fail due to invalid input: InvalidToken" } case object InvalidClaims extends Invalid { def error = "Fail to Verify the claims" } case object ClaimsDoNotMatch extends Invalid { def error = "Claims (are valid) but do not match the expected value" } case object InvalidClaimsIsNotJsonObj extends Invalid { def error = "The claims must be a valid json Obj" } @@ -41,12 +42,12 @@ object SDJWT { def issueCredential( issueKey: IssuerPrivateKey, claims: String, - ): CredentialJson = issueCredential(issueKey, claims, None) + ): CredentialCompact = issueCredential(issueKey, claims, None) def issueCredential( issueKey: IssuerPrivateKey, claimsMap: Map[String, String], - ): CredentialJson = { + ): CredentialCompact = { given encoder: JsonEncoder[String | Int] = new JsonEncoder[String | Int] { override def unsafeEncode(b: String | Int, indent: Option[Int], out: zio.json.internal.Write): Unit = { @@ -66,28 +67,28 @@ object SDJWT { issueKey: IssuerPrivateKey, claims: String, holderKey: HolderPublicKey, - ): CredentialJson = issueCredential(issueKey, claims, Some(holderKey)) + ): CredentialCompact = issueCredential(issueKey, claims, Some(holderKey)) private def issueCredential( issueKey: IssuerPrivateKey, claims: String, holderKey: Option[HolderPublicKey] - ): CredentialJson = { + ): CredentialCompact = { val issuer = new SdjwtIssuerWrapper(issueKey.value, issueKey.signAlg) // null) val sdjwt = issuer.issueSdJwtAllLevel( claims, // user_claims holderKey.map(_.jwt).orNull, // holder_key false, // add_decoy_claims - SdjwtSerializationFormat.JSON // COMPACT // serialization_format + SdjwtSerializationFormat.COMPACT // COMPACT // serialization_format ) - CredentialJson(sdjwt) + CredentialCompact.unsafeFromCompact(sdjwt) } def createPresentation( - sdjwt: CredentialJson, + sdjwt: CredentialCompact, claimsToDisclose: String, - ): PresentationJson = { - val holder = SdjwtHolderWrapper(sdjwt.value, SdjwtSerializationFormat.JSON) + ): PresentationCompact = { + val holder = SdjwtHolderWrapper(sdjwt.compact, SdjwtSerializationFormat.COMPACT) val presentation = holder.createPresentation( claimsToDisclose, null, // nonce @@ -95,7 +96,7 @@ object SDJWT { null, // holder_key null, // signAlg, // sign_alg ) - PresentationJson(presentation) + PresentationCompact.unsafeFromCompact(presentation) } /** Create a presentation with challenge @@ -109,13 +110,13 @@ object SDJWT { * A presentation */ def createPresentation( - sdjwt: CredentialJson, + sdjwt: CredentialCompact, claimsToDisclose: String, nonce: String, aud: String, holderKey: HolderPrivateKey - ): PresentationJson = { - val holder = SdjwtHolderWrapper(sdjwt.value, SdjwtSerializationFormat.JSON) + ): PresentationCompact = { + val holder = SdjwtHolderWrapper(sdjwt.compact, SdjwtSerializationFormat.COMPACT) val presentation = holder.createPresentation( claimsToDisclose, nonce, // nonce @@ -123,26 +124,27 @@ object SDJWT { holderKey.value, // encodingKey("ES256"), // holder_key holderKey.signAlg, // null, // sign_alg ) - PresentationJson(presentation) + PresentationCompact.unsafeFromCompact(presentation) } def getVerifiedClaims( key: IssuerPublicKey, - presentation: PresentationJson, - claims: String + presentation: PresentationCompact, ): ClaimsValidationResult = { Try { val verifier = SdjwtVerifierWrapper( - presentation.value, // sd_jwt_presentation + presentation.compact, // sd_jwt_presentation key.pem, // public_key null, // expected_aud null, // expected_nonce - SdjwtSerializationFormat.JSON // serialization_format + SdjwtSerializationFormat.COMPACT // serialization_format ) verifier.getVerifiedClaims() } match { case Failure(ex: SdjwtException.Unspecified) if ex.getMessage() == "invalid input: InvalidSignature" => InvalidSignature + case Failure(ex: SdjwtException.Unspecified) if ex.getMessage() == "invalid input: InvalidToken" => + InvalidToken case Failure(ex) => InvalidError(ex.getMessage()) case Success(claims) => claims.fromJson[Json] match @@ -155,24 +157,25 @@ object SDJWT { def getVerifiedClaims( key: IssuerPublicKey, - presentation: PresentationJson, - claims: String, + presentation: PresentationCompact, expectedNonce: String, expectedAud: String, // holderKey: HolderPrivateKey ): ClaimsValidationResult = { Try { val verifier = SdjwtVerifierWrapper( - presentation.value, // sd_jwt_presentation + presentation.compact, // sd_jwt_presentation key.pem, // public_key expectedAud, // expected_aud expectedNonce, // expected_nonce - SdjwtSerializationFormat.JSON // serialization_format + SdjwtSerializationFormat.COMPACT // serialization_format ) verifier.getVerifiedClaims() } match { case Failure(ex: SdjwtException.Unspecified) if ex.getMessage() == "invalid input: InvalidSignature" => InvalidSignature + case Failure(ex: SdjwtException.Unspecified) if ex.getMessage() == "invalid input: InvalidToken" => + InvalidToken case Failure(ex) => InvalidError(ex.getMessage()) case Success(claims) => claims.fromJson[Json] match @@ -185,21 +188,23 @@ object SDJWT { @deprecated("use getVerifiedClaims instaded", "ever") def verifyAndComparePresentation( key: IssuerPublicKey, - presentation: PresentationJson, + presentation: PresentationCompact, claims: String ): ClaimsValidationResult = { Try { val verifier = SdjwtVerifierWrapper( - presentation.value, // sd_jwt_presentation + presentation.compact, // sd_jwt_presentation key.pem, // public_key null, // expected_aud null, // expected_nonce - SdjwtSerializationFormat.JSON // serialization_format + SdjwtSerializationFormat.COMPACT // serialization_format ) verifier.verify(claims) } match { case Failure(ex: SdjwtException.Unspecified) if ex.getMessage() == "invalid input: InvalidSignature" => InvalidSignature + case Failure(ex: SdjwtException.Unspecified) if ex.getMessage() == "invalid input: InvalidToken" => + InvalidToken case Failure(ex) => InvalidError(ex.getMessage()) case Success(true) => ValidAnyMatch case Success(false) => InvalidClaims @@ -220,7 +225,7 @@ object SDJWT { @deprecated("use getVerifiedClaims instaded", "ever") def verifyAndComparePresentation( key: IssuerPublicKey, - presentation: PresentationJson, + presentation: PresentationCompact, claims: String, expectedNonce: String, expectedAud: String, @@ -228,16 +233,18 @@ object SDJWT { ): ClaimsValidationResult = { Try { val verifier = SdjwtVerifierWrapper( - presentation.value, // sd_jwt_presentation + presentation.compact, // sd_jwt_presentation key.pem, // public_key expectedAud, // expected_aud expectedNonce, // expected_nonce - SdjwtSerializationFormat.JSON // serialization_format + SdjwtSerializationFormat.COMPACT // serialization_format ) verifier.verify(claims) } match { case Failure(ex: SdjwtException.Unspecified) if ex.getMessage() == "invalid input: InvalidSignature" => InvalidSignature + case Failure(ex: SdjwtException.Unspecified) if ex.getMessage() == "invalid input: InvalidToken" => + InvalidToken case Failure(ex) => InvalidError(ex.getMessage()) case Success(true) => ValidAnyMatch case Success(false) => InvalidClaims diff --git a/pollux/sd-jwt/src/test/scala/org/hyperledger/identus/pollux/sdjwt/SDJWTSpec.scala b/pollux/sd-jwt/src/test/scala/org/hyperledger/identus/pollux/sdjwt/SDJWTSpec.scala index b94dc96403..f3d2944a90 100644 --- a/pollux/sd-jwt/src/test/scala/org/hyperledger/identus/pollux/sdjwt/SDJWTSpec.scala +++ b/pollux/sd-jwt/src/test/scala/org/hyperledger/identus/pollux/sdjwt/SDJWTSpec.scala @@ -107,19 +107,46 @@ def FAlSE_CLAIMS_PRESENTED = object SDJWTSpec extends ZIOSpecDefault { override def spec = suite("SDJWTRawSpec")( + test("CredentialCompact") { + val credentialExample = + "eyJhbGciOiJFUzI1NiJ9.eyJfc2QiOlsiMXRJNHZLQ2R3ald5aExxT0lkbTloUHYxNFo0ellEVlRiekgzUWd2S3ctTSIsIkM2alFVaDJiYzh3YTB6dHJiVFNnMUJxQkNMQ2tJSk5lU3ZuSkoyZ2NDc2MiXSwiX3NkX2FsZyI6InNoYS0yNTYiLCJpc3MiOiJkaWQ6ZXhhbXBsZTppc3N1ZXIiLCJpYXQiOjE2ODMwMDAwMDAsImV4cCI6MTg4MzAwMDAwMCwiY25mIjp7Imp3ayI6eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2IiwieCI6IlRDQUVSMTladnUzT0hGNGo0VzR2ZlNWb0hJUDFJTGlsRGxzN3ZDZUdlbWMiLCJ5IjoiWnhqaVdXYlpNUUdIVldLVlE0aGJTSWlyc1ZmdWVjQ0U2dDRqVDlGMkhaUSJ9fX0.MpRZOLNaCCs4_h7Injbp4MfiGfnieu2aMFG1cbD8KM_NyKwRnPrcMeSHkUGhDoYqTPfOFdvbLeWefWQr-u3hRw~WyJhbXZPbWxTMFJDajhwakQ0d2FpWVNBIiwgInN1YiIsICJkaWQ6ZXhhbXBsZTpob2xkZXIiXQ~WyJ1ZW9zVjRTMzVjMG1JbGFXZld5enp3IiwgInN0cmVldF9hZGRyZXNzIiwgIlNjaHVsc3RyLiAxMiJd~WyI4c3ZDaG1EcFlhUl9RSXFRVXY1OTF3IiwgImxvY2FsaXR5IiwgIlNjaHVscGZvcnRhIl0~WyJvbEw4dnV1LVgtVkR2QjR6R1pBS0p3IiwgInJlZ2lvbiIsICJTYWNoc2VuLUFuaGFsdCJd~WyJweV9tZmdZSjNrVDIwRkZDZkdMR2N3IiwgImNvdW50cnkiLCAiREUiXQ~WyJPY2RLUFNYT2VtVTNITlNqRmVkYldRIiwgImFkZHJlc3MiLCB7Il9zZCI6WyIxVlRyYzU0LVJmV3FEdnM4ay0yT2NDZmJnbWwxWWg5TTR4X3hJTHA2Qk9jIiwiMkJCSjhMR1M0UUZsYUZMNGY4UzFWUDhvaG83dzJQUkVlUHJwelRVQktMOCIsIkEwWnBkSUhrVjVrX3N4b3hMYkZoU3B3UEZFcEJUeWdoM0h2SnNzNmxRS0EiLCJzT3lnSmQwMWE2dzV1YUszMVJEUXF1dTk5VTJ2TENxWHNyd2lodHBvTklJIl19XQ~" + val c = CredentialCompact.unsafeFromCompact(credentialExample) + + assertTrue(c.jwtHeader == "eyJhbGciOiJFUzI1NiJ9") && + assertTrue( + c.jwtPayload == + "eyJfc2QiOlsiMXRJNHZLQ2R3ald5aExxT0lkbTloUHYxNFo0ellEVlRiekgzUWd2S3ctTSIsIkM2alFVaDJiYzh3YTB6dHJiVFNnMUJxQkNMQ2tJSk5lU3ZuSkoyZ2NDc2MiXSwiX3NkX2FsZyI6InNoYS0yNTYiLCJpc3MiOiJkaWQ6ZXhhbXBsZTppc3N1ZXIiLCJpYXQiOjE2ODMwMDAwMDAsImV4cCI6MTg4MzAwMDAwMCwiY25mIjp7Imp3ayI6eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2IiwieCI6IlRDQUVSMTladnUzT0hGNGo0VzR2ZlNWb0hJUDFJTGlsRGxzN3ZDZUdlbWMiLCJ5IjoiWnhqaVdXYlpNUUdIVldLVlE0aGJTSWlyc1ZmdWVjQ0U2dDRqVDlGMkhaUSJ9fX0" + ) && + assertTrue( + c.jwtSignature == "MpRZOLNaCCs4_h7Injbp4MfiGfnieu2aMFG1cbD8KM_NyKwRnPrcMeSHkUGhDoYqTPfOFdvbLeWefWQr-u3hRw" + ) && + assertTrue( + c.kbJWT.isEmpty + ) && + assertTrue( + Seq( + "WyJhbXZPbWxTMFJDajhwakQ0d2FpWVNBIiwgInN1YiIsICJkaWQ6ZXhhbXBsZTpob2xkZXIiXQ", + "WyJ1ZW9zVjRTMzVjMG1JbGFXZld5enp3IiwgInN0cmVldF9hZGRyZXNzIiwgIlNjaHVsc3RyLiAxMiJd", + "WyI4c3ZDaG1EcFlhUl9RSXFRVXY1OTF3IiwgImxvY2FsaXR5IiwgIlNjaHVscGZvcnRhIl0", + "WyJvbEw4dnV1LVgtVkR2QjR6R1pBS0p3IiwgInJlZ2lvbiIsICJTYWNoc2VuLUFuaGFsdCJd", + "WyJweV9tZmdZSjNrVDIwRkZDZkdMR2N3IiwgImNvdW50cnkiLCAiREUiXQ", + "WyJPY2RLUFNYT2VtVTNITlNqRmVkYldRIiwgImFkZHJlc3MiLCB7Il9zZCI6WyIxVlRyYzU0LVJmV3FEdnM4ay0yT2NDZmJnbWwxWWg5TTR4X3hJTHA2Qk9jIiwiMkJCSjhMR1M0UUZsYUZMNGY4UzFWUDhvaG83dzJQUkVlUHJwelRVQktMOCIsIkEwWnBkSUhrVjVrX3N4b3hMYkZoU3B3UEZFcEJUeWdoM0h2SnNzNmxRS0EiLCJzT3lnSmQwMWE2dzV1YUszMVJEUXF1dTk5VTJ2TENxWHNyd2lodHBvTklJIl19XQ", + ) == c.disclosures + ) + }, test("issue credential") { val credential = SDJWT.issueCredential(ISSUER_KEY, CLAIMS, HOLDER_KEY_JWK_PUBLIC) - assertTrue(!credential.value.isEmpty()) + assertTrue(!credential.compact.isEmpty()) }, test("make presentation") { val credential = SDJWT.issueCredential(ISSUER_KEY, CLAIMS) val presentation = SDJWT.createPresentation(credential, CLAIMS_PRESENTED) - assertTrue(!presentation.value.isEmpty()) + assertTrue(!presentation.compact.isEmpty()) }, test("getVerifiedClaims presentation") { val credential = SDJWT.issueCredential(ISSUER_KEY, CLAIMS) val presentation = SDJWT.createPresentation(credential, CLAIMS_QUERY) - val ret = SDJWT.getVerifiedClaims(ISSUER_KEY_PUBLIC, presentation, CLAIMS_PRESENTED) + val ret = SDJWT.getVerifiedClaims(ISSUER_KEY_PUBLIC, presentation) assertTrue( """{"iss":"did:example:issuer","iat":1683000000,"exp":1883000000,"address":{"country":"DE"}}""" .fromJson[ast.Json.Obj] @@ -130,7 +157,7 @@ object SDJWTSpec extends ZIOSpecDefault { test("issue credential without sub & iat and getVerifiedClaims") { val credential = SDJWT.issueCredential(ISSUER_KEY, CLAIMS_WITHOUT_SUB_IAT) val presentation = SDJWT.createPresentation(credential, CLAIMS_QUERY) - val ret = SDJWT.getVerifiedClaims(ISSUER_KEY_PUBLIC, presentation, CLAIMS_PRESENTED) + val ret = SDJWT.getVerifiedClaims(ISSUER_KEY_PUBLIC, presentation) assertTrue( """{"iss":"did:example:issuer","exp":1883000000,"address":{"country":"DE"}}""" .fromJson[ast.Json.Obj] @@ -161,8 +188,8 @@ object SDJWTSpec extends ZIOSpecDefault { "did:example:verifier", HOLDER_KEY ) - assert(presentation.value)(isNonEmptyString) - // Assertion { TestArrow.make[PresentationJson, String] { a => TestTrace.succeed(a.value) } >>> isEmptyString.arrow } + assert(presentation.compact)(isNonEmptyString) + // Assertion { TestArrow.make[PresentationCompact, String] { a => TestTrace.succeed(a.value) } >>> isEmptyString.arrow } }, test("verify presentation with holder presentation challenge") { val credential = SDJWT.issueCredential(ISSUER_KEY, CLAIMS, HOLDER_KEY_JWK_PUBLIC) @@ -277,7 +304,7 @@ object SDJWTSpec extends ZIOSpecDefault { assertTrue(ret == SDJWT.InvalidSignature) }, // methods - test("get iss field from PresentationJson") { + test("get iss field from PresentationCompact") { val ed25519KeyPair = KmpEd25519KeyOps.generateKeyPair val issuerKey = IssuerPrivateKey(ed25519KeyPair.privateKey) IssuerPublicKey(ed25519KeyPair.publicKey)