diff --git a/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/model/error/PresentationError.scala b/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/model/error/PresentationError.scala index 7ecf89525c..a7227cee7e 100644 --- a/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/model/error/PresentationError.scala +++ b/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/model/error/PresentationError.scala @@ -17,6 +17,6 @@ object PresentationError { object MissingCredential extends PresentationError object MissingCredentialFormat extends PresentationError final case class UnsupportedCredentialFormat(vcFormat: String) extends PresentationError - + final case class InvalidAnoncredPresentationRequest(error: String) extends PresentationError final case class MissingAnoncredPresentationRequest(error: String) extends PresentationError } diff --git a/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/PresentationServiceImpl.scala b/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/PresentationServiceImpl.scala index 12678eb92d..f81699abeb 100644 --- a/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/PresentationServiceImpl.scala +++ b/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/PresentationServiceImpl.scala @@ -21,7 +21,7 @@ import zio.* import java.rmi.UnexpectedException import java.time.Instant import java.util as ju -import java.util.UUID +import java.util.{UUID, Base64 as JBase64} private class PresentationServiceImpl( presentationRepository: PresentationRepository, @@ -278,9 +278,17 @@ private class PresentationServiceImpl( val jsonF = PresentCredentialRequestFormat.JWT.name // stable identifier val anoncredF = PresentCredentialRequestFormat.Anoncred.name // stable identifier head.format match - case None => ZIO.fail(PresentationError.MissingCredentialFormat) - case Some(`jsonF`) => ZIO.succeed(CredentialFormat.JWT) - case Some(`anoncredF`) => ZIO.succeed(CredentialFormat.AnonCreds) + case None => ZIO.fail(PresentationError.MissingCredentialFormat) + case Some(`jsonF`) => ZIO.succeed(CredentialFormat.JWT) + case Some(`anoncredF`) => + head.data match + case Base64(data) => + val decodedData = new String(JBase64.getUrlDecoder.decode(data)) + AnoncredPresentationRequestV1.schemaSerDes + .validate(decodedData) + .map(_ => CredentialFormat.AnonCreds) + .mapError(error => InvalidAnoncredPresentationRequest(error.error)) + case _ => ZIO.fail(InvalidAnoncredPresentationRequest("Expecting Base64-encoded data")) case Some(unsupportedFormat) => ZIO.fail(PresentationError.UnsupportedCredentialFormat(unsupportedFormat)) case _ => ZIO.fail(PresentationError.UnexpectedError("Presentation with multi attachments")) } diff --git a/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/PresentationServiceSpec.scala b/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/PresentationServiceSpec.scala index 96d2f1ea1d..332810668a 100644 --- a/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/PresentationServiceSpec.scala +++ b/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/PresentationServiceSpec.scala @@ -319,7 +319,7 @@ object PresentationServiceSpec extends ZIOSpecDefault with PresentationServiceSp fails(equalTo(UnsupportedCredentialFormat(vcFormat = "Some/UnsupportedCredentialFormat"))) ) }, - test("receiveRequestPresentation updates the RequestPresentation in PresentatinRecord") { + test("receiveRequestPresentation JWT updates the RequestPresentation in PresentationRecord") { for { svc <- ZIO.service[PresentationService] connectionId = Some("connectionId") @@ -350,6 +350,100 @@ object PresentationServiceSpec extends ZIOSpecDefault with PresentationServiceSp assertTrue(aRecord.requestPresentationData == Some(requestPresentation)) } }, + test("receiveRequestPresentation Anoncred updates the RequestPresentation in PresentationRecord") { + for { + svc <- ZIO.service[PresentationService] + connectionId = Some("connectionId") + body = RequestPresentation.Body(goal_code = Some("Presentation Request")) + prover = DidId("did:peer:Prover") + verifier = DidId("did:peer:Verifier") + anoncredPresentationRequestV1 = AnoncredPresentationRequestV1( + Map.empty, + Map.empty, + "name", + "nonce", + "version", + None + ) + attachmentDescriptor = AttachmentDescriptor.buildBase64Attachment( + mediaType = Some("application/json"), + format = Some(PresentCredentialRequestFormat.Anoncred.name), + payload = AnoncredPresentationRequestV1.schemaSerDes.serialize(anoncredPresentationRequestV1).getBytes() + ) + requestPresentation = RequestPresentation( + body = body, + attachments = Seq(attachmentDescriptor), + to = prover, + from = verifier, + ) + aRecord <- svc.receiveRequestPresentation(connectionId, requestPresentation) + + } yield { + assertTrue(aRecord.connectionId == connectionId) && + assertTrue(aRecord.protocolState == PresentationRecord.ProtocolState.RequestReceived) && + assertTrue(aRecord.requestPresentationData == Some(requestPresentation)) + } + }, + test("receiveRequestPresentation Anoncred should fail given invalid attachment") { + for { + svc <- ZIO.service[PresentationService] + connectionId = Some("connectionId") + body = RequestPresentation.Body(goal_code = Some("Presentation Request")) + presentationAttachmentAsJson = + """{ + "challenge": "1f44d55f-f161-4938-a659-f8026467f126", + "domain": "us.gov/DriverLicense", + "credential_manifest": {} + }""" + prover = DidId("did:peer:Prover") + verifier = DidId("did:peer:Verifier") + + attachmentDescriptor = AttachmentDescriptor.buildJsonAttachment( + payload = presentationAttachmentAsJson, + format = Some(PresentCredentialProposeFormat.Anoncred.name) + ) + requestPresentation = RequestPresentation( + body = body, + attachments = Seq(attachmentDescriptor), + to = prover, + from = verifier, + ) + result <- svc.receiveRequestPresentation(connectionId, requestPresentation).exit + + } yield assert(result)( + fails(equalTo(InvalidAnoncredPresentationRequest("Expecting Base64-encoded data"))) + ) + }, + test("receiveRequestPresentation Anoncred should fail given invalid anoncred format") { + for { + svc <- ZIO.service[PresentationService] + connectionId = Some("connectionId") + body = RequestPresentation.Body(goal_code = Some("Presentation Request")) + presentationAttachmentAsJson = + """{ + "challenge": "1f44d55f-f161-4938-a659-f8026467f126", + "domain": "us.gov/DriverLicense", + "credential_manifest": {} + }""" + prover = DidId("did:peer:Prover") + verifier = DidId("did:peer:Verifier") + attachmentDescriptor = AttachmentDescriptor.buildBase64Attachment( + mediaType = Some("application/json"), + format = Some(PresentCredentialRequestFormat.Anoncred.name), + payload = presentationAttachmentAsJson.getBytes() + ) + requestPresentation = RequestPresentation( + body = body, + attachments = Seq(attachmentDescriptor), + to = prover, + from = verifier, + ) + result <- svc.receiveRequestPresentation(connectionId, requestPresentation).exit + + } yield assert(result)( + fails(isSubtype[InvalidAnoncredPresentationRequest](anything)) + ) + }, test("acceptRequestPresentation updates the PresentatinRecord JWT") { for { repo <- ZIO.service[CredentialRepository] diff --git a/prism-agent/service/server/src/main/scala/io/iohk/atala/presentproof/controller/PresentProofController.scala b/prism-agent/service/server/src/main/scala/io/iohk/atala/presentproof/controller/PresentProofController.scala index 02dc1223d2..6ab89d8016 100644 --- a/prism-agent/service/server/src/main/scala/io/iohk/atala/presentproof/controller/PresentProofController.scala +++ b/prism-agent/service/server/src/main/scala/io/iohk/atala/presentproof/controller/PresentProofController.scala @@ -47,6 +47,8 @@ object PresentProofController { ErrorResponse.badRequest(title = "InvalidFlowState", detail = Some(msg)) case PresentationError.MissingAnoncredPresentationRequest(msg) => ErrorResponse.badRequest(title = "Missing Anoncred Presentation Request", detail = Some(msg)) + case PresentationError.InvalidAnoncredPresentationRequest(msg) => + ErrorResponse.badRequest(title = "Invalid Anoncred Presentation Request", detail = Some(msg)) case PresentationError.UnexpectedError(msg) => ErrorResponse.internalServerError(detail = Some(msg)) case PresentationError.IssuedCredentialNotFoundError(_) =>