Skip to content

Commit

Permalink
WIP progress
Browse files Browse the repository at this point in the history
Signed-off-by: mineme0110 <shailesh.patil@iohk.io>
  • Loading branch information
mineme0110 committed May 20, 2024
1 parent b497297 commit 46313eb
Show file tree
Hide file tree
Showing 10 changed files with 291 additions and 67 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -203,14 +203,10 @@ final case class ManagedDIDKeyTemplate(
@description(ManagedDIDKeyTemplate.annotations.purpose.description)
@encodedExample(ManagedDIDKeyTemplate.annotations.purpose.example)
purpose: Purpose,
// @description(ManagedDIDKeyTemplate.annotations.curve.description)
// @encodedExample(ManagedDIDKeyTemplate.annotations.curve.example)
// curve: Option[Curve]
) {
// TODO: this curve option is hidden for now, to be added back after integration test with node
def curve: Option[Curve] = None
}

@description(ManagedDIDKeyTemplate.annotations.curve.description)
@encodedExample(ManagedDIDKeyTemplate.annotations.curve.example)
curve: Option[Curve]
)
object ManagedDIDKeyTemplate {
object annotations {
object id
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ object RequestPresentationInput {
validator = Validator.enumeration(
List(
Some("JWT"),
Some("vc+sd-jwt"),
Some("AnonCreds")
)
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.hyperledger.identus.pollux.core.service

import com.nimbusds.jose.jwk.OctetKeyPair
import io.circe.Json
import io.circe.syntax.*
import org.hyperledger.identus.agent.walletapi.model.{ManagedDIDState, PublicationState}
Expand Down Expand Up @@ -32,13 +33,15 @@ import org.hyperledger.identus.shared.utils.aspects.CustomMetricsAspect
import zio.*
import zio.prelude.ZValidation
import org.hyperledger.identus.castor.core.model.did.EllipticCurve

import java.net.URI
import java.rmi.UnexpectedException
import java.time.{Instant, ZoneId}
import java.util.UUID
import scala.language.implicitConversions
import org.hyperledger.identus.pollux.sdjwt
import org.hyperledger.identus.pollux.sdjwt.*
import org.hyperledger.identus.shared.crypto.{Ed25519KeyPair, Ed25519PublicKey}

object CredentialServiceImpl {
val layer: URLayer[
Expand Down Expand Up @@ -458,6 +461,17 @@ private class CredentialServiceImpl(
s"${record.id}_issuance_flow_holder_req_pending_to_generated"
)
} yield count
case (CredentialFormat.SDJWT, Some(subjectId)) =>
for {
_ <- ZIO
.fromEither(PrismDID.fromString(subjectId))
.mapError(_ => CredentialServiceError.UnsupportedDidFormat(subjectId))
count <- credentialRepository
.updateWithSubjectId(recordId, subjectId, ProtocolState.RequestPending)
.mapError(RepositoryError.apply) @@ CustomMetricsAspect.startRecordingTime(
s"${record.id}_issuance_flow_holder_req_pending_to_generated"
)
} yield count
case (CredentialFormat.AnonCreds, None) =>
credentialRepository
.updateCredentialRecordProtocolState(recordId, ProtocolState.OfferReceived, ProtocolState.RequestPending)
Expand Down Expand Up @@ -548,13 +562,12 @@ private class CredentialServiceImpl(
)
} yield jwtIssuer
}
private[this] def getSDJwtIssuerKeys(

private[this] def getEd25519SigningKeyPair(
jwtIssuerDID: PrismDID,
verificationRelationship: VerificationRelationship
) = {
import org.hyperledger.identus.shared.crypto.Ed25519KeyPair
for {
// Automatically infer keyId to use by resolving DID and choose the corresponding VerificationRelationship
issuingKeyId <- didService
.resolveDID(jwtIssuerDID)
.mapError(e => UnexpectedError(s"Error occured while resolving Issuing DID during VC creation: ${e.toString}"))
Expand All @@ -563,14 +576,43 @@ private class CredentialServiceImpl(
.someOrFail(
UnexpectedError(s"Issuing DID doesn't have a key in ${verificationRelationship.name} to use: $jwtIssuerDID")
)
ecKeyPair <- managedDIDService
ed25519keyPair <- managedDIDService
.findDIDKeyPair(jwtIssuerDID.asCanonical, issuingKeyId)
.map(_.collect { case keyPair: Ed25519KeyPair => keyPair })
.mapError(e => UnexpectedError(s"Error occurred while getting issuer key-pair: ${e.toString}"))
.someOrFail(
UnexpectedError(s"Issuer key-pair does not exist in the wallet: ${jwtIssuerDID.toString}#$issuingKeyId")
)
} yield ecKeyPair
} yield ed25519keyPair
}

/** @param jwtIssuerDID
* This can holder prism did / issuer prism did
* @param verificationRelationship
* Holder it Authentication and Issuer it is AssertionMethod
* @return
* JwtIssuer
* @see
* org.hyperledger.identus.pollux.vc.jwt.Issuer
*/
private[this] def getSDJwtIssuer(
jwtIssuerDID: PrismDID,
verificationRelationship: VerificationRelationship
): ZIO[WalletAccessContext, UnexpectedError, JwtIssuer] = {
for {
ed25519keyPair <- getEd25519SigningKeyPair(jwtIssuerDID, verificationRelationship)
} yield {

val d = java.util.Base64.getUrlEncoder.withoutPadding().encodeToString(ed25519keyPair.privateKey.getEncoded)
val x = java.util.Base64.getUrlEncoder.withoutPadding().encodeToString(ed25519keyPair.publicKey.getEncoded)
val okpJson = s"""{"kty":"OKP","crv":"Ed25519","d":"$d","x":"$x"}"""
val octetKeyPair = OctetKeyPair.parse(okpJson)
JwtIssuer(
org.hyperledger.identus.pollux.vc.jwt.DID(jwtIssuerDID.toString),
EdSigner(ed25519keyPair),
Ed25519PublicKey.toJavaPublicKey(ed25519keyPair.publicKey)
)
}
}

override def generateJWTCredentialRequest(
Expand Down Expand Up @@ -611,11 +653,11 @@ private class CredentialServiceImpl(
} yield record
}

// Holder requesting the credential
override def generateSDJWTCredentialRequest(
recordId: DidCommID
): ZIO[WalletAccessContext, CredentialServiceError, IssueCredentialRecord] = {
for {
// TODO similar logic generateJWTCredentialRequest
record <- getRecordWithState(recordId, ProtocolState.RequestPending)
subjectId <- ZIO
.fromOption(record.subjectId)
Expand All @@ -624,7 +666,7 @@ private class CredentialServiceImpl(
.fromEither(PrismDID.fromString(subjectId))
.mapError(_ => CredentialServiceError.UnsupportedDidFormat(subjectId))
longFormPrismDID <- getLongForm(subjectDID, true).mapError(err => UnexpectedError(err.getMessage))
jwtIssuer <- createJwtIssuer(longFormPrismDID, VerificationRelationship.Authentication)
jwtIssuer <- getSDJwtIssuer(longFormPrismDID, VerificationRelationship.Authentication)
presentationPayload <- createPresentationPayload(record, jwtIssuer)
signedPayload = JwtPresentation.encodeJwt(presentationPayload.toJwtPresentationPayload, jwtIssuer)
formatAndOffer <- ZIO
Expand Down Expand Up @@ -1278,7 +1320,7 @@ private class CredentialServiceImpl(
record <- markCredentialGenerated(record, issueCredential)
} yield record
}

// Issuer Generating the credential
override def generateSDJWTCredential(
recordId: DidCommID,
): ZIO[WalletAccessContext, CredentialServiceError, IssueCredentialRecord] = {
Expand Down Expand Up @@ -1308,7 +1350,7 @@ private class CredentialServiceImpl(
.mapError(t => RepositoryError(t)),
payload => ZIO.logInfo("JWT Presentation Validation Successful!")
)
jwtIssuerKey <- getSDJwtIssuerKeys(longFormPrismDID, VerificationRelationship.AssertionMethod)
ed25519KeyPair <- getEd25519SigningKeyPair(longFormPrismDID, VerificationRelationship.AssertionMethod)
offerCredentialData <- ZIO
.fromOption(record.offerCredentialData)
.mapError(_ =>
Expand All @@ -1318,8 +1360,7 @@ private class CredentialServiceImpl(
)
preview = offerCredentialData.body.credential_preview
claims <- CredentialService.convertAttributesToJsonClaims(preview.body.attributes)

sdJwtPrivateKey = sdjwt.IssuerPrivateKey(jwtIssuerKey.privateKey)
sdJwtPrivateKey = sdjwt.IssuerPrivateKey(ed25519KeyPair.privateKey)
credential = SDJWT.issueCredential(sdJwtPrivateKey, claims.asJson.noSpaces, ???) // FIXME

issueCredential = IssueCredential.build(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@ import org.bouncycastle.crypto.util.SubjectPublicKeyInfoFactory
import scala.util.Try
import scala.util.Failure
import scala.util.Success
import org.bouncycastle.cert.path.CertPathValidationResult

import zio.json.*
// TODO move to apollo
private[sdjwt] object Utils {

Expand Down Expand Up @@ -134,12 +133,28 @@ object PresentationJson {
}

object SDJWT {

given encoder: JsonEncoder[String | Int] = new JsonEncoder[String | Int] {
override def unsafeEncode(b: String | Int, indent: Option[Int], out: zio.json.internal.Write): Unit = {
b match {
case obj: String => JsonEncoder.string.unsafeEncode(obj, indent, out)
case obj: Int => JsonEncoder.int.unsafeEncode(obj, indent, out)
}
}
}
def issueCredential(
issueKey: IssuerPrivateKey,
claims: String,
): CredentialJson = issueCredential(issueKey, claims, None)

def issueCredential(
issueKey: IssuerPrivateKey,
claimsMap: Map[String, String],
): CredentialJson = {
val claims = claimsMap ++
Map("sub" -> "did:example:holder", "iss" -> "did:example:issuer", "iat" -> 1683000000, "exp" -> 1883000000)
issueCredential(issueKey, claims.toJson, None)
}

def issueCredential(
issueKey: IssuerPrivateKey,
claims: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,10 @@ object SDJWTSpec extends ZIOSpecDefault {
test("make presentation") {
val credential = SDJWT.issueCredential(ISSUER_KEY, CLAIMS)
val presentation = SDJWT.createPresentation(credential, CLAIMS_PRESENTED)
println("**************")
println(s"${presentation.value}")
println("**************")

assertTrue(!presentation.value.isEmpty())
},
test("verify presentation") {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
package org.hyperledger.identus.pollux.vc.jwt

import com.nimbusds.jose.{JWSAlgorithm, JWSHeader}
import com.nimbusds.jose.crypto.ECDSASigner
import com.nimbusds.jose.crypto.{ECDSASigner, Ed25519Signer}
import com.nimbusds.jose.crypto.bc.BouncyCastleProviderSingleton
import com.nimbusds.jose.jwk.{Curve, ECKey}
import com.nimbusds.jose.jwk.{Curve, ECKey, OctetKeyPair}
import com.nimbusds.jose.{JWSAlgorithm, JWSHeader}
import com.nimbusds.jwt.{JWTClaimsSet, SignedJWT}
import io.circe.*
import org.hyperledger.identus.shared.crypto.Ed25519KeyPair
import zio.*
import pdi.jwt.algorithms.JwtECDSAAlgorithm
import pdi.jwt.{JwtAlgorithm, JwtCirce}

import java.security.*
import java.util.Base64

opaque type JWT = String

Expand Down Expand Up @@ -40,7 +40,7 @@ class ES256KSigner(privateKey: PrivateKey) extends Signer {
}

override def generateProofForJson(payload: Json, pk: PublicKey): Task[Proof] = {
EddsaJcs2022ProofGenerator.generateProof(payload, privateKey, pk)
EcdsaJcs2019ProofGenerator.generateProof(payload, privateKey, pk)
}

override def encode(claim: Json): JWT = {
Expand All @@ -54,6 +54,31 @@ class ES256KSigner(privateKey: PrivateKey) extends Signer {
}
}

class EdSigner(ed25519KeyPair: Ed25519KeyPair) extends Signer {
lazy val signer: Ed25519Signer = {
val d = java.util.Base64.getUrlEncoder.withoutPadding().encodeToString(ed25519KeyPair.privateKey.getEncoded)
val x = java.util.Base64.getUrlEncoder.withoutPadding().encodeToString(ed25519KeyPair.publicKey.getEncoded)
val okpJson = s"""{"kty":"OKP","crv":"Ed25519","d":"$d","x":"$x"}"""
val octetKeyPair = OctetKeyPair.parse(okpJson)
val ed25519Signer = Ed25519Signer(octetKeyPair)
ed25519Signer
}

override def generateProofForJson(payload: Json, pk: PublicKey): Task[Proof] = {
EddsaJcs2022ProofGenerator.generateProof(payload, ed25519KeyPair)
}

override def encode(claim: Json): JWT = {
val claimSet = JWTClaimsSet.parse(claim.noSpaces)
val signedJwt = SignedJWT(
new JWSHeader.Builder(JWSAlgorithm.EdDSA).build(),
claimSet
)
signedJwt.sign(signer)
JWT(signedJwt.serialize())
}
}

def toJWKFormat(holderJwk: ECKey): JsonWebKey = {
JsonWebKey(
kty = "EC",
Expand Down
Loading

0 comments on commit 46313eb

Please sign in to comment.