Skip to content

Commit

Permalink
[ATL-1342] feat(pollux): Credential Json Decoding. JWT Crendential En…
Browse files Browse the repository at this point in the history
…coding and Validation (#65)

[ATL-1342] feat(pollux): Added JWT And W3C Credential Json Decoding. Added JWT Crendential Encoding and Validation. Added New Demo
  • Loading branch information
CryptoKnightIOG authored Oct 17, 2022
1 parent 6f5534d commit c4440df
Show file tree
Hide file tree
Showing 7 changed files with 330 additions and 38 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import cats.implicits.*
import io.circe.Json
import pdi.jwt.algorithms.JwtECDSAAlgorithm

case class JWT(jwt: String)
case class EncodedJWT(jwt: String)
case class JWTHeader(typ: String = "JWT", alg: Option[String])
case class JWTPayload(
iss: Option[String],
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,26 @@
package io.iohk.atala.pollux.vc.jwt

import cats.data.{Validated, ValidatedNel}
import cats.implicits._
import cats.Applicative
import pdi.jwt.Jwt
import io.circe.generic.auto._, io.circe.syntax._
import cats.data.{Validated, ValidatedNel}
import cats.implicits.*
import io.circe
import io.circe.generic.auto.*
import io.circe.parser.decode
import io.circe.syntax.*
import io.circe.{Decoder, Encoder, HCursor, Json}
import pdi.jwt.{Jwt, JwtCirce}

import java.security.{KeyPairGenerator, PublicKey}
import java.time.{Instant, ZonedDateTime}
import scala.util.{Failure, Success, Try}

case class Proof(`type`: String)

trait Verifiable(proof: Proof)

case class IssuerDID(id: String)
case class Issuer(did: IssuerDID, signer: Signer, publicKey: PublicKey)

trait W3CCredential(
`@context`: Vector[String],
`type`: Vector[String],
Expand All @@ -23,22 +30,28 @@ trait W3CCredential(
)

trait VerifiableCredential

trait W3CVerifiableCredential extends W3CCredential, Verifiable
case class JwtVerifiableCredential(jwt: JWT) extends VerifiableCredential

case class JwtVerifiableCredential(jwt: EncodedJWT) extends VerifiableCredential

trait VerifiedCredential extends JWTVerified, W3CVerifiableCredential

case class CredentialStatus(
id: String,
`type`: String
)

case class RefreshService(
id: String,
`type`: String
)

case class CredentialSchema(
id: String,
`type`: String
)

sealed trait CredentialPayload(
maybeSub: Option[String],
`@context`: Vector[String],
Expand Down Expand Up @@ -86,7 +99,7 @@ sealed trait CredentialPayload(
).mapN((iss, nbf) =>
W3CCredentialPayload(
`@context` = `@context`.distinct,
id = maybeJti,
maybeId = maybeJti,
`type` = `type`.distinct,
issuer = IssuerDID(id = iss),
issuanceDate = nbf,
Expand All @@ -96,7 +109,8 @@ sealed trait CredentialPayload(
maybeCredentialStatus = maybeConnectionStatus,
maybeRefreshService = maybeRefreshService,
maybeEvidence = maybeEvidence,
maybeTermsOfUse = maybeTermsOfUse
maybeTermsOfUse = maybeTermsOfUse,
aud = aud
)
)
}
Expand Down Expand Up @@ -136,10 +150,9 @@ case class JwtCredentialPayload(
credentialSubject = vc.credentialSubject
)

object JwtCredentialPayload {}
case class W3CCredentialPayload(
`@context`: Vector[String],
id: Option[String],
maybeId: Option[String],
`type`: Vector[String],
issuer: IssuerDID,
issuanceDate: Instant,
Expand All @@ -149,14 +162,17 @@ case class W3CCredentialPayload(
maybeCredentialStatus: Option[CredentialStatus],
maybeRefreshService: Option[RefreshService],
maybeEvidence: Option[Json],
maybeTermsOfUse: Option[Json]
maybeTermsOfUse: Option[Json],

/** Not part of W3C Credential but included to preserve in case of conversion from JWT. */
aud: Vector[String] = Vector.empty
) extends CredentialPayload(
maybeSub = credentialSubject.hcursor.downField("id").as[String].toOption,
`@context` = `@context`.distinct,
`type` = `type`.distinct,
maybeJti = id,
maybeJti = maybeId,
maybeNbf = Some(issuanceDate),
aud = Vector.empty,
aud = aud,
maybeExp = maybeExpirationDate,
maybeIss = Some(issuer.id),
maybeConnectionStatus = maybeCredentialStatus,
Expand Down Expand Up @@ -199,9 +215,9 @@ object VerifiedCredentialJson {
Json
.obj(
("@context", w3cCredentialPayload.`@context`.asJson),
("id", w3cCredentialPayload.id.asJson),
("id", w3cCredentialPayload.maybeId.asJson),
("type", w3cCredentialPayload.`type`.asJson),
("issuer", Json.fromString(w3cCredentialPayload.issuer.id)),
("issuer", w3cCredentialPayload.issuer.id.asJson),
("issuanceDate", w3cCredentialPayload.issuanceDate.asJson),
("expirationDate", w3cCredentialPayload.maybeExpirationDate.asJson),
("credentialSchema", w3cCredentialPayload.maybeCredentialSchema.asJson),
Expand Down Expand Up @@ -247,18 +263,132 @@ object VerifiedCredentialJson {
}
}

object Decoders {
object Implicits {
implicit val refreshServiceDecoder: Decoder[RefreshService] =
(c: HCursor) =>
for {
id <- c.downField("id").as[String]
`type` <- c.downField("type").as[String]
} yield {
RefreshService(id = id, `type` = `type`)
}

implicit val credentialSchemaDecoder: Decoder[CredentialSchema] =
(c: HCursor) =>
for {
id <- c.downField("id").as[String]
`type` <- c.downField("type").as[String]
} yield {
CredentialSchema(id = id, `type` = `type`)
}

implicit val credentialStatusEncoder: Decoder[CredentialStatus] =
(c: HCursor) =>
for {
id <- c.downField("id").as[String]
`type` <- c.downField("type").as[String]
} yield {
CredentialStatus(id = id, `type` = `type`)
}

implicit val w3cCredentialPayloadEncoder: Decoder[W3CCredentialPayload] =
(c: HCursor) =>
for {
`@context` <- c.downField("@context").as[Vector[String]]
maybeId <- c.downField("id").as[Option[String]]
`type` <- c.downField("type").as[Vector[String]]
issuer <- c.downField("issuer").as[String]
issuanceDate <- c.downField("issuanceDate").as[Instant]
maybeExpirationDate <- c.downField("expirationDate").as[Option[Instant]]
maybeCredentialSchema <- c.downField("credentialSchema").as[Option[CredentialSchema]]
credentialSubject <- c.downField("credentialSubject").as[Json]
maybeCredentialStatus <- c.downField("credentialStatus").as[Option[CredentialStatus]]
maybeRefreshService <- c.downField("refreshService").as[Option[RefreshService]]
maybeEvidence <- c.downField("evidence").as[Option[Json]]
maybeTermsOfUse <- c.downField("termsOfUse").as[Option[Json]]
} yield {
W3CCredentialPayload(
`@context` = `@context`.distinct,
maybeId = maybeId,
`type` = `type`.distinct,
issuer = IssuerDID(id = issuer),
issuanceDate = issuanceDate,
maybeExpirationDate = maybeExpirationDate,
maybeCredentialSchema = maybeCredentialSchema,
credentialSubject = credentialSubject,
maybeCredentialStatus = maybeCredentialStatus,
maybeRefreshService = maybeRefreshService,
maybeEvidence = maybeEvidence,
maybeTermsOfUse = maybeTermsOfUse,
aud = Vector.empty
)
}

implicit val jwtVcDecoder: Decoder[JwtVc] =
(c: HCursor) =>
for {
`@context` <- c.downField("@context").as[Vector[String]]
`type` <- c.downField("type").as[Vector[String]]
maybeCredentialSchema <- c.downField("credentialSchema").as[Option[CredentialSchema]]
credentialSubject <- c.downField("credentialSubject").as[Json]
maybeCredentialStatus <- c.downField("credentialStatus").as[Option[CredentialStatus]]
maybeRefreshService <- c.downField("refreshService").as[Option[RefreshService]]
maybeEvidence <- c.downField("evidence").as[Option[Json]]
maybeTermsOfUse <- c.downField("termsOfUse").as[Option[Json]]
} yield {
JwtVc(
`@context` = `@context`.distinct,
`type` = `type`.distinct,
maybeCredentialSchema = maybeCredentialSchema,
credentialSubject = credentialSubject,
maybeCredentialStatus = maybeCredentialStatus,
maybeRefreshService = maybeRefreshService,
maybeEvidence = maybeEvidence,
maybeTermsOfUse = maybeTermsOfUse
)
}

implicit val jwtCredentialPayloadDecoder: Decoder[JwtCredentialPayload] =
(c: HCursor) =>
for {
maybeIss <- c.downField("iss").as[Option[String]]
maybeSub <- c.downField("sub").as[Option[String]]
vc <- c.downField("vc").as[JwtVc]
maybeNbf <- c.downField("nbf").as[Option[Instant]]
maybeAud <- c.downField("aud").as[Option[Vector[String]]]
maybeExp <- c.downField("exp").as[Option[Instant]]
maybeJti <- c.downField("jti").as[Option[String]]
} yield {
JwtCredentialPayload(
maybeIss = maybeIss,
maybeSub = maybeSub,
vc = vc,
maybeNbf = maybeNbf,
aud = maybeAud.orEmpty,
maybeExp = maybeExp,
maybeJti = maybeJti
)
}
}
}
}

object VerifiableCredential {
import VerifiedCredentialJson.Encoders.Implicits._
def createJwt(payload: JwtCredentialPayload, issuer: Issuer): JWT = {
val jwtCredentialPayload = payload
/* val validationResult = jwtCredentialPayload
.map(payload => issuer.signer.encode(payload.toJson))
*/
JWT("")
object JwtVerifiableCredential {

import VerifiedCredentialJson.Decoders.Implicits.*
import VerifiedCredentialJson.Encoders.Implicits.*

def encodeJwt(payload: JwtCredentialPayload, issuer: Issuer): EncodedJWT =
EncodedJWT(jwt = issuer.signer.encode(payload.asJson))

def toEncodedJwt(payload: W3CCredentialPayload, issuer: Issuer): EncodedJWT =
encodeJwt(payload.toJwtCredentialPayload, issuer)

def decodeJwt(encodedJWT: EncodedJWT, publicKey: PublicKey): Try[JwtCredentialPayload] = {
JwtCirce.decodeRaw(encodedJWT.jwt, publicKey).flatMap(decode[JwtCredentialPayload](_).toTry)
}

def createJsonW3CCredential(payload: W3CCredentialPayload): Json =
payload.asJson
def validateEncodedJwt(encodedJWT: EncodedJWT, publicKey: PublicKey): Boolean =
JwtCirce.isValid(encodedJWT.jwt, publicKey)
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@ trait W3CPresentation(

trait VerifiablePresentation
trait W3CVerifiablePresentation extends W3CPresentation, Verifiable
trait JWTVerifiablePresentation(jwt: JWT) extends VerifiablePresentation
trait JWTVerifiablePresentation(jwt: EncodedJWT) extends VerifiablePresentation
trait VerifiedPresentation extends JWTVerified, W3CVerifiablePresentation
Original file line number Diff line number Diff line change
@@ -1,14 +1,23 @@
package io.iohk.atala.pollux.vc.jwt.demos
import io.iohk.atala.pollux.vc.jwt.{CredentialSchema, CredentialStatus, IssuerDID, RefreshService, W3CCredentialPayload}
import io.iohk.atala.pollux.vc.jwt.VerifiedCredentialJson.Encoders.Implicits._
import io.iohk.atala.pollux.vc.jwt.{
CredentialSchema,
CredentialStatus,
IssuerDID,
JwtCredentialPayload,
RefreshService,
W3CCredentialPayload
}
import io.iohk.atala.pollux.vc.jwt.VerifiedCredentialJson.Encoders.Implicits.*
import io.iohk.atala.pollux.vc.jwt.VerifiedCredentialJson.Decoders.Implicits.*
import cats.implicits.*
import io.circe.*
import net.reactivecore.cjs.resolver.Downloader
import net.reactivecore.cjs.{DocumentValidator, Loader, Result}
import pdi.jwt.{JwtAlgorithm, JwtCirce, JwtClaim}

import io.circe.generic.auto._, io.circe.syntax._
import io.circe.generic.auto.*
import io.circe.syntax.*
import io.circe.{Decoder, Encoder, HCursor, Json}
import io.circe.parser.decode

import java.security.*
import java.security.spec.*
Expand All @@ -17,7 +26,7 @@ import java.time.{Instant, ZonedDateTime}
@main def CredentialDemo(): Unit =
val w3cCredentialPayload = W3CCredentialPayload(
`@context` = Vector("https://www.w3.org/2018/credentials/v1", "https://www.w3.org/2018/credentials/examples/v1"),
id = Some("http://example.edu/credentials/3732"),
maybeId = Some("http://example.edu/credentials/3732"),
`type` = Vector("VerifiableCredential", "UniversityDegreeCredential"),
issuer = IssuerDID(id = "https://example.edu/issuers/565049"),
issuanceDate = Instant.parse("2010-01-01T00:00:00Z"),
Expand Down Expand Up @@ -48,21 +57,62 @@ import java.time.{Instant, ZonedDateTime}
maybeEvidence = Option.empty,
maybeTermsOfUse = Option.empty
)

println("")
println("==================")
println("W3C => W3C Json")
println("==================")
val w3cJson = w3cCredentialPayload.asJson.toString()
println(w3cJson)

println("")
println("==================")
println("W3C")
println("W3C Json => W3C")
println("==================")
println(w3cCredentialPayload.asJson.toString())
val decodedW3CJson = decode[W3CCredentialPayload](w3cJson).toOption.get
println(decodedW3CJson)

println("")
println("==================")
println("W3C => JWT")
println("==================")
val jwtCredentialPayload = w3cCredentialPayload.toJwtCredentialPayload
println(jwtCredentialPayload.asJson.toString())
println(jwtCredentialPayload)

println("")
println("==================")
println("JWT => JWT+AUD")
println("==================")
val jwtAudCredentialPayload =
jwtCredentialPayload.copy(aud =
Vector("did:example:4a57546973436f6f6c4a4a57573", "did:example:s7dfsd86f5sd6fsdf6sfs6d5sdf")
)
println(jwtAudCredentialPayload)

println("")
println("==================")
println("JWT+AUD => JWT+AUD Json")
println("==================")
val jwtAudCredentialJson = jwtAudCredentialPayload.asJson.toString()
println(jwtAudCredentialJson)

println("")
println("==================")
println("JWT+AUD Json => JWT+AUD ")
println("==================")
val decodedJwtAudCredentialPayload = decode[JwtCredentialPayload](jwtAudCredentialJson).toOption.get
println(decodedJwtAudCredentialPayload)

println("")
println("==================")
println("JWT+AUD => W3C")
println("==================")
val convertedJwtAudToW3CCredential = decodedJwtAudCredentialPayload.toW3CCredentialPayload
println(convertedJwtAudToW3CCredential.toOption.get.asJson.toString())

println("")
println("==================")
println("JWT => W3C")
println("W3C => JWT+AUD")
println("==================")
jwtCredentialPayload.toW3CCredentialPayload.foreach(payload => println(payload.asJson.toString()))
val convertedW3CToJwtAudCredential = convertedJwtAudToW3CCredential.toOption.get.toJwtCredentialPayload
println(convertedW3CToJwtAudCredential.asJson.toString())
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
package io.iohk.atala.pollux.vc.jwt
package io.iohk.atala.pollux.vc.jwt.demos

import io.circe.*
import pdi.jwt.JwtClaim
import pdi.jwt.{JwtAlgorithm, JwtCirce}
Expand Down
Loading

0 comments on commit c4440df

Please sign in to comment.