Skip to content

Commit

Permalink
feat: Semantic-Subject-Verification (#963)
Browse files Browse the repository at this point in the history
Signed-off-by: Bassam Riman <[email protected]>
  • Loading branch information
CryptoKnightIOG committed Apr 23, 2024
1 parent 5c06b4e commit 4d128a7
Show file tree
Hide file tree
Showing 13 changed files with 338 additions and 129 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import io.iohk.atala.pollux.core.model.schema.`type`.{
CredentialJsonSchemaType,
CredentialSchemaType
}
import io.iohk.atala.pollux.core.model.schema.validator.JsonSchemaValidatorImpl
import io.iohk.atala.pollux.core.model.schema.validator.{JsonSchemaValidator, JsonSchemaValidatorImpl}
import io.iohk.atala.pollux.core.service.URIDereferencer
import zio.*
import zio.json.*
Expand Down Expand Up @@ -116,11 +116,10 @@ object CredentialSchema {
given JsonEncoder[CredentialSchema] = DeriveJsonEncoder.gen[CredentialSchema]
given JsonDecoder[CredentialSchema] = DeriveJsonDecoder.gen[CredentialSchema]

def validateJWTClaims(
def validSchemaValidator(
schemaId: String,
claims: String,
uriDereferencer: URIDereferencer
): IO[CredentialSchemaError, Unit] = {
): IO[CredentialSchemaError, JsonSchemaValidator] = {
for {
uri <- ZIO.attempt(new URI(schemaId)).mapError(t => URISyntaxError(t.getMessage))
content <- uriDereferencer.dereference(uri).mapError(err => UnexpectedError(err.toString))
Expand All @@ -137,7 +136,17 @@ object CredentialSchema {
.mapError(error => CredentialSchemaParsingError(s"Failed to parse schema content as Json or OEA: $error"))
.flatMap(cs => JsonSchemaValidatorImpl.from(cs.schema).mapError(SchemaError.apply))
)
_ <- schemaValidator.validate(claims).mapError(SchemaError.apply)
} yield schemaValidator
}

def validateJWTCredentialSubject(
schemaId: String,
credentialSubject: String,
uriDereferencer: URIDereferencer
): IO[CredentialSchemaError, Unit] = {
for {
schemaValidator <- validSchemaValidator(schemaId, uriDereferencer)
_ <- schemaValidator.validate(credentialSubject).mapError(SchemaError.apply)
} yield ()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ private class CredentialServiceImpl(
_ <- maybeSchemaId match
case Some(schemaId) =>
CredentialSchema
.validateJWTClaims(schemaId, claims.noSpaces, uriDereferencer)
.validateJWTCredentialSubject(schemaId, claims.noSpaces, uriDereferencer)
.mapError(e => CredentialSchemaError(e))
case None =>
ZIO.unit
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,18 @@ trait VcVerificationService {
def verify(request: List[VcVerificationRequest]): IO[VcVerificationServiceError, List[VcVerificationResult]]
}

sealed trait VcVerificationParameter

case class AudienceParameter(aud: String) extends VcVerificationParameter

final case class VcVerificationRequest(
credential: String,
verifications: List[VcVerification]
verification: VcVerification,
parameter: Option[VcVerificationParameter]
)

final case class VcVerificationResult(
credential: String,
checks: List[VcVerification],
successfulChecks: List[VcVerification],
failedChecks: List[VcVerification],
failedAsWarningChecks: List[VcVerification]
verification: VcVerification,
success: Boolean
)

final case class VcVerificationOutcome(verification: VcVerification, success: Boolean)
Original file line number Diff line number Diff line change
Expand Up @@ -3,58 +3,46 @@ 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, JWTVerification, JwtCredential}
import sttp.tapir.Schema
import zio.{IO, *}

class VcVerificationServiceImpl(didResolver: DidResolver, uriDereferencer: URIDereferencer)
extends VcVerificationService {
override def verify(
vcVerificationRequests: List[VcVerificationRequest]
): IO[VcVerificationServiceError, List[VcVerificationResult]] = {
vcVerificationRequests.map(vcVerificationRequest =>
val verificationOutcomesZIO = ZIO.collectAll(
vcVerificationRequest.verifications
.map(verification => verify(vcVerificationRequest.credential, verification))
ZIO.collectAll(
vcVerificationRequests.map(vcVerificationRequest =>
verify(vcVerificationRequest.credential, vcVerificationRequest.verification, vcVerificationRequest.parameter)
)

verificationOutcomesZIO.map(verificationOutcomes => {
val successfulChecks = verificationOutcomes.filter(_.success).map(_.verification)

val failedVerifications = verificationOutcomes.filterNot(_.success).map(_.verification)

val failedAsErrorChecks =
failedVerifications.filter(verification => verification.failureType == VcVerificationFailureType.ERROR)

val failedAsWarningChecks =
failedVerifications.filter(verification => verification.failureType == VcVerificationFailureType.WARN)

VcVerificationResult(
credential = vcVerificationRequest.credential,
checks = vcVerificationRequest.verifications,
successfulChecks = successfulChecks,
failedChecks = failedAsErrorChecks,
failedAsWarningChecks = failedAsWarningChecks
)
})
)
ZIO.succeed(List.empty)
}

private def verify(
credential: String,
verification: VcVerification
): IO[VcVerificationServiceError, VcVerificationOutcome] = {
verification match {
case VcVerification.SchemaCheck => verifySchema(credential)
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"))
verification: VcVerification,
maybeParameter: Option[VcVerificationParameter]
): IO[VcVerificationServiceError, VcVerificationResult] = {
(verification, maybeParameter) match {
case (VcVerification.SchemaCheck, None) => verifySchema(credential)
case (VcVerification.SignatureVerification, None) => verifySignature(credential)
case (VcVerification.ExpirationCheck, None) => verifyExpiration(credential)
case (VcVerification.NotBeforeCheck, None) => verifyNotBefore(credential)
case (VcVerification.AlgorithmVerification, None) => verifyAlgorithm(credential)
case (VcVerification.IssuerIdentification, None) => verifyIssuerIdentification(credential)
case (VcVerification.SubjectVerification, None) => verifySubjectVerification(credential)
case (VcVerification.SemanticCheckOfClaims, None) => verifySemanticCheckOfClaims(credential)
case (VcVerification.AudienceCheck, Some(AudienceParameter(aud))) => verifyAudienceCheck(credential, aud)
case _ =>
ZIO.fail(
VcVerificationServiceError.UnexpectedError(
s"Unsupported Verification:$verification and Parameters:$maybeParameter"
)
)
}
}

private def verifySchema(credential: String): IO[VcVerificationServiceError, VcVerificationOutcome] = {
private def verifySchema(credential: String): IO[VcVerificationServiceError, VcVerificationResult] = {
val result =
for {
decodedJwt <-
Expand All @@ -65,40 +53,81 @@ class VcVerificationServiceImpl(didResolver: DidResolver, uriDereferencer: URIDe
ZIO
.fromOption(decodedJwt.maybeCredentialSchema)
.mapError(error => VcVerificationServiceError.UnexpectedError(s"Missing Credential Schema: $error"))
} yield CredentialSchema
.validateJWTClaims(
credentialSchema.id,
decodedJwt.credentialSubject.noSpaces,
uriDereferencer
)
result <- CredentialSchema
.validSchemaValidator(
credentialSchema.id,
uriDereferencer
)
.mapError(error => VcVerificationServiceError.UnexpectedError(s"Schema Validator Failed: $error"))
} yield result

result.map(validation =>
validation
.as(
VcVerificationOutcome(
result
.as(
VcVerificationResult(
credential = credential,
verification = VcVerification.SchemaCheck,
success = true
)
)
.catchAll(_ =>
ZIO.succeed(
VcVerificationResult(
credential = credential,
verification = VcVerification.SchemaCheck,
success = true
success = false
)
)
.catchAll(_ =>
ZIO.succeed(
VcVerificationOutcome(
verification = VcVerification.SchemaCheck,
success = false
)
)
}

private def verifySubjectVerification(credential: String): IO[VcVerificationServiceError, VcVerificationResult] = {
val result =
for {
decodedJwt <-
JwtCredential
.decodeJwt(JWT(credential))
.mapError(error => VcVerificationServiceError.UnexpectedError(s"Unable decode JWT: $error"))
credentialSchema <-
ZIO
.fromOption(decodedJwt.maybeCredentialSchema)
.mapError(error => VcVerificationServiceError.UnexpectedError(s"Missing Credential Schema: $error"))
result <- CredentialSchema
.validateJWTCredentialSubject(
credentialSchema.id,
decodedJwt.credentialSubject.noSpaces,
uriDereferencer
)
)
)
.mapError(error =>
VcVerificationServiceError.UnexpectedError(s"JWT Credential Subject Validation Failed: $error")
)
} yield result

ZIO.succeed(VcVerificationOutcome(verification = VcVerification.SchemaCheck, success = true))
result
.as(
VcVerificationResult(
credential = credential,
verification = VcVerification.SubjectVerification,
success = true
)
)
.catchAll(_ =>
ZIO.succeed(
VcVerificationResult(
credential = credential,
verification = VcVerification.SubjectVerification,
success = false
)
)
)
}

private def verifySignature(credential: String): IO[VcVerificationServiceError, VcVerificationOutcome] = {
private def verifySignature(credential: String): IO[VcVerificationServiceError, VcVerificationResult] = {
JwtCredential
.validateEncodedJWT(JWT(credential))(didResolver)
.mapError(error => VcVerificationServiceError.UnexpectedError(error))
.map(validation =>
VcVerificationOutcome(
VcVerificationResult(
credential = credential,
verification = VcVerification.SignatureVerification,
success = validation
.map(_ => true)
Expand All @@ -107,9 +136,10 @@ class VcVerificationServiceImpl(didResolver: DidResolver, uriDereferencer: URIDe
)
}

private def verifyExpiration(credential: String): IO[VcVerificationServiceError, VcVerificationOutcome] = {
private def verifyExpiration(credential: String): IO[VcVerificationServiceError, VcVerificationResult] = {
ZIO.succeed(
VcVerificationOutcome(
VcVerificationResult(
credential = credential,
verification = VcVerification.ExpirationCheck,
success = JwtCredential
.validateExpiration(JWT(credential))
Expand All @@ -119,9 +149,10 @@ class VcVerificationServiceImpl(didResolver: DidResolver, uriDereferencer: URIDe
)
}

private def verifyNotBefore(credential: String): IO[VcVerificationServiceError, VcVerificationOutcome] = {
private def verifyNotBefore(credential: String): IO[VcVerificationServiceError, VcVerificationResult] = {
ZIO.succeed(
VcVerificationOutcome(
VcVerificationResult(
credential = credential,
verification = VcVerification.NotBeforeCheck,
success = JwtCredential
.validateNotBefore(JWT(credential))
Expand All @@ -131,9 +162,10 @@ class VcVerificationServiceImpl(didResolver: DidResolver, uriDereferencer: URIDe
)
}

private def verifyAlgorithm(credential: String): IO[VcVerificationServiceError, VcVerificationOutcome] = {
private def verifyAlgorithm(credential: String): IO[VcVerificationServiceError, VcVerificationResult] = {
ZIO.succeed(
VcVerificationOutcome(
VcVerificationResult(
credential = credential,
verification = VcVerification.AlgorithmVerification,
success = JWTVerification
.validateAlgorithm(JWT(credential))
Expand All @@ -143,19 +175,61 @@ class VcVerificationServiceImpl(didResolver: DidResolver, uriDereferencer: URIDe
)
}

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

private def verifySemanticCheckOfClaims(credential: String): IO[VcVerificationServiceError, VcVerificationResult] = {
val result =
for {
decodedJwt <-
JwtCredential
.decodeJwt(JWT(credential))
.mapError(error => VcVerificationServiceError.UnexpectedError(s"Unable decode JWT: $error"))
} yield decodedJwt

result
.as(
VcVerificationResult(
credential = credential,
verification = VcVerification.SubjectVerification,
success = true
)
)
.catchAll(_ =>
ZIO.succeed(
VcVerificationResult(
credential = credential,
verification = VcVerification.SubjectVerification,
success = false
)
)
)
}

private def verifyAudienceCheck(
credential: String,
aud: String
): IO[VcVerificationServiceError, VcVerificationResult] = {
ZIO.succeed(
VcVerificationResult(
credential = credential,
verification = VcVerification.SubjectVerification,
success = true
)
)
}
}

object VcVerificationServiceImpl {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ object JWTVerification {
.fromOptionWith("An algorithm must be specified in the header")(JwtCirce.parseHeader(header).algorithm)
result <-
Validation
.fromPredicateWith("No PublicKey to validate against found")(
.fromPredicateWith("Algorithm Not Supported")(
SUPPORT_PUBLIC_KEY_TYPES.getOrElse(algorithm.name, Set.empty)
)(_.nonEmpty)
.flatMap(_ => Validation.unit)
Expand Down
Loading

0 comments on commit 4d128a7

Please sign in to comment.