From b8d24699d3d85984f9bd122608d33c7c4de736b3 Mon Sep 17 00:00:00 2001 From: FabioPinheiro Date: Mon, 6 May 2024 15:43:17 +0100 Subject: [PATCH] feat: integrate SD JWT --- build.sbt | 20 +++- .../identus/pollux/sdjwt/SDJWT.scala | 71 +++++++++++++++ .../identus/pollux/sdjwt/SDJWTSpec.scala | 91 +++++++++++++++++++ 3 files changed, 178 insertions(+), 4 deletions(-) create mode 100644 pollux/sd-jwt/src/main/scala/org/hyperledger/identus/pollux/sdjwt/SDJWT.scala create mode 100644 pollux/sd-jwt/src/test/scala/org/hyperledger/identus/pollux/sdjwt/SDJWTSpec.scala diff --git a/build.sbt b/build.sbt index 655eac4939..acf5373a57 100644 --- a/build.sbt +++ b/build.sbt @@ -163,6 +163,7 @@ lazy val D = new { val mockito: ModuleID = "org.scalatestplus" %% "mockito-4-11" % V.mockito % Test val monocle: ModuleID = "dev.optics" %% "monocle-core" % V.monocle % Test val monocleMacro: ModuleID = "dev.optics" %% "monocle-macro" % V.monocle % Test + val scalaTest = "org.scalatest" %% "scalatest" % "3.2.16" % Test val apollo = "io.iohk.atala.prism.apollo" % "apollo-jvm" % V.apollo // We have to exclude bouncycastle since for some reason bitcoinj depends on bouncycastle jdk15to18 @@ -329,12 +330,10 @@ lazy val D_Pollux_VC_JWT = new { val zioTestSbt = "dev.zio" %% "zio-test-sbt" % V.zio % Test val zioTestMagnolia = "dev.zio" %% "zio-test-magnolia" % V.zio % Test - val scalaTest = "org.scalatest" %% "scalatest" % "3.2.16" % Test - // Dependency Modules val zioDependencies: Seq[ModuleID] = Seq(zio, zioPrelude, zioTest, zioTestSbt, zioTestMagnolia) val baseDependencies: Seq[ModuleID] = - zioDependencies :+ D.jwtCirce :+ circeJsonSchema :+ networkntJsonSchemaValidator :+ D.nimbusJwt :+ scalaTest + zioDependencies :+ D.jwtCirce :+ circeJsonSchema :+ networkntJsonSchemaValidator :+ D.nimbusJwt :+ D.scalaTest // Project Dependencies lazy val polluxVcJwtDependencies: Seq[ModuleID] = baseDependencies @@ -434,6 +433,7 @@ publish / skip := true val commonSetttings = Seq( testFrameworks ++= Seq(new TestFramework("zio.test.sbt.ZTestFramework")), + libraryDependencies ++= Seq(D.zioTest, D.zioTestSbt, D.zioTestMagnolia), // Needed for Kotlin coroutines that support new memory management mode resolvers += "JetBrains Space Maven Repository" at "https://maven.pkg.jetbrains.space/public/p/kotlinx-coroutines/maven", resolvers += "jitpack" at "https://jitpack.io", @@ -776,9 +776,20 @@ lazy val polluxAnoncreds = project lazy val polluxAnoncredsTest = project .in(file("pollux/anoncredsTest")) - .settings(libraryDependencies ++= Seq("org.scalatest" %% "scalatest" % "3.2.15" % Test)) + .settings(libraryDependencies += D.scalaTest) .dependsOn(polluxAnoncreds % "compile->test") +lazy val polluxSDJWT = project + .in(file("pollux/sd-jwt")) + .settings(commonSetttings) + .settings( + name := "pollux-sd-jwt", + resolvers += Resolver.mavenLocal, // TODO REMOVE + libraryDependencies += "io.iohk" % "sd-jwt-kmp-jvm" % "1.0-SNAPSHOT", + // libraryDependencies ++= Seq(D.zio, D.zioTest) + ) + .dependsOn(sharedCrypto) + // ##################### // ##### connect ##### // ##################### @@ -919,6 +930,7 @@ lazy val aggregatedProjects: Seq[ProjectReference] = Seq( polluxDoobie, polluxAnoncreds, polluxAnoncredsTest, + polluxSDJWT, connectCore, connectDoobie, agentWalletAPI, 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 new file mode 100644 index 0000000000..0a1ca0f4ae --- /dev/null +++ b/pollux/sd-jwt/src/main/scala/org/hyperledger/identus/pollux/sdjwt/SDJWT.scala @@ -0,0 +1,71 @@ +package org.hyperledger.identus.pollux.sdjwt + +import sd_jwt_rs.* + +opaque type SDJWTPublicKey = String +object SDJWTPublicKey: + def fromPem(pemKey: String): SDJWTPublicKey = pemKey + extension (pemKey: SDJWTPublicKey) + def value: String = pemKey + def pem: String = pemKey + +opaque type SDJWTPrivateKey = String +object SDJWTPrivateKey: + def fromPem(pemKey: String): SDJWTPrivateKey = pemKey + // def makeNew = { + // // val pr = org.hyperledger.identus.shared.crypto.Apollo.default.ed25519.generateKeyPair + // } + extension (keyPem: SDJWTPrivateKey) + def value: String = keyPem + def encodingKey = new sd_jwt_rs.EncodingKeyValue(keyPem.value) + +opaque type SDJWTCredentialJson = String +object SDJWTCredentialJson: + def apply(value: String): SDJWTCredentialJson = value + extension (c: SDJWTCredentialJson) + def value: String = c + // + +opaque type SDJWTPresentationJson = String +object SDJWTPresentationJson: + def apply(value: String): SDJWTPresentationJson = value + extension (c: SDJWTPresentationJson) + def value: String = c + // + +object SDJWT { + + def issueCredential(key: SDJWTPrivateKey, claims: String): SDJWTCredentialJson = { + val issuer = new SdjwtIssuerWrapper(key.encodingKey, "ES256") // null) + val sdjwt = issuer.issueSdJwtAllLevel( + claims, // user_claims + null, // holder_key + false, // add_decoy_claims + SdjwtSerializationFormat.JSON // COMPACT // serialization_format + ) + SDJWTCredentialJson(sdjwt) + } + def createPresentation(sdjwt: SDJWTCredentialJson, claimsToDisclose: String): SDJWTPresentationJson = { + val holder = SdjwtHolderWrapper(sdjwt.value, SdjwtSerializationFormat.JSON) + val presentation = holder.createPresentation( + claimsToDisclose, + null, // nonce + null, // aud + null, // holder_key + null, // sign_alg + ) + SDJWTPresentationJson(presentation) + } + + def verifiePresentation(key: SDJWTPublicKey, presentation: SDJWTPresentationJson, claims: String) = { + val verifier = SdjwtVerifierWrapper( + presentation.value, // sd_jwt_presentation + key.pem, // public_key + null, // expected_aud + null, // expected_nonce + SdjwtSerializationFormat.JSON // serialization_format + ) + val result: Boolean = verifier.verify(claims) + result + } +} 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 new file mode 100644 index 0000000000..81d08aebb6 --- /dev/null +++ b/pollux/sd-jwt/src/test/scala/org/hyperledger/identus/pollux/sdjwt/SDJWTSpec.scala @@ -0,0 +1,91 @@ +package org.hyperledger.identus.pollux.sdjwt + +import sd_jwt_rs.* + +import zio.* +import zio.test.* +import zio.test.Assertion.* +import org.hyperledger.identus.pollux.sdjwt.* + +val ISSUER_KEY = SDJWTPrivateKey.fromPem( + """-----BEGIN PRIVATE KEY----- + |MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgUr2bNKuBPOrAaxsR + |nbSH6hIhmNTxSGXshDSUD1a1y7ihRANCAARvbx3gzBkyPDz7TQIbjF+ef1IsxUwz + |X1KWpmlVv+421F7+c1sLqGk4HUuoVeN8iOoAcE547pJhUEJyf5Asc6pP + |-----END PRIVATE KEY----- + |""".stripMargin +) + +val ISSUER_KEY_PUBLIC = SDJWTPublicKey.fromPem( + """-----BEGIN PUBLIC KEY----- + MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEb28d4MwZMjw8+00CG4xfnn9SLMVM + M19SlqZpVb/uNtRe/nNbC6hpOB1LqFXjfIjqAHBOeO6SYVBCcn+QLHOqTw== + -----END PUBLIC KEY----- + |""".stripMargin +) + +val CLAIMS = + """{ + | "sub": "6c5c0a49-b589-431d-bae7-219122a9ec2c", + | "iss": "https://example.com/issuer", + | "iat": 1683000000, + | "exp": 1883000000, + | "address": { + | "street_address": "Schulstr. 12", + | "locality": "Schulpforta", + | "region": "Sachsen-Anhalt", + | "country": "DE" + | } + |}""".stripMargin + +val CLAIMS_PRESENTED = + """{ + | "sub": "6c5c0a49-b589-431d-bae7-219122a9ec2c", + | "iss": "https://example.com/issuer", + | "iat": 1683000000, + | "exp": 1883000000, + | "address": { + | "region": "Sachsen-Anhalt", + | "country": "DE" + | } + |}""".stripMargin + +val FAlSE_CLAIMS_PRESENTED = + """{ + | "sub": "6c5c0a49-b589-431d-bae7-219122a9ec2c", + | "iss": "https://example.com/issuer", + | "iat": 1683000000, + | "exp": 1883000000, + | "address": { + | "region": "Sachsen-Anhalt", + | "country": "PT" + | } + |}""".stripMargin + +object SDJWTSpec extends ZIOSpecDefault { + + override def spec = suite("SDJWTRawSpec")( + test("issue credential") { + val credential = SDJWT.issueCredential(ISSUER_KEY, CLAIMS) + assertTrue(!credential.value.isEmpty()) + }, + test("make presentation") { + val credential = SDJWT.issueCredential(ISSUER_KEY, CLAIMS) + val presentation = SDJWT.createPresentation(credential, CLAIMS_PRESENTED) + assertTrue(!presentation.value.isEmpty()) + }, + test("verifie presentation") { + val credential = SDJWT.issueCredential(ISSUER_KEY, CLAIMS) + val presentation = SDJWT.createPresentation(credential, CLAIMS_PRESENTED) + val ret = SDJWT.verifiePresentation(ISSUER_KEY_PUBLIC, presentation, CLAIMS_PRESENTED) + assertTrue(ret) + }, + test("fail to verifie false claimes presentation") { + val credential = SDJWT.issueCredential(ISSUER_KEY, CLAIMS) + val presentation = SDJWT.createPresentation(credential, CLAIMS_PRESENTED) + val ret = SDJWT.verifiePresentation(ISSUER_KEY_PUBLIC, presentation, FAlSE_CLAIMS_PRESENTED) + assertTrue(!ret) + }, + ) + +}