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

input-output-hk / atala-prism-building-blocks / 8719666590

17 Apr 2024 09:33AM UTC coverage: 31.196% (-0.3%) from 31.477%
8719666590

Pull #966

patlo-iog
feat: key management for Ed25519 and X25519

fix: support jwk okp for ECCompressKeyData in DID doc

feat: store random key via jdbc

build: move json from shared to castor

fix: store actual jwk with RLS added

chore: remove redendant implicits

wip: get key metadata

wip: read key from jdbc secret storage

chore: remove in-memory impl

chore: use external lib for jwk

feat: get keypair of any type

fix: non secret sql

feat: support update-operation with new key type

chore: cleanup

fix: managed did curve usage validation

feat: store did secret vault impl

chore: cleanup
Signed-off-by: Pat Losoponkul <pat.losoponkul@iohk.io>
Pull Request #966: feat: key management for Ed25519 and X25519

109 of 352 new or added lines in 22 files covered. (30.97%)

340 existing lines in 109 files now uncovered.

4490 of 14393 relevant lines covered (31.2%)

0.31 hits per line

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

63.38
/castor/lib/core/src/main/scala/io/iohk/atala/castor/core/model/did/w3c/W3CModelHelper.scala
1
package io.iohk.atala.castor.core.model.did.w3c
2

3
import io.circe.Json
4
import io.iohk.atala.castor.core.model.did.*
5
import io.iohk.atala.castor.core.model.did.ServiceEndpoint.UriOrJsonEndpoint
6
import io.iohk.atala.shared.crypto.Apollo
7
import io.iohk.atala.shared.models.Base64UrlString
8
import io.iohk.atala.shared.models.HexString
9

10
import java.time.Instant
11
import java.time.ZoneOffset
12
import java.time.format.DateTimeFormatter
13

14
object W3CModelHelper extends W3CModelHelper
15

