Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/fix/scala-steward-dco' into main3
Browse files Browse the repository at this point in the history
  • Loading branch information
yshyn-iohk committed May 21, 2024
2 parents 4b66cdd + 9258105 commit 64b1e03
Show file tree
Hide file tree
Showing 27 changed files with 49 additions and 92 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@ package object error {
final case class TooManyDidPublicKeyAccess(limit: Int, access: Option[Int]) extends OperationValidationError
final case class TooManyDidServiceAccess(limit: Int, access: Option[Int]) extends OperationValidationError
final case class InvalidArgument(msg: String) extends OperationValidationError
final case class InvalidPublicKeyData(ids: Seq[String]) extends OperationValidationError
final case class InvalidMasterKeyType(ids: Seq[String]) extends OperationValidationError
final case class InvalidMasterKeyData(ids: Seq[String]) extends OperationValidationError
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import org.hyperledger.identus.shared.crypto.Apollo
import zio.*

import scala.collection.immutable.ArraySeq
import scala.util.Failure

object DIDOperationValidator {
final case class Config(
Expand Down Expand Up @@ -51,7 +50,6 @@ private object CreateOperationValidator extends BaseOperationValidator {
_ <- validateUniquePublicKeyId(operation, extractKeyIds)
_ <- validateUniqueServiceId(operation, extractServiceIds)
_ <- validateMasterKeyIsSecp256k1(operation, extractKeyData)
_ <- validateKeyData(operation, extractKeyData)
_ <- validateKeyIdIsUriFragment(operation, extractKeyIds)
_ <- validateKeyIdLength(config)(operation, extractKeyIds)
_ <- validateServiceIdIsUriFragment(operation, extractServiceIds)
Expand Down Expand Up @@ -102,7 +100,6 @@ private object UpdateOperationValidator extends BaseOperationValidator {
_ <- validateMaxPublicKeysAccess(config)(operation, extractKeyIds)
_ <- validateMaxServiceAccess(config)(operation, extractServiceIds)
_ <- validateMasterKeyIsSecp256k1(operation, extractKeyData)
_ <- validateKeyData(operation, extractKeyData)
_ <- validateKeyIdIsUriFragment(operation, extractKeyIds)
_ <- validateKeyIdLength(config)(operation, extractKeyIds)
_ <- validateServiceIdIsUriFragment(operation, extractServiceIds)
Expand Down Expand Up @@ -360,45 +357,26 @@ private trait BaseOperationValidator {
UriUtils.normalizeUri(uri).contains(uri)
}

protected def validateKeyData[T <: PrismDIDOperation](
operation: T,
keyDataExtractor: KeyDataExtractor[T]
): Either[OperationValidationError, Unit] = {
val keys = keyDataExtractor(operation)
val apollo = Apollo.default
val parsedKeys = keys.map { case (id, _, keyData) =>
val pk = keyData match {
case PublicKeyData.ECKeyData(EllipticCurve.SECP256K1, x, y) =>
apollo.secp256k1.publicKeyFromCoordinate(x.toByteArray, y.toByteArray)
case PublicKeyData.ECKeyData(EllipticCurve.ED25519, x, _) =>
apollo.ed25519.publicKeyFromEncoded(x.toByteArray)
case PublicKeyData.ECKeyData(EllipticCurve.X25519, x, _) =>
apollo.x25519.publicKeyFromEncoded(x.toByteArray)
case PublicKeyData.ECCompressedKeyData(EllipticCurve.SECP256K1, data) =>
apollo.secp256k1.publicKeyFromEncoded(data.toByteArray)
case PublicKeyData.ECCompressedKeyData(EllipticCurve.ED25519, data) =>
apollo.ed25519.publicKeyFromEncoded(data.toByteArray)
case PublicKeyData.ECCompressedKeyData(EllipticCurve.X25519, data) =>
apollo.x25519.publicKeyFromEncoded(data.toByteArray)
}
id -> pk
}

val invalidKeyDataIds = parsedKeys.collect { case (id, Failure(_)) => id }
if (invalidKeyDataIds.isEmpty) Right(())
else Left(OperationValidationError.InvalidPublicKeyData(invalidKeyDataIds))
}

protected def validateMasterKeyIsSecp256k1[T <: PrismDIDOperation](
operation: T,
keyDataExtractor: KeyDataExtractor[T]
): Either[OperationValidationError, Unit] = {
val keys = keyDataExtractor(operation)
val masterKeys = keys.collect { case (id, InternalKeyPurpose.Master, keyData) => id -> keyData }
val invalidKeyIds = masterKeys.filter(_._2.crv != EllipticCurve.SECP256K1).map(_._1)
val invalidKeyIds = masterKeys
.filter { case (_, pk) =>
pk match {
case PublicKeyData.ECKeyData(EllipticCurve.SECP256K1, x, y) =>
Apollo.default.secp256k1.publicKeyFromCoordinate(x.toByteArray, y.toByteArray).isFailure
case PublicKeyData.ECCompressedKeyData(EllipticCurve.SECP256K1, data) =>
Apollo.default.secp256k1.publicKeyFromEncoded(data.toByteArray).isFailure
case _ => true // master key must be secp256k1
}
}
.map(_._1)

if (invalidKeyIds.isEmpty) Right(())
else Left(OperationValidationError.InvalidMasterKeyType(invalidKeyIds))
else Left(OperationValidationError.InvalidMasterKeyData(invalidKeyIds))
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -340,24 +340,6 @@ object DIDOperationValidatorSpec extends ZIOSpecDefault {
invalidArgumentContainsString("service id is invalid: [Wrong service]")
)
},
test("reject CreateOperation when publicKeyData is invalid") {
val op = createPrismDIDOperation(
publicKeys = Seq(
PublicKey(
id = "key-0",
purpose = VerificationRelationship.Authentication,
publicKeyData = PublicKeyData.ECKeyData(
crv = EllipticCurve.SECP256K1,
x = Base64UrlString.fromStringUnsafe("00"),
y = Base64UrlString.fromStringUnsafe("00")
)
)
)
)
assert(DIDOperationValidator(Config.default).validate(op))(
isLeft(equalTo(OperationValidationError.InvalidPublicKeyData(Seq("key-0"))))
)
},
test("reject CreateOperation when master key is not a secp256k1 key") {
val op = createPrismDIDOperation(
internalKeys = Seq(
Expand All @@ -369,11 +351,20 @@ object DIDOperationValidatorSpec extends ZIOSpecDefault {
x = Base64UrlString.fromStringUnsafe("11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo"),
y = Base64UrlString.fromStringUnsafe("")
)
),
InternalPublicKey(
id = "master1",
purpose = InternalKeyPurpose.Master,
publicKeyData = PublicKeyData.ECKeyData(
crv = EllipticCurve.SECP256K1,
x = Base64UrlString.fromStringUnsafe("11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo"),
y = Base64UrlString.fromStringUnsafe("")
)
)
)
)
assert(DIDOperationValidator(Config.default).validate(op))(
isLeft(equalTo(OperationValidationError.InvalidMasterKeyType(Seq("master0"))))
isLeft(equalTo(OperationValidationError.InvalidMasterKeyData(Seq("master0", "master1"))))
)
}
).provideLayer(testLayer)
Expand Down Expand Up @@ -597,38 +588,32 @@ object DIDOperationValidatorSpec extends ZIOSpecDefault {
invalidArgumentContainsString("must not have both 'type' and 'serviceEndpoints' empty")
)
},
test("reject UpdateOperation publicKeyData is invalid") {
val action = UpdateDIDAction.AddKey(
PublicKey(
id = "key0",
purpose = VerificationRelationship.Authentication,
test("reject UpdateOperation when master key is not a secp256k1 key") {
val action1 = UpdateDIDAction.AddInternalKey(
InternalPublicKey(
id = "master0",
purpose = InternalKeyPurpose.Master,
publicKeyData = PublicKeyData.ECKeyData(
crv = EllipticCurve.SECP256K1,
x = Base64UrlString.fromStringUnsafe("00"),
y = Base64UrlString.fromStringUnsafe("00")
crv = EllipticCurve.ED25519,
x = Base64UrlString.fromStringUnsafe("11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo"),
y = Base64UrlString.fromStringUnsafe("")
)
)
)
val op = updatePrismDIDOperation(Seq(action))
assert(DIDOperationValidator(Config.default).validate(op))(
isLeft(equalTo(OperationValidationError.InvalidPublicKeyData(Seq("key0"))))
)
},
test("reject UpdateOperation when master key is not a secp256k1 key") {
val action = UpdateDIDAction.AddInternalKey(
val action2 = UpdateDIDAction.AddInternalKey(
InternalPublicKey(
id = "master0",
id = "master1",
purpose = InternalKeyPurpose.Master,
publicKeyData = PublicKeyData.ECKeyData(
crv = EllipticCurve.ED25519,
crv = EllipticCurve.SECP256K1,
x = Base64UrlString.fromStringUnsafe("11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo"),
y = Base64UrlString.fromStringUnsafe("")
)
)
)
val op = updatePrismDIDOperation(Seq(action))
val op = updatePrismDIDOperation(Seq(action1, action2))
assert(DIDOperationValidator(Config.default).validate(op))(
isLeft(equalTo(OperationValidationError.InvalidMasterKeyType(Seq("master0"))))
isLeft(equalTo(OperationValidationError.InvalidMasterKeyData(Seq("master0", "master1"))))
)
}
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -203,13 +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 {
Expand Down
18 changes: 9 additions & 9 deletions docs/decisions/20230405-did-linked-resources.md
Original file line number Diff line number Diff line change
Expand Up @@ -393,9 +393,9 @@ As the solution is based on the latest ToIP specification, it derives all positi
#### Negative Consequences
- scalability: the specification is inspired by the Cheqd approach to store the linkedResourceMetadata inside of the DID Document
- the convention for references and the logic must be carefully reviewed:
- `schemaId` in this solution is `{didRef}/resources/{cardano_transaction_id}`, so it doesn't refer to the `id` but to the Tx where everything else is stored (it's an interesting idea for a stateless design)
- resource metadata is built according to the ToIP specification but for AnonCreds entities only: credential schema and credential definition.
- technology stack: it doesn't fit to current Atala PRISM platform, but can be used for inspiration.
- `schemaId` in this solution is `{didRef}/resources/{cardano_transaction_id}`, so it doesn't refer to the `id` but to the Tx where everything else is stored (it's an interesting idea for a stateless design)
- resource metadata is built according to the ToIP specification but for AnonCreds entities only: credential schema and credential definition.
- technology stack: it doesn't fit to current platform, but can be used for inspiration.


### Hyperledger AnonCreds
Expand Down Expand Up @@ -495,9 +495,9 @@ Taking into account the advantages and disadvantages of the existing solutions t
-the resource is linked to the DID by convention specified in the W3C specification, so specifying the resource in the DID URL and defining the service endpoint that exposes the resource allows to discover and fetch the resource using the Universal Resolver
- as an option, the same resource can be discovered and fetched by the PRISM platform backend and SDK without loading the Universal resolver
- the resource integrity must be guaranteed by one of the following options:
- by signing the payload with one of the DID's keys or
- by publishing the resource metadata that contains the information about the resource (id, type, name, media type, hash) on-chain or
- for the resource that is less than the blockchain limitation (up to 64KB) by publishing the resource together with the hash, and/or signature
- by signing the payload with one of the DID's keys or
- by publishing the resource metadata that contains the information about the resource (id, type, name, media type, hash) on-chain or
- for the resource that is less than the blockchain limitation (up to 64KB) by publishing the resource together with the hash, and/or signature
- the resource can be stored in the cloud storage - PostgreSQL database - for indexing and lookup API

As the Atala PRISM platform can leverage the Cardano blockchain and there is a strong requirement for longevity and security - the resource together with the signature and/or hash must be stored in the Cardano blockchain.
Expand All @@ -506,9 +506,9 @@ An example of this solution will be the following (concerning the current infras

- prism-node must be able to store the generic resource payload, signature and/or hash on-chain and restore the given resource in the underlying database (PostgreSQL) for indexing and lookup API
- credential schema (or any other resource module) must be a part of the Atala SSI infrastructure and allow
- publishing the concrete resource as a generic resource using the prism-node API
- expose the API for discovery and fetching the resource by URL
- expose the API for managing the resources (create, publish, lookup with pagination)
- publishing the concrete resource as a generic resource using the prism-node API
- expose the API for discovery and fetching the resource by URL
- expose the API for managing the resources (create, publish, lookup with pagination)
- the Universal Resolver for the DID Method must be able to discover and fetch the resource by DID URL
- is needed, SDK and backend services can fetch the resources directly (not via the Universal Resolver)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ import com.nimbusds.jose.jwk.{Curve, ECKey}
import com.nimbusds.jwt.{JWTClaimsSet, SignedJWT}
import io.circe.*
import zio.*
import pdi.jwt.algorithms.JwtECDSAAlgorithm
import pdi.jwt.{JwtAlgorithm, JwtCirce}

import java.security.*

Expand Down

0 comments on commit 64b1e03

Please sign in to comment.