diff --git a/atala-prism-sdk/src/commonMain/kotlin/io/iohk/atala/prism/walletsdk/apollo/ApolloImpl.kt b/atala-prism-sdk/src/commonMain/kotlin/io/iohk/atala/prism/walletsdk/apollo/ApolloImpl.kt index 37935d29d..ada17708c 100644 --- a/atala-prism-sdk/src/commonMain/kotlin/io/iohk/atala/prism/walletsdk/apollo/ApolloImpl.kt +++ b/atala-prism-sdk/src/commonMain/kotlin/io/iohk/atala/prism/walletsdk/apollo/ApolloImpl.kt @@ -6,9 +6,12 @@ import io.iohk.atala.prism.apollo.derivation.MnemonicLengthException import io.iohk.atala.prism.walletsdk.apollo.helpers.BytesOps import io.iohk.atala.prism.walletsdk.apollo.utils.Ed25519KeyPair import io.iohk.atala.prism.walletsdk.apollo.utils.Ed25519PrivateKey +import io.iohk.atala.prism.walletsdk.apollo.utils.Ed25519PublicKey import io.iohk.atala.prism.walletsdk.apollo.utils.Secp256k1PrivateKey +import io.iohk.atala.prism.walletsdk.apollo.utils.Secp256k1PublicKey import io.iohk.atala.prism.walletsdk.apollo.utils.X25519KeyPair import io.iohk.atala.prism.walletsdk.apollo.utils.X25519PrivateKey +import io.iohk.atala.prism.walletsdk.apollo.utils.X25519PublicKey import io.iohk.atala.prism.walletsdk.domain.buildingblocks.Apollo import io.iohk.atala.prism.walletsdk.domain.models.ApolloError import io.iohk.atala.prism.walletsdk.domain.models.Curve @@ -19,8 +22,10 @@ import io.iohk.atala.prism.walletsdk.domain.models.keyManagement.DerivationPathK import io.iohk.atala.prism.walletsdk.domain.models.keyManagement.IndexKey import io.iohk.atala.prism.walletsdk.domain.models.keyManagement.KeyTypes import io.iohk.atala.prism.walletsdk.domain.models.keyManagement.PrivateKey +import io.iohk.atala.prism.walletsdk.domain.models.keyManagement.PublicKey import io.iohk.atala.prism.walletsdk.domain.models.keyManagement.RawKey import io.iohk.atala.prism.walletsdk.domain.models.keyManagement.SeedKey +import io.iohk.atala.prism.walletsdk.domain.models.keyManagement.StorableKey import io.iohk.atala.prism.walletsdk.domain.models.keyManagement.TypeKey /** @@ -108,9 +113,7 @@ class ApolloImpl : Apollo { throw ApolloError.InvalidIndex("Index must be an integer") } val derivationPath = - if (properties[DerivationPathKey().property] != null && - properties[DerivationPathKey().property] !is String - ) { + if (properties[DerivationPathKey().property] != null && properties[DerivationPathKey().property] !is String) { throw ApolloError.InvalidDerivationPath("Derivation path must be a string") } else { "m/$index'/0'/0'" @@ -143,4 +146,52 @@ class ApolloImpl : Apollo { } throw ApolloError.InvalidKeyType(TypeKey().property, KeyTypes.values().map { it.type }.toTypedArray()) } + + override fun isPrivateKeyData(identifier: String, data: ByteArray): Boolean { + return identifier.endsWith("priv") + } + + override fun isPublicKeyData(identifier: String, data: ByteArray): Boolean { + return identifier.endsWith("pub") + } + + override fun restorePrivateKey(key: StorableKey): PrivateKey { + return when (key.restorationIdentifier) { + "secp256k1+priv" -> { + Secp256k1PrivateKey(key.storableData) + } + + "x25519+priv" -> { + X25519PrivateKey(key.storableData) + } + + "ed25519+priv" -> { + Ed25519PrivateKey(key.storableData) + } + + else -> { + throw ApolloError.RestorationFailedNoIdentifierOrInvalid() + } + } + } + + override fun restorePublicKey(key: StorableKey): PublicKey { + return when (key.restorationIdentifier) { + "secp256k1+pub" -> { + Secp256k1PublicKey(key.storableData) + } + + "x25519+pub" -> { + X25519PublicKey(key.storableData) + } + + "ed25519+pub" -> { + Ed25519PublicKey(key.storableData) + } + + else -> { + throw ApolloError.RestorationFailedNoIdentifierOrInvalid() + } + } + } } diff --git a/atala-prism-sdk/src/commonMain/kotlin/io/iohk/atala/prism/walletsdk/domain/buildingblocks/Apollo.kt b/atala-prism-sdk/src/commonMain/kotlin/io/iohk/atala/prism/walletsdk/domain/buildingblocks/Apollo.kt index c24c011bc..7ef9d8294 100644 --- a/atala-prism-sdk/src/commonMain/kotlin/io/iohk/atala/prism/walletsdk/domain/buildingblocks/Apollo.kt +++ b/atala-prism-sdk/src/commonMain/kotlin/io/iohk/atala/prism/walletsdk/domain/buildingblocks/Apollo.kt @@ -3,13 +3,14 @@ package io.iohk.atala.prism.walletsdk.domain.buildingblocks import io.iohk.atala.prism.apollo.derivation.MnemonicLengthException import io.iohk.atala.prism.walletsdk.domain.models.Seed import io.iohk.atala.prism.walletsdk.domain.models.SeedWords +import io.iohk.atala.prism.walletsdk.domain.models.keyManagement.KeyRestoration import io.iohk.atala.prism.walletsdk.domain.models.keyManagement.PrivateKey import kotlin.jvm.Throws /** * Apollo defines the set of cryptographic operations that are used in the Atala PRISM. */ -interface Apollo { +interface Apollo : KeyRestoration { /** * Creates a random set of mnemonic phrases that can be used as a seed for generating diff --git a/atala-prism-sdk/src/commonMain/kotlin/io/iohk/atala/prism/walletsdk/domain/models/Errors.kt b/atala-prism-sdk/src/commonMain/kotlin/io/iohk/atala/prism/walletsdk/domain/models/Errors.kt index 953c960a8..9eb5e38f4 100644 --- a/atala-prism-sdk/src/commonMain/kotlin/io/iohk/atala/prism/walletsdk/domain/models/Errors.kt +++ b/atala-prism-sdk/src/commonMain/kotlin/io/iohk/atala/prism/walletsdk/domain/models/Errors.kt @@ -16,7 +16,8 @@ abstract interface Error { * This object may include an error code, an error message, and possibly an array of underlying errors. If the error * received does not conform to the [Error] interface, it will be classified as an [UnknownPrismError]. */ -abstract class UnknownPrismError @JvmOverloads constructor(message: String? = null, cause: Throwable? = null) : Error, Throwable(message, cause) { +abstract class UnknownPrismError @JvmOverloads constructor(message: String? = null, cause: Throwable? = null) : Error, + Throwable(message, cause) { override val code: Int? get() = null @@ -36,7 +37,8 @@ abstract class UnknownPrismError @JvmOverloads constructor(message: String? = nu * This object may include an error code and an error message. If the error received conforms to the [KnownPrismError], * it will be classified as a known error. */ -abstract class KnownPrismError @JvmOverloads constructor(message: String? = null, cause: Throwable? = null) : Error, Throwable(message, cause) { +abstract class KnownPrismError @JvmOverloads constructor(message: String? = null, cause: Throwable? = null) : Error, + Throwable(message, cause) { override val code: Int? get() = null override val message: String? @@ -54,7 +56,8 @@ abstract class KnownPrismError @JvmOverloads constructor(message: String? = null * This object may include an error code, an error message, and possibly an array of underlying errors. * If the error received does not conform to the [KnownPrismError], it will be classified as an [UnknownPrismError]. */ -abstract class UnknownError @JvmOverloads constructor(message: String? = null, cause: Throwable? = null) : UnknownPrismError(message, cause) { +abstract class UnknownError @JvmOverloads constructor(message: String? = null, cause: Throwable? = null) : + UnknownPrismError(message, cause) { class SomethingWentWrongError( message: String? = null, @@ -78,7 +81,8 @@ abstract class UnknownError @JvmOverloads constructor(message: String? = null, c * This object may include an error code and an error message. If the error received conforms to the [KnownPrismError], * it will be classified as a known error. */ -sealed class CommonError @JvmOverloads constructor(message: String? = null, cause: Throwable? = null) : KnownPrismError(message, cause) { +sealed class CommonError @JvmOverloads constructor(message: String? = null, cause: Throwable? = null) : + KnownPrismError(message, cause) { class InvalidURLError(val url: String) : CommonError() { override val code: Int get() = -2 @@ -103,7 +107,8 @@ sealed class CommonError @JvmOverloads constructor(message: String? = null, caus * This object may include an error code and an error message. If the error received conforms to the [KnownPrismError], * it will be classified as a known error. */ -sealed class ApolloError @JvmOverloads constructor(message: String? = null, cause: Throwable? = null) : KnownPrismError(message, cause) { +sealed class ApolloError @JvmOverloads constructor(message: String? = null, cause: Throwable? = null) : + KnownPrismError(message, cause) { class InvalidMnemonicWord(private val invalidWords: Array? = null) : ApolloError() { override val code: Int get() = 11 @@ -180,6 +185,14 @@ sealed class ApolloError @JvmOverloads constructor(message: String? = null, caus override val message: String get() = invalidMessage } + + class RestorationFailedNoIdentifierOrInvalid() : ApolloError() { + + override val code: Int + get() = 20 + override val message: String + get() = "Restoration failed: no identifier or invalid" + } } /** @@ -189,7 +202,8 @@ sealed class ApolloError @JvmOverloads constructor(message: String? = null, caus * This object may include an error code and an error message. If the error received conforms to the [KnownPrismError], * it will be classified as a known error. */ -sealed class CastorError @JvmOverloads constructor(message: String? = null, cause: Throwable? = null) : KnownPrismError(message, cause) { +sealed class CastorError @JvmOverloads constructor(message: String? = null, cause: Throwable? = null) : + KnownPrismError(message, cause) { class KeyCurveNotSupported(val curve: String) : CastorError() { override val code: Int get() = 21 @@ -276,7 +290,8 @@ sealed class CastorError @JvmOverloads constructor(message: String? = null, caus * This object may include an error code and an error message. If the error received conforms to the [KnownPrismError], * it will be classified as a known error. */ -sealed class MercuryError @JvmOverloads constructor(message: String? = null, cause: Throwable? = null) : KnownPrismError(message, cause) { +sealed class MercuryError @JvmOverloads constructor(message: String? = null, cause: Throwable? = null) : + KnownPrismError(message, cause) { class NoDIDReceiverSetError : MercuryError() { override val code: Int @@ -349,7 +364,8 @@ sealed class MercuryError @JvmOverloads constructor(message: String? = null, cau * This object may include an error code and an error message. If the error received conforms to the [KnownPrismError], * it will be classified as a known error. */ -sealed class PlutoError @JvmOverloads constructor(message: String? = null, cause: Throwable? = null) : KnownPrismError(message, cause) { +sealed class PlutoError @JvmOverloads constructor(message: String? = null, cause: Throwable? = null) : + KnownPrismError(message, cause) { class MissingDataPersistence(val type: String, private val affecting: String) : PlutoError() { override val code: Int @@ -433,7 +449,8 @@ sealed class PlutoError @JvmOverloads constructor(message: String? = null, cause * This object may include an error code and an error message. If the error received conforms to the [KnownPrismError], * it will be classified as a known error. */ -sealed class PolluxError @JvmOverloads constructor(message: String? = null, cause: Throwable? = null) : KnownPrismError(message, cause) { +sealed class PolluxError @JvmOverloads constructor(message: String? = null, cause: Throwable? = null) : + KnownPrismError(message, cause) { class InvalidPrismDID : PolluxError() { override val code: Int get() = 53 @@ -442,7 +459,8 @@ sealed class PolluxError @JvmOverloads constructor(message: String? = null, caus get() = "To create a JWT presentation a Prism DID is required" } - class InvalidCredentialError @JvmOverloads constructor(message: String? = null, cause: Throwable? = null) : PolluxError(message, cause) { + class InvalidCredentialError @JvmOverloads constructor(message: String? = null, cause: Throwable? = null) : + PolluxError(message, cause) { override val code: Int get() = 51 @@ -474,7 +492,10 @@ sealed class PolluxError @JvmOverloads constructor(message: String? = null, caus get() = "No domain or challenge found as part of the offer json" } - class InvalidCredentialDefinitionError @JvmOverloads constructor(message: String? = null, cause: Throwable? = null) : PolluxError(message, cause) { + class InvalidCredentialDefinitionError @JvmOverloads constructor( + message: String? = null, + cause: Throwable? = null + ) : PolluxError(message, cause) { override val code: Int get() = 56 diff --git a/atala-prism-sdk/src/commonMain/kotlin/io/iohk/atala/prism/walletsdk/domain/models/keyManagement/ExportableImportableKey.kt b/atala-prism-sdk/src/commonMain/kotlin/io/iohk/atala/prism/walletsdk/domain/models/keyManagement/ExportableImportableKey.kt index d63d5b8f3..6fc163a56 100644 --- a/atala-prism-sdk/src/commonMain/kotlin/io/iohk/atala/prism/walletsdk/domain/models/keyManagement/ExportableImportableKey.kt +++ b/atala-prism-sdk/src/commonMain/kotlin/io/iohk/atala/prism/walletsdk/domain/models/keyManagement/ExportableImportableKey.kt @@ -43,7 +43,7 @@ data class PEMKey(val keyType: PEMKeyType, val keyData: ByteArray) { constructor(keyType: PEMKeyType, keyData: String) : this(keyType, keyData.base64UrlDecodedBytes) fun pemEncoded(): String { - val base64Data = keyData.base64PadEncoded + val base64Data = keyData.base64PadEncoded.chunked(64).joinToString("\n") val beginMarker = "-----BEGIN $keyType-----" val endMarker = "-----END $keyType-----" diff --git a/atala-prism-sdk/src/commonMain/kotlin/io/iohk/atala/prism/walletsdk/domain/models/keyManagement/KeyRestoration.kt b/atala-prism-sdk/src/commonMain/kotlin/io/iohk/atala/prism/walletsdk/domain/models/keyManagement/KeyRestoration.kt new file mode 100644 index 000000000..b86386b9e --- /dev/null +++ b/atala-prism-sdk/src/commonMain/kotlin/io/iohk/atala/prism/walletsdk/domain/models/keyManagement/KeyRestoration.kt @@ -0,0 +1,12 @@ +package io.iohk.atala.prism.walletsdk.domain.models.keyManagement + +interface KeyRestoration { + + fun isPrivateKeyData(identifier: String, data: ByteArray): Boolean + + fun isPublicKeyData(identifier: String, data: ByteArray): Boolean + + fun restorePrivateKey(key: StorableKey): PrivateKey + + fun restorePublicKey(key: StorableKey): PublicKey +} diff --git a/atala-prism-sdk/src/commonTest/kotlin/io/iohk/atala/prism/walletsdk/apollo/ApolloTests.kt b/atala-prism-sdk/src/commonTest/kotlin/io/iohk/atala/prism/walletsdk/apollo/ApolloTests.kt index fe3ff58c1..8b462a775 100644 --- a/atala-prism-sdk/src/commonTest/kotlin/io/iohk/atala/prism/walletsdk/apollo/ApolloTests.kt +++ b/atala-prism-sdk/src/commonTest/kotlin/io/iohk/atala/prism/walletsdk/apollo/ApolloTests.kt @@ -20,7 +20,9 @@ import io.iohk.atala.prism.walletsdk.domain.models.Curve import io.iohk.atala.prism.walletsdk.domain.models.KeyCurve import io.iohk.atala.prism.walletsdk.domain.models.Seed import io.iohk.atala.prism.walletsdk.domain.models.keyManagement.KeyPair +import io.iohk.atala.prism.walletsdk.domain.models.keyManagement.StorableKey import kotlinx.serialization.json.Json +import org.junit.Assert.assertNotEquals import org.junit.Before import org.junit.Test import kotlin.test.assertEquals @@ -177,4 +179,30 @@ class ApolloTests { signature[0] = 1 assertFalse((keyPair.publicKey as Ed25519PublicKey).verify(message.toByteArray(), signature)) } + + @Test + fun testRestorePrivateKey_whenStorableSecp256k1_thenRestoredOk() { + val keyPairSecp256k1 = + Secp256k1KeyPair.generateKeyPair(Seed(Mnemonic.createRandomSeed()), KeyCurve(Curve.SECP256K1)) + val keyPairEd25519 = Ed25519KeyPair.generateKeyPair() + val privateKeySecp256k1 = keyPairSecp256k1.privateKey as Secp256k1PrivateKey + val privateKeyEd25519 = keyPairEd25519.privateKey as Ed25519PrivateKey + val storableKey = keyPairSecp256k1.privateKey as StorableKey + val restoredKey = apollo.restorePrivateKey(storableKey) + assertEquals(privateKeySecp256k1.raw, restoredKey.raw) + assertNotEquals(privateKeyEd25519.raw, restoredKey.raw) + } + + @Test + fun testRestorePublicKey_whenStorableSecp256k1_thenRestoredOk() { + val keyPairSecp256k1 = + Secp256k1KeyPair.generateKeyPair(Seed(Mnemonic.createRandomSeed()), KeyCurve(Curve.SECP256K1)) + val keyPairEd25519 = Ed25519KeyPair.generateKeyPair() + val publicKeySecp256k1 = keyPairSecp256k1.publicKey as Secp256k1PublicKey + val publicKeyEd25519 = keyPairEd25519.publicKey as Ed25519PublicKey + val storableKey = keyPairSecp256k1.publicKey as StorableKey + val restoredKey = apollo.restorePublicKey(storableKey) + assertEquals(publicKeySecp256k1.raw, restoredKey.raw) + assertNotEquals(publicKeyEd25519.raw, restoredKey.raw) + } } diff --git a/atala-prism-sdk/src/commonTest/kotlin/io/iohk/atala/prism/walletsdk/castor/ApolloMock.kt b/atala-prism-sdk/src/commonTest/kotlin/io/iohk/atala/prism/walletsdk/castor/ApolloMock.kt index eb5d0eb66..d2b068fc5 100644 --- a/atala-prism-sdk/src/commonTest/kotlin/io/iohk/atala/prism/walletsdk/castor/ApolloMock.kt +++ b/atala-prism-sdk/src/commonTest/kotlin/io/iohk/atala/prism/walletsdk/castor/ApolloMock.kt @@ -11,6 +11,7 @@ import io.iohk.atala.prism.walletsdk.domain.models.Signature import io.iohk.atala.prism.walletsdk.domain.models.keyManagement.KeyPair import io.iohk.atala.prism.walletsdk.domain.models.keyManagement.PrivateKey import io.iohk.atala.prism.walletsdk.domain.models.keyManagement.PublicKey +import io.iohk.atala.prism.walletsdk.domain.models.keyManagement.StorableKey class ApolloMock : Apollo { var createRandomMnemonicsReturn: Array = emptyArray() @@ -41,4 +42,20 @@ class ApolloMock : Apollo { override fun createPrivateKey(properties: Map): PrivateKey { TODO("Not yet implemented") } + + override fun isPrivateKeyData(identifier: String, data: ByteArray): Boolean { + TODO("Not yet implemented") + } + + override fun isPublicKeyData(identifier: String, data: ByteArray): Boolean { + TODO("Not yet implemented") + } + + override fun restorePrivateKey(key: StorableKey): PrivateKey { + TODO("Not yet implemented") + } + + override fun restorePublicKey(key: StorableKey): PublicKey { + TODO("Not yet implemented") + } } diff --git a/atala-prism-sdk/src/commonTest/kotlin/io/iohk/atala/prism/walletsdk/prismagent/ApolloMock.kt b/atala-prism-sdk/src/commonTest/kotlin/io/iohk/atala/prism/walletsdk/prismagent/ApolloMock.kt index 7f5be9223..f49bc2101 100644 --- a/atala-prism-sdk/src/commonTest/kotlin/io/iohk/atala/prism/walletsdk/prismagent/ApolloMock.kt +++ b/atala-prism-sdk/src/commonTest/kotlin/io/iohk/atala/prism/walletsdk/prismagent/ApolloMock.kt @@ -9,6 +9,8 @@ import io.iohk.atala.prism.walletsdk.domain.models.Seed import io.iohk.atala.prism.walletsdk.domain.models.SeedWords import io.iohk.atala.prism.walletsdk.domain.models.keyManagement.KeyPair import io.iohk.atala.prism.walletsdk.domain.models.keyManagement.PrivateKey +import io.iohk.atala.prism.walletsdk.domain.models.keyManagement.PublicKey +import io.iohk.atala.prism.walletsdk.domain.models.keyManagement.StorableKey class ApolloMock : Apollo { var createRandomMnemonicsReturn: Array = emptyArray() @@ -33,4 +35,20 @@ class ApolloMock : Apollo { override fun createPrivateKey(properties: Map): PrivateKey { return createPrivateKey ?: Secp256k1PrivateKey(ByteArray(0)) } + + override fun isPrivateKeyData(identifier: String, data: ByteArray): Boolean { + TODO("Not yet implemented") + } + + override fun isPublicKeyData(identifier: String, data: ByteArray): Boolean { + TODO("Not yet implemented") + } + + override fun restorePrivateKey(key: StorableKey): PrivateKey { + TODO("Not yet implemented") + } + + override fun restorePublicKey(key: StorableKey): PublicKey { + TODO("Not yet implemented") + } }