16
private[castor] trait W3CModelHelper {
17

1✔
18
  private val XML_DATETIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'Z'")
19

×
20
  private def toXmlDateTime(time: Instant): String = {
×
21
    val zonedDateTime = time.atZone(ZoneOffset.UTC)
×
22
    XML_DATETIME_FORMATTER.format(zonedDateTime)
23
  }
24

25
  extension (didMetadata: DIDMetadata) {
1✔
26
    def toW3C: DIDDocumentMetadataRepr = DIDDocumentMetadataRepr(
27
      deactivated = didMetadata.deactivated,
1✔
28
      canonicalId = didMetadata.canonicalId.map(_.toString),
1✔
29
      versionId = HexString.fromByteArray(didMetadata.lastOperationHash.toArray).toString,
×
30
      created = didMetadata.created.map(toXmlDateTime),
1✔
31
      updated = didMetadata.updated.map(toXmlDateTime)
32
    )
33
  }
34

35
  extension (didData: DIDData) {
1✔
36
    def toW3C(did: PrismDID): DIDDocumentRepr = {
37
      import VerificationRelationship.*
1✔
38
      val embeddedKeys = didData.publicKeys.map(k => k.toW3C(did, did))
1✔
39
      val keyRefWithPurpose = didData.publicKeys.map(k => k.purpose -> s"${did.toString}#${k.id}")
1✔
40
      val services = didData.services.map(_.toW3C(did))
41
      DIDDocumentRepr(
1✔
42
        id = did.toString,
1✔
43
        controller = did.toString,
44
        verificationMethod = embeddedKeys,
1✔
45
        authentication = keyRefWithPurpose.collect { case (Authentication, k) => k },
1✔
46
        assertionMethod = keyRefWithPurpose.collect { case (AssertionMethod, k) => k },
1✔
47
        keyAgreement = keyRefWithPurpose.collect { case (KeyAgreement, k) => k },
1✔
48
        capabilityInvocation = keyRefWithPurpose.collect { case (CapabilityInvocation, k) => k },
1✔
49
        capabilityDelegation = keyRefWithPurpose.collect { case (CapabilityDelegation, k) => k },
50
        service = services,
1✔
51
        context = deriveContext(embeddedKeys, services)
52
      )
53
    }
54

55
    // Reference: https://github.com/input-output-hk/prism-did-method-spec/blob/main/w3c-spec/PRISM-method.md#constructing-a-json-ld-did-document
1✔
UNCOV
56
    private def deriveContext(keys: Seq[PublicKeyRepr], services: Seq[ServiceRepr]): Seq[String] = {
×
57
      val mandatoryContext = Seq("https://www.w3.org/ns/did/v1")
58
      val additionalContext = {
1✔
59
        val keyTypes = keys.map(_.`type`).toSet
60
        val serviceTypes = services
×
61
          .map(_.`type`)
1✔
62
          .flatMap {
×
63
            case s: String      => Seq(s)
1✔
64
            case s: Seq[String] => s
65
          }
1✔
UNCOV
66
          .toSet
×
67
        Seq(
×
68
          Option.when(keyTypes.contains("JsonWebKey2020"))("https://w3id.org/security/suites/jws-2020/v1"),
×
69
          Option.when(serviceTypes.contains("DIDCommMessaging"))("https://didcomm.org/messaging/contexts/v2"),
×
70
          Option.when(serviceTypes.contains("LinkedDomains"))(
71
            "https://identity.foundation/.well-known/did-configuration/v1"
72
          )
73
        ).flatten
74
      }
UNCOV
75
      val userDefinedContext = didData.context
×
76
      mandatoryContext ++ additionalContext ++ userDefinedContext
77
    }
78
  }
79

80
  extension (service: Service) {
1✔
81
    def toW3C(did: PrismDID): ServiceRepr =
82
      ServiceRepr(
1✔
83
        id = s"${did.toString}#${service.id}",
1✔
84
        `type` = serviceTypeToW3C(service.`type`),
1✔
85
        serviceEndpoint = serviceEndpointToW3C(service.serviceEndpoint)
86
      )
87

1✔
88
    private def serviceTypeToW3C(serviceType: ServiceType): String | Seq[String] = {
89
      import ServiceType.*
90
      serviceType match {
×
91
        case ServiceType.Single(name)    => name.value
×
92
        case names: ServiceType.Multiple => names.values.map(_.value)
93
      }
94
    }
95

1✔
96
    private def serviceEndpointToW3C(serviceEndpoint: ServiceEndpoint): Json = {
97
      serviceEndpoint match {
1✔
98
        case ServiceEndpoint.Single(uri) =>
99
          uri match {
1✔
100
            case UriOrJsonEndpoint.Uri(uri)   => Json.fromString(uri.value)
1✔
101
            case UriOrJsonEndpoint.Json(json) => Json.fromJsonObject(json)
102
          }
×
103
        case ep: ServiceEndpoint.Multiple =>
×
104
          val uris = ep.values.map {
×
105
            case UriOrJsonEndpoint.Uri(uri)   => Json.fromString(uri.value)
×
106
            case UriOrJsonEndpoint.Json(json) => Json.fromJsonObject(json)
107
          }
×
108
          Json.arr(uris: _*)
109
      }
110
    }
111
  }
112

113
  extension (publicKey: PublicKey) {
1✔
114
    def toW3C(did: PrismDID, controller: PrismDID): PublicKeyRepr = {
115
      val curve = publicKey.publicKeyData match {
1✔
116
        case PublicKeyData.ECCompressedKeyData(crv, _) => crv
1✔
117
        case PublicKeyData.ECKeyData(crv, _, _)        => crv
118
      }
119
      val publicKeyJwk = curve match {
1✔
120
        case EllipticCurve.SECP256K1 => secp256k1Repr(publicKey.publicKeyData)
×
121
        case EllipticCurve.ED25519   => okpPublicKeyRepr(publicKey.publicKeyData)
×
122
        case EllipticCurve.X25519    => okpPublicKeyRepr(publicKey.publicKeyData)
123
      }
124
      PublicKeyRepr(
1✔
125
        id = s"${did.toString}#${publicKey.id}",
126
        `type` = "JsonWebKey2020",
1✔
127
        controller = controller.toString,
128
        publicKeyJwk = publicKeyJwk
129
      )
130
    }
131

×
132
    private def okpPublicKeyRepr(pk: PublicKeyData): PublicKeyJwk = {
133
      pk match {
×
134
        case PublicKeyData.ECCompressedKeyData(crv, data) =>
135
          PublicKeyJwk(
136
            kty = "OKP",
137
            crv = crv.name,
×
138
            x = Some(data.toStringNoPadding),
139
            y = None
140
          )
×
141
        case PublicKeyData.ECKeyData(crv, x, _) =>
142
          PublicKeyJwk(
143
            kty = "OKP",
UNCOV
144
            crv = crv.name,
×
145
            x = Some(x.toStringNoPadding),
146
            y = None
147
          )
148
      }
149
    }
150

1✔
151
    private def secp256k1Repr(pk: PublicKeyData): PublicKeyJwk = {
152
      val (x, y) = pk match {
1✔
153
        case PublicKeyData.ECKeyData(_, x, y) => (x, y)
1✔
154
        case PublicKeyData.ECCompressedKeyData(_, data) =>
1✔
155
          val point = Apollo.default.secp256k1.publicKeyFromEncoded(data.toByteArray).get.getECPoint
1✔
156
          val x = Base64UrlString.fromByteArray(point.x)
1✔
157
          val y = Base64UrlString.fromByteArray(point.y)
158
          (x, y)
159
      }
160
      PublicKeyJwk(
161
        kty = "EC",
162
        crv = EllipticCurve.SECP256K1.name,
1✔
163
        x = Some(x.toStringNoPadding),
1✔
164
        y = Some(y.toStringNoPadding)
165
      )
166
    }
167
  }
168

169
}
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