Skip to content

Commit

Permalink
feat: VC Verification API Algorithm Issuer Verification (#962)
Browse files Browse the repository at this point in the history
Signed-off-by: Bassam Riman <[email protected]>
  • Loading branch information
CryptoKnightIOG committed Apr 16, 2024
1 parent 22d88c7 commit f765118
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package io.iohk.atala.pollux.core.service.verification

import io.iohk.atala.pollux.core.model.schema.CredentialSchema
import io.iohk.atala.pollux.core.service.URIDereferencer
import io.iohk.atala.pollux.vc.jwt.{DidResolver, JWT, JwtCredential}
import io.iohk.atala.pollux.vc.jwt.{DidResolver, JWT, JWTVerification, JwtCredential}
import zio.{IO, *}

class VcVerificationServiceImpl(didResolver: DidResolver, uriDereferencer: URIDereferencer)
Expand Down Expand Up @@ -48,6 +48,8 @@ class VcVerificationServiceImpl(didResolver: DidResolver, uriDereferencer: URIDe
case VcVerification.SignatureVerification => verifySignature(credential)
case VcVerification.ExpirationCheck => verifyExpiration(credential)
case VcVerification.NotBeforeCheck => verifyNotBefore(credential)
case VcVerification.AlgorithmVerification => verifyAlgorithm(credential)
case VcVerification.IssuerIdentification => verifyIssuerIdentification(credential)
case _ => ZIO.fail(VcVerificationServiceError.UnexpectedError(s"Unsupported Verification $verification"))
}
}
Expand Down Expand Up @@ -97,7 +99,7 @@ class VcVerificationServiceImpl(didResolver: DidResolver, uriDereferencer: URIDe
.mapError(error => VcVerificationServiceError.UnexpectedError(error))
.map(validation =>
VcVerificationOutcome(
verification = VcVerification.SchemaCheck,
verification = VcVerification.SignatureVerification,
success = validation
.map(_ => true)
.getOrElse(false)
Expand All @@ -108,7 +110,7 @@ class VcVerificationServiceImpl(didResolver: DidResolver, uriDereferencer: URIDe
private def verifyExpiration(credential: String): IO[VcVerificationServiceError, VcVerificationOutcome] = {
ZIO.succeed(
VcVerificationOutcome(
verification = VcVerification.SchemaCheck,
verification = VcVerification.ExpirationCheck,
success = JwtCredential
.validateExpiration(JWT(credential))
.map(_ => true)
Expand All @@ -120,14 +122,40 @@ class VcVerificationServiceImpl(didResolver: DidResolver, uriDereferencer: URIDe
private def verifyNotBefore(credential: String): IO[VcVerificationServiceError, VcVerificationOutcome] = {
ZIO.succeed(
VcVerificationOutcome(
verification = VcVerification.SchemaCheck,
verification = VcVerification.NotBeforeCheck,
success = JwtCredential
.validateNotBefore(JWT(credential))
.map(_ => true)
.getOrElse(false)
)
)
}

private def verifyAlgorithm(credential: String): IO[VcVerificationServiceError, VcVerificationOutcome] = {
ZIO.succeed(
VcVerificationOutcome(
verification = VcVerification.AlgorithmVerification,
success = JWTVerification
.validateAlgorithm(JWT(credential))
.map(_ => true)
.getOrElse(false)
)
)
}

private def verifyIssuerIdentification(credential: String): IO[VcVerificationServiceError, VcVerificationOutcome] = {
JwtCredential
.validateIssuerJWT(JWT(credential))(didResolver)
.mapError(error => VcVerificationServiceError.UnexpectedError(error))
.map(validation =>
VcVerificationOutcome(
verification = VcVerification.IssuerIdentification,
success = validation
.map(_ => true)
.getOrElse(false)
)
)
}
}

object VcVerificationServiceImpl {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
package io.iohk.atala.pollux.vc.jwt

import com.nimbusds.jose.JWSVerifier
import com.nimbusds.jose.crypto.ECDSAVerifier
import com.nimbusds.jose.crypto.bc.BouncyCastleProviderSingleton
import com.nimbusds.jose.jwk.*
import com.nimbusds.jose.util.Base64URL
import com.nimbusds.jose.JWSVerifier
import com.nimbusds.jose.crypto.bc.BouncyCastleProviderSingleton
import com.nimbusds.jwt.SignedJWT
import io.circe
import io.circe.generic.auto.*
import io.iohk.atala.castor.core.model.did.VerificationRelationship
import pdi.jwt.*
import zio.prelude.*
import zio.*
import zio.prelude.*

import java.security.interfaces.ECPublicKey
import java.security.PublicKey
import java.security.interfaces.ECPublicKey
import scala.util.{Failure, Success, Try}

object JWTVerification {
Expand All @@ -24,25 +25,49 @@ object JWTVerification {
"ES256" -> Set("ES256") // TODO: Only use valid type (added just for compatibility in the Demo code)
)

def validateEncodedJwt[T](jwt: JWT, proofPurpose: Option[VerificationRelationship] = None)(
didResolver: DidResolver
)(decoder: String => Validation[String, T])(issuerDidExtractor: T => String): IO[String, Validation[String, Unit]] = {
val decodeJWT = Validation
.fromTry(JwtCirce.decodeRawAll(jwt.value, JwtOptions(false, false, false)))
.mapError(_.getMessage)
def validateAlgorithm(jwt: JWT): Validation[String, Unit] = {
val decodedJWT =
Validation
.fromTry(JwtCirce.decodeRawAll(jwt.value, JwtOptions(false, false, false)))
.mapError(_.getMessage)
for {
decodedJwtTask <- decodedJWT
(header, _, _) = decodedJwtTask
algorithm <- Validation
.fromOptionWith("An algorithm must be specified in the header")(JwtCirce.parseHeader(header).algorithm)
result <-
Validation
.fromPredicateWith("No PublicKey to validate against found")(
SUPPORT_PUBLIC_KEY_TYPES.getOrElse(algorithm.name, Set.empty)
)(_.nonEmpty)
.flatMap(_ => Validation.unit)

val extractAlgorithm: Validation[String, JwtAlgorithm] =
} yield result
}

def validateIssuer[T](jwt: JWT)(didResolver: DidResolver)(
decoder: String => Validation[String, T]
)(issuerDidExtractor: T => String): IO[String, Validation[String, DIDDocument]] = {
val decodedJWT =
Validation
.fromTry(JwtCirce.decodeRawAll(jwt.value, JwtOptions(false, false, false)))
.mapError(_.getMessage)

val claim: Validation[String, String] =
for {
decodedJwtTask <- decodeJWT
(header, _, _) = decodedJwtTask
algorithm <- Validation
.fromOptionWith("An algorithm must be specified in the header")(JwtCirce.parseHeader(header).algorithm)
} yield algorithm
decodedJwtTask <- decodedJWT
(_, claim, _) = decodedJwtTask
} yield claim

validateIssuerFromClaim(claim)(didResolver)(decoder)(issuerDidExtractor)
}

def validateIssuerFromClaim[T](validatedClaim: Validation[String, String])(didResolver: DidResolver)(
decoder: String => Validation[String, T]
)(issuerDidExtractor: T => String): IO[String, Validation[String, DIDDocument]] = {
val validatedIssuerDid: Validation[String, String] =
for {
decodedJwtTask <- decodeJWT
(_, claim, _) = decodedJwtTask
claim <- validatedClaim
decodedClaim <- decoder(claim)
extractIssuerDid = issuerDidExtractor(decodedClaim)
} yield extractIssuerDid
Expand All @@ -55,6 +80,32 @@ object JWTVerification {
)(identity)
.map(b => b.flatten)

loadDidDocument
}

def validateEncodedJwt[T](jwt: JWT, proofPurpose: Option[VerificationRelationship] = None)(
didResolver: DidResolver
)(decoder: String => Validation[String, T])(issuerDidExtractor: T => String): IO[String, Validation[String, Unit]] = {
val decodedJWT = Validation
.fromTry(JwtCirce.decodeRawAll(jwt.value, JwtOptions(false, false, false)))
.mapError(_.getMessage)

val extractAlgorithm: Validation[String, JwtAlgorithm] =
for {
decodedJwtTask <- decodedJWT
(header, _, _) = decodedJwtTask
algorithm <- Validation
.fromOptionWith("An algorithm must be specified in the header")(JwtCirce.parseHeader(header).algorithm)
} yield algorithm

val claim: Validation[String, String] =
for {
decodedJwtTask <- decodedJWT
(_, claim, _) = decodedJwtTask
} yield claim

val loadDidDocument = validateIssuerFromClaim(claim)(didResolver)(decoder)(issuerDidExtractor)

loadDidDocument
.map(validatedDidDocument => {
for {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -742,6 +742,14 @@ object JwtCredential {
)(_.iss)
}

def validateIssuerJWT(
jwt: JWT,
)(didResolver: DidResolver): IO[String, Validation[String, DIDDocument]] = {
JWTVerification.validateIssuer(jwt)(didResolver: DidResolver)(claim =>
Validation.fromEither(decode[JwtCredentialPayload](claim).left.map(_.toString))
)(_.iss)
}

def validateJwtSchema(
jwt: JWT
)(schemaResolver: SchemaResolver)(
Expand Down

0 comments on commit f765118

Please sign in to comment.