• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

hyperledger / identus-cloud-agent / 10793991050

10 Sep 2024 01:56PM UTC coverage: 48.504% (-4.5%) from 52.962%
10793991050

push

web-flow
build: sbt and plugins dependency update (#1337)

Signed-off-by: Hyperledger Bot <hyperledger-bot@hyperledger.org>
Signed-off-by: Yurii Shynbuiev <yurii.shynbuiev@iohk.io>
Co-authored-by: Hyperledger Bot <hyperledger-bot@hyperledger.org>
Co-authored-by: Yurii Shynbuiev <yurii.shynbuiev@iohk.io>

7406 of 15269 relevant lines covered (48.5%)

0.49 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

53.9
/pollux/vc-jwt/src/main/scala/org/hyperledger/identus/pollux/vc/jwt/Proof.scala
1
package org.hyperledger.identus.pollux.vc.jwt
2

3
import cats.implicits.*
4
import com.nimbusds.jose.{JWSAlgorithm, JWSHeader, JWSObject, Payload}
5
import com.nimbusds.jose.crypto.ECDSASigner
6
import com.nimbusds.jwt.SignedJWT
7
import io.circe.*
8
import io.circe.syntax.*
9
import org.hyperledger.identus.shared.crypto.{Ed25519KeyPair, Ed25519PublicKey, KmpEd25519KeyOps}
10
import org.hyperledger.identus.shared.json.Json as JsonUtils
11
import org.hyperledger.identus.shared.utils.Base64Utils
12
import scodec.bits.ByteVector
13
import zio.*
14

15
import java.security.*
16
import java.security.interfaces.ECPublicKey
17
import java.time.{Instant, ZoneOffset}
18
import scala.jdk.CollectionConverters.*
19

20
sealed trait Proof {
21
  val id: Option[String] = None
22
  val `type`: String
23
  val proofPurpose: String
24
  val verificationMethod: String
25
  val created: Option[Instant] = None
26
  val domain: Option[String] = None
27
  val challenge: Option[String] = None
28
  val previousProof: Option[String] = None
29
  val nonce: Option[String] = None
30
}
31

32
trait DataIntegrityProof extends Proof {
33
  val proofValue: String
34
}
35

36
object Proof {
37
  given decodeProof: Decoder[Proof] = new Decoder[Proof] {
38
    final def apply(c: HCursor): Decoder.Result[Proof] = {
1✔
39
      val decoders: List[Decoder[Proof]] = List(
1✔
40
        Decoder[EddsaJcs2022Proof].widen,
1✔
41
        Decoder[EcdsaSecp256k1Signature2019Proof].widen,
1✔
42
        // Note: Add another proof types here when available
43
      )
44
      decoders.foldLeft(
45
        Left[DecodingFailure, Proof](DecodingFailure("Cannot decode as Proof", c.history)): Decoder.Result[Proof]
1✔
46
      ) { (acc, decoder) =>
1✔
47
        acc.orElse(decoder.tryDecode(c))
1✔
48
      }
49
    }
50
  }
51
}
52

53
object EcdsaSecp256k1Signature2019ProofGenerator {
54
  private def stripLeadingZero(arr: Array[Byte]): Array[Byte] = {
1✔
55
    if (arr.length == 33 && arr.head == 0) then arr.tail else arr
1✔
56
  }
57
  def generateProof(payload: Json, signer: ECDSASigner, pk: ECPublicKey): Task[EcdsaSecp256k1Signature2019Proof] = {
1✔
58
    for {
1✔
59
      dataToSign <- ZIO.fromEither(JsonUtils.canonicalizeJsonLDoRdf(payload.spaces2))
1✔
60
      created = Instant.now()
1✔
61
      header = new JWSHeader.Builder(JWSAlgorithm.ES256K)
1✔
62
        .base64URLEncodePayload(false)
1✔
63
        .criticalParams(Set("b64").asJava)
1✔
64
        .build()
1✔
65
      payload = Payload(dataToSign)
1✔
66
      jwsObject = JWSObject(header, payload)
1✔
67
      _ = jwsObject.sign(signer)
1✔
68
      jws = jwsObject.serialize(true)
1✔
69
      x = stripLeadingZero(pk.getW.getAffineX.toByteArray)
1✔
70
      y = stripLeadingZero(pk.getW.getAffineY.toByteArray)
1✔
71
      jwk = JsonWebKey(
1✔
72
        kty = "EC",
73
        crv = Some("secp256k1"),
74
        key_ops = Vector("verify"),
1✔
75
        x = Some(Base64Utils.encodeURL(x)),
1✔
76
        y = Some(Base64Utils.encodeURL(y)),
1✔
77
      )
78
      ecdaSecp256k1VerificationKey2019 = EcdsaSecp256k1VerificationKey2019(
1✔
79
        publicKeyJwk = jwk
80
      )
81
      verificationMethodUrl = Base64Utils.createDataUrl(
1✔
82
        ecdaSecp256k1VerificationKey2019.asJson.dropNullValues.noSpaces.getBytes,
1✔
83
        "application/json"
84
      )
85
    } yield EcdsaSecp256k1Signature2019Proof(
1✔
86
      jws = jws,
87
      verificationMethod = verificationMethodUrl,
88
      created = Some(created),
89
    )
90
  }
91

92
  def verifyProof(payload: Json, jws: String, pk: ECPublicKey): Task[Boolean] = {
1✔
93
    for {
1✔
94
      dataToVerify <- ZIO.fromEither(JsonUtils.canonicalizeJsonLDoRdf(payload.spaces2))
1✔
95
      verifier = JWTVerification.toECDSAVerifier(pk)
1✔
96
      signedJws = SignedJWT.parse(jws)
1✔
97
      header = signedJws.getHeader
1✔
98
      signature = signedJws.getSignature
1✔
99
      payload = Payload(dataToVerify)
1✔
100
      jwsObject = new SignedJWT(header.toBase64URL, payload.toBase64URL, signature)
1✔
101
      isValid = jwsObject.verify(verifier)
1✔
102
    } yield isValid
1✔
103
  }
104
}
105

106
object EddsaJcs2022ProofGenerator {
107
  private val ed25519MultiBaseHeader: Array[Byte] = Array(-19, 1) // 0xed01
×
108

109
  private def pkToMultiKey(pk: Ed25519PublicKey): MultiKey = {
×
110
    val encoded = pk.getEncoded
×
111
    val withHeader = ed25519MultiBaseHeader ++ encoded
×
112
    val base58Encoded = ByteVector.view(withHeader).toBase58
×
113
    MultiKey(publicKeyMultibase =
×
114
      Some(
115
        MultiBaseString(
116
          header = MultiBaseString.Header.Base58Btc,
117
          data = base58Encoded
118
        )
119
      )
120
    )
121
  }
122

123
  private def multiKeytoPk(multiKey: MultiKey): Either[String, Ed25519PublicKey] = {
×
124
    for {
×
125
      multiBaseStr <- multiKey.publicKeyMultibase.toRight("No public key provided inside MultiKey")
×
126
      bytesWithHeader <- multiBaseStr.getBytes
×
127
      pkBytes <- Either.cond(
×
128
        bytesWithHeader.take(2).sameElements(ed25519MultiBaseHeader),
×
129
        bytesWithHeader.drop(2),
×
130
        "Invalid multiBaseString header for ed25519"
131
      )
132
      maybePk <- Either.cond(
×
133
        pkBytes.length == 32,
×
134
        KmpEd25519KeyOps.publicKeyFromEncoded(pkBytes),
×
135
        "Invalid public key length, must be 32"
136
      )
137
      pk <- maybePk.toEither.left.map(_.getMessage)
×
138

139
    } yield pk
140
  }
141

142
  def generateProof(payload: Json, ed25519KeyPair: Ed25519KeyPair): Task[EddsaJcs2022Proof] = {
×
143
    for {
×
144
      canonicalizedJsonString <- ZIO.fromEither(JsonUtils.canonicalizeToJcs(payload.spaces2))
×
145
      canonicalizedJson <- ZIO.fromEither(parser.parse(canonicalizedJsonString))
×
146
      dataToSign = canonicalizedJson.noSpaces.getBytes
×
147
      signature = ed25519KeyPair.privateKey.sign(dataToSign)
×
148
      base58BtsEncodedSignature = MultiBaseString(
149
        header = MultiBaseString.Header.Base58Btc,
150
        data = ByteVector.view(signature).toBase58
×
151
      ).toMultiBaseString
×
152
      created = Instant.now()
×
153
      multiKey = pkToMultiKey(ed25519KeyPair.publicKey)
×
154
      verificationMethod = Base64Utils.createDataUrl(
×
155
        multiKey.asJson.dropNullValues.noSpaces.getBytes,
×
156
        "application/json"
157
      )
158
    } yield EddsaJcs2022Proof(
×
159
      proofValue = base58BtsEncodedSignature,
160
      maybeCreated = Some(created),
161
      verificationMethod = verificationMethod
162
    )
163
  }
164

165
  def verifyProof(payload: Json, proofValue: String, pk: MultiKey): IO[ParsingFailure, Boolean] = for {
×
166
    canonicalizedJsonString <- ZIO
×
167
      .fromEither(JsonUtils.canonicalizeToJcs(payload.spaces2))
×
168
      .mapError(ioError => ParsingFailure("Error Parsing canonicalized", ioError))
169
    canonicalizedJson <- ZIO
×
170
      .fromEither(parser.parse(canonicalizedJsonString))
×
171
    dataToVerify = canonicalizedJson.noSpaces.getBytes
×
172
    signature <- ZIO
×
173
      .fromEither(MultiBaseString.fromString(proofValue).flatMap(_.getBytes))
×
174
      .mapError(error =>
175
        // TODO fix RuntimeException
176
        ParsingFailure(error, new RuntimeException(error))
×
177
      )
178
    kmmPk <- ZIO
×
179
      .fromEither(multiKeytoPk(pk))
×
180
      .mapError(error =>
181
        // TODO fix RuntimeException
182
        ParsingFailure("Error Parsing MultiBaseString", new RuntimeException("Error Parsing MultiBaseString"))
×
183
      )
184

185
    isValid = verify(kmmPk, signature, dataToVerify)
×
186
  } yield isValid
×
187

188
  private def verify(publicKey: Ed25519PublicKey, signature: Array[Byte], data: Array[Byte]): Boolean = {
×
189
    publicKey.verify(data, signature).isSuccess
×
190
  }
191
}
192

193
case class EddsaJcs2022Proof(proofValue: String, verificationMethod: String, maybeCreated: Option[Instant])
194
    extends Proof
195
    with DataIntegrityProof {
196
  override val created: Option[Instant] = maybeCreated
197
  override val `type`: String = "DataIntegrityProof"
198
  override val proofPurpose: String = "assertionMethod"
199
  val cryptoSuite: String = "eddsa-jcs-2022"
200
}
201

202
object EddsaJcs2022Proof {
203
  given proofEncoder: Encoder[EddsaJcs2022Proof] =
204
    DataIntegrityProofCodecs.proofEncoder[EddsaJcs2022Proof]("eddsa-jcs-2022")
×
205

206
  given proofDecoder: Decoder[EddsaJcs2022Proof] = DataIntegrityProofCodecs.proofDecoder[EddsaJcs2022Proof](
1✔
207
    (proofValue, verificationMethod, created) => EddsaJcs2022Proof(proofValue, verificationMethod, created),
208
    "eddsa-jcs-2022"
209
  )
210
}
211

212
case class EcdsaSecp256k1Signature2019Proof(
213
    jws: String,
214
    verificationMethod: String,
215
    override val created: Option[Instant] = None,
×
216
    override val challenge: Option[String] = None,
1✔
217
    override val domain: Option[String] = None,
1✔
218
    override val nonce: Option[String] = None
1✔
219
) extends Proof {
220
  override val `type`: String = "EcdsaSecp256k1Signature2019"
221
  override val proofPurpose: String = "assertionMethod"
222
}
223

224
object EcdsaSecp256k1Signature2019Proof {
225

226
  given proofEncoder: Encoder[EcdsaSecp256k1Signature2019Proof] =
227
    (proof: EcdsaSecp256k1Signature2019Proof) =>
228
      Json
229
        .obj(
1✔
230
          ("id", proof.id.asJson),
1✔
231
          ("type", proof.`type`.asJson),
1✔
232
          ("proofPurpose", proof.proofPurpose.asJson),
1✔
233
          ("verificationMethod", proof.verificationMethod.asJson),
1✔
234
          ("created", proof.created.map(_.atOffset(ZoneOffset.UTC)).asJson),
1✔
235
          ("domain", proof.domain.asJson),
1✔
236
          ("challenge", proof.challenge.asJson),
1✔
237
          ("jws", proof.jws.asJson),
1✔
238
          ("nonce", proof.nonce.asJson),
1✔
239
        )
240

241
  given proofDecoder: Decoder[EcdsaSecp256k1Signature2019Proof] =
242
    (c: HCursor) =>
243
      for {
1✔
244
        id <- c.downField("id").as[Option[String]]
1✔
245
        `type` <- c.downField("type").as[String]
1✔
246
        proofPurpose <- c.downField("proofPurpose").as[String]
1✔
247
        verificationMethod <- c.downField("verificationMethod").as[String]
1✔
248
        created <- c.downField("created").as[Option[Instant]]
1✔
249
        domain <- c.downField("domain").as[Option[String]]
1✔
250
        challenge <- c.downField("challenge").as[Option[String]]
1✔
251
        jws <- c.downField("jws").as[String]
1✔
252
        nonce <- c.downField("nonce").as[Option[String]]
1✔
253
      } yield {
254
        EcdsaSecp256k1Signature2019Proof(
255
          jws = jws,
256
          verificationMethod = verificationMethod,
257
          created = created,
258
          challenge = challenge,
259
          domain = domain,
260
          nonce = nonce
261
        )
262
      }
263
}
264

265
object DataIntegrityProofCodecs {
266
  def proofEncoder[T <: DataIntegrityProof](cryptoSuiteValue: String): Encoder[T] = (proof: T) =>
×
267
    Json.obj(
×
268
      ("id", proof.id.asJson),
×
269
      ("type", proof.`type`.asJson),
×
270
      ("proofPurpose", proof.proofPurpose.asJson),
×
271
      ("verificationMethod", proof.verificationMethod.asJson),
×
272
      ("created", proof.created.map(_.atOffset(ZoneOffset.UTC)).asJson),
×
273
      ("domain", proof.domain.asJson),
×
274
      ("challenge", proof.challenge.asJson),
×
275
      ("proofValue", proof.proofValue.asJson),
×
276
      ("cryptoSuite", Json.fromString(cryptoSuiteValue)),
×
277
      ("previousProof", proof.previousProof.asJson),
×
278
      ("nonce", proof.nonce.asJson)
×
279
    )
280

281
  def proofDecoder[T <: DataIntegrityProof](
1✔
282
      createProof: (String, String, Option[Instant]) => T,
283
      cryptoSuiteValue: String
284
  ): Decoder[T] =
285
    (c: HCursor) =>
286
      for {
1✔
287
        id <- c.downField("id").as[Option[String]]
1✔
288
        `type` <- c.downField("type").as[String]
1✔
289
        proofPurpose <- c.downField("proofPurpose").as[String]
1✔
290
        verificationMethod <- c.downField("verificationMethod").as[String]
1✔
291
        created <- c.downField("created").as[Option[Instant]]
1✔
292
        domain <- c.downField("domain").as[Option[String]]
1✔
293
        challenge <- c.downField("challenge").as[Option[String]]
1✔
294
        proofValue <- c.downField("proofValue").as[String]
1✔
295
        previousProof <- c.downField("previousProof").as[Option[String]]
×
296
        nonce <- c.downField("nonce").as[Option[String]]
×
297
        cryptoSuite <- c.downField("cryptoSuite").as[String]
×
298
      } yield createProof(proofValue, verificationMethod, created)
×
299
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc