From 7367b5a325c1fdce6596a9cbd6cbb7247691ad90 Mon Sep 17 00:00:00 2001 From: waltkb <68587968+waltkb@users.noreply.github.com> Date: Wed, 12 Feb 2025 21:25:19 +0100 Subject: [PATCH 01/22] Add Ed (edwards curve), EdDSA (Ed25519/Ed448) and XDH (X25519/X448) interfaces --- .../src/commonMain/kotlin/algorithms/Ed.kt | 76 +++++++++++++++++++ .../src/commonMain/kotlin/algorithms/EdDSA.kt | 26 +++++++ .../src/commonMain/kotlin/algorithms/XDH.kt | 25 ++++++ 3 files changed, 127 insertions(+) create mode 100644 cryptography-core/src/commonMain/kotlin/algorithms/Ed.kt create mode 100644 cryptography-core/src/commonMain/kotlin/algorithms/EdDSA.kt create mode 100644 cryptography-core/src/commonMain/kotlin/algorithms/XDH.kt diff --git a/cryptography-core/src/commonMain/kotlin/algorithms/Ed.kt b/cryptography-core/src/commonMain/kotlin/algorithms/Ed.kt new file mode 100644 index 00000000..3f3bf50f --- /dev/null +++ b/cryptography-core/src/commonMain/kotlin/algorithms/Ed.kt @@ -0,0 +1,76 @@ +package algorithms + +import dev.whyoleg.cryptography.* +import dev.whyoleg.cryptography.materials.key.* +import kotlin.jvm.* + +@SubclassOptInRequired(CryptographyProviderApi::class) +public interface Ed> : CryptographyAlgorithm { + public fun publicKeyDecoder(curve: Curve): KeyDecoder + public fun privateKeyDecoder(curve: Curve): KeyDecoder + public fun keyPairGenerator(curve: Curve): KeyGenerator + + @JvmInline + public value class Curve(public val name: String) { + public companion object { + public val Ed25519: Curve get() = Curve("Ed25519") + public val X25519: Curve get() = Curve("X25519") + + public val Ed448: Curve get() = Curve("Ed448") + public val X448: Curve get() = Curve("X448") + } + } + + // Similar key interfaces but with Edwards-specific formats + @SubclassOptInRequired(CryptographyProviderApi::class) + public interface KeyPair : Key { + public val publicKey: PublicK + public val privateKey: PrivateK + } + + @SubclassOptInRequired(CryptographyProviderApi::class) + public interface PublicKey : EncodableKey { + public sealed class Format : KeyFormat { + final override fun toString(): String = name + + public data object JWK : Format() { + override val name: String get() = "JWK" + } + + public data object RAW : Format() { + override val name: String get() = "RAW" + } + + public data object DER : Format() { + override val name: String get() = "DER" + } + + public data object PEM : Format() { + override val name: String get() = "PEM" + } + } + } + + @SubclassOptInRequired(CryptographyProviderApi::class) + public interface PrivateKey : EncodableKey { + public sealed class Format : KeyFormat { + final override fun toString(): String = name + + public data object JWK : Format() { + override val name: String get() = "JWK" + } + + public data object RAW : Format() { + override val name: String get() = "RAW" + } + + public data object DER : Format() { + override val name: String get() = "DER" + } + + public data object PEM : Format() { + override val name: String get() = "PEM" + } + } + } +} diff --git a/cryptography-core/src/commonMain/kotlin/algorithms/EdDSA.kt b/cryptography-core/src/commonMain/kotlin/algorithms/EdDSA.kt new file mode 100644 index 00000000..489be029 --- /dev/null +++ b/cryptography-core/src/commonMain/kotlin/algorithms/EdDSA.kt @@ -0,0 +1,26 @@ +package algorithms + +import dev.whyoleg.cryptography.CryptographyAlgorithmId +import dev.whyoleg.cryptography.CryptographyProviderApi +import dev.whyoleg.cryptography.operations.SignatureGenerator +import dev.whyoleg.cryptography.operations.SignatureVerifier + +@SubclassOptInRequired(CryptographyProviderApi::class) +public interface EdDSA : Ed { + override val id: CryptographyAlgorithmId get() = Companion + + public companion object : CryptographyAlgorithmId("EdDSA") + + @SubclassOptInRequired(CryptographyProviderApi::class) + public interface KeyPair : Ed.KeyPair + + @SubclassOptInRequired(CryptographyProviderApi::class) + public interface PublicKey : Ed.PublicKey { + public fun signatureVerifier(): SignatureVerifier + } + + @SubclassOptInRequired(CryptographyProviderApi::class) + public interface PrivateKey : Ed.PrivateKey { + public fun signatureGenerator(): SignatureGenerator + } +} diff --git a/cryptography-core/src/commonMain/kotlin/algorithms/XDH.kt b/cryptography-core/src/commonMain/kotlin/algorithms/XDH.kt new file mode 100644 index 00000000..91a0ca77 --- /dev/null +++ b/cryptography-core/src/commonMain/kotlin/algorithms/XDH.kt @@ -0,0 +1,25 @@ +package algorithms + +import dev.whyoleg.cryptography.CryptographyAlgorithmId +import dev.whyoleg.cryptography.CryptographyProviderApi +import dev.whyoleg.cryptography.operations.SharedSecretGenerator + +@SubclassOptInRequired(CryptographyProviderApi::class) +public interface XDH : Ed { + override val id: CryptographyAlgorithmId get() = Companion + + public companion object : CryptographyAlgorithmId("XDH") + + @SubclassOptInRequired(CryptographyProviderApi::class) + public interface KeyPair : Ed.KeyPair + + @SubclassOptInRequired(CryptographyProviderApi::class) + public interface PublicKey : Ed.PublicKey { + public fun sharedSecretGenerator(): SharedSecretGenerator + } + + @SubclassOptInRequired(CryptographyProviderApi::class) + public interface PrivateKey : Ed.PrivateKey { + public fun sharedSecretGenerator(): SharedSecretGenerator + } +} From 713a4a709cb8f6b5c248f92f1c26698fba533ee0 Mon Sep 17 00:00:00 2001 From: waltkb <68587968+waltkb@users.noreply.github.com> Date: Wed, 12 Feb 2025 21:25:26 +0100 Subject: [PATCH 02/22] Add JDK implementation for Ed (edwards curve), EdDSA (Ed25519/Ed448) and XDH (X25519/X448) interfaces --- .../src/jvmMain/kotlin/algorithms/JdkEd.kt | 107 ++++++++++++++++++ .../src/jvmMain/kotlin/algorithms/JdkEdDSA.kt | 39 +++++++ .../src/jvmMain/kotlin/algorithms/JdkXDH.kt | 43 +++++++ 3 files changed, 189 insertions(+) create mode 100644 cryptography-providers/jdk/src/jvmMain/kotlin/algorithms/JdkEd.kt create mode 100644 cryptography-providers/jdk/src/jvmMain/kotlin/algorithms/JdkEdDSA.kt create mode 100644 cryptography-providers/jdk/src/jvmMain/kotlin/algorithms/JdkXDH.kt diff --git a/cryptography-providers/jdk/src/jvmMain/kotlin/algorithms/JdkEd.kt b/cryptography-providers/jdk/src/jvmMain/kotlin/algorithms/JdkEd.kt new file mode 100644 index 00000000..a9825768 --- /dev/null +++ b/cryptography-providers/jdk/src/jvmMain/kotlin/algorithms/JdkEd.kt @@ -0,0 +1,107 @@ +package algorithms + +import dev.whyoleg.cryptography.materials.key.KeyDecoder +import dev.whyoleg.cryptography.materials.key.KeyGenerator +import dev.whyoleg.cryptography.providers.jdk.JKeyPair +import dev.whyoleg.cryptography.providers.jdk.JKeyPairGenerator +import dev.whyoleg.cryptography.providers.jdk.JPrivateKey +import dev.whyoleg.cryptography.providers.jdk.JPublicKey +import dev.whyoleg.cryptography.providers.jdk.JdkCryptographyState +import dev.whyoleg.cryptography.providers.jdk.materials.JdkEncodableKey +import dev.whyoleg.cryptography.providers.jdk.materials.JdkKeyPairGenerator +import dev.whyoleg.cryptography.providers.jdk.materials.JdkPrivateKeyDecoder +import dev.whyoleg.cryptography.providers.jdk.materials.JdkPublicKeyDecoder +import dev.whyoleg.cryptography.providers.jdk.materials.unwrapPem +import dev.whyoleg.cryptography.providers.jdk.materials.wrapPem +import dev.whyoleg.cryptography.serialization.pem.PemLabel +//import java.security.spec.XECPublicKeySpec // TODO: for raw encoding + +internal sealed class JdkEd>( + protected val state: JdkCryptographyState, +) : Ed { + protected abstract fun JPublicKey.convert(): PublicK + protected abstract fun JPrivateKey.convert(): PrivateK + protected abstract fun JKeyPair.convert(): KP + + final override fun publicKeyDecoder(curve: Ed.Curve): KeyDecoder { + return EdPublicKeyDecoder(curve.jdkName) + } + + final override fun privateKeyDecoder(curve: Ed.Curve): KeyDecoder { + return EdPrivateKeyDecoder(curve.jdkName) + } + + final override fun keyPairGenerator(curve: Ed.Curve): KeyGenerator { + return EdKeyPairGenerator(curve.jdkName) + } + + private val Ed.Curve.jdkName: String get() = name + + private inner class EdKeyPairGenerator( + private val algorithm: String, + ) : JdkKeyPairGenerator(state, algorithm) { + override fun JKeyPairGenerator.init() { + //initialize(null, state.secureRandom) // TODO + } + + override fun JKeyPair.convert(): KP = with(this@JdkEd) { convert() } + } + + private inner class EdPublicKeyDecoder( + private val algorithm: String, + ) : JdkPublicKeyDecoder(state, algorithm) { + override fun JPublicKey.convert(): PublicK = with(this@JdkEd) { convert() } + + override fun decodeFromByteArrayBlocking(format: Ed.PublicKey.Format, bytes: ByteArray): PublicK = when (format) { + Ed.PublicKey.Format.JWK -> error("$format is not supported") + /* Ed.PublicKey.Format.RAW -> keyFactory.use { + it.generatePublic(XECPublicKeySpec(NamedParameterSpec(algorithm), bytes)) + }.convert()*/ // TODO: for raw encoding + Ed.PublicKey.Format.RAW -> TODO("Todo: raw encoding") + Ed.PublicKey.Format.DER -> decodeFromDer(bytes) + Ed.PublicKey.Format.PEM -> decodeFromDer(unwrapPem(PemLabel.PublicKey, bytes)) + } + } + + private inner class EdPrivateKeyDecoder( + private val algorithm: String, + ) : JdkPrivateKeyDecoder(state, algorithm) { + override fun JPrivateKey.convert(): PrivateK = with(this@JdkEd) { convert() } + + override fun decodeFromByteArrayBlocking(format: Ed.PrivateKey.Format, bytes: ByteArray): PrivateK = when (format) { + Ed.PrivateKey.Format.JWK -> error("$format is not supported") + Ed.PrivateKey.Format.RAW -> TODO("Todo: raw encoding") + /*Ed.PrivateKey.Format.RAW -> keyFactory.use { + it.generatePrivate(XECPrivateKeySpec(NamedParameterSpec(algorithm), bytes)) + }.convert()*/ // TODO: for raw encoding + Ed.PrivateKey.Format.DER -> decodeFromDer(bytes) + Ed.PrivateKey.Format.PEM -> decodeFromDer(unwrapPem(PemLabel.PrivateKey, bytes)) + } + } + + protected abstract class BaseEdPublicKey( + private val key: JPublicKey, + ) : Ed.PublicKey, JdkEncodableKey(key) { + final override fun encodeToByteArrayBlocking(format: Ed.PublicKey.Format): ByteArray = when (format) { + Ed.PublicKey.Format.JWK -> error("$format is not supported") + Ed.PublicKey.Format.RAW -> TODO("Todo: raw encoding") +// Ed.PublicKey.Format.RAW -> (key as XECPublicKey).encoded + Ed.PublicKey.Format.DER -> encodeToDer() + Ed.PublicKey.Format.PEM -> wrapPem(PemLabel.PublicKey, encodeToDer()) + else -> TODO() + } + } + + protected abstract class BaseEdPrivateKey( + private val key: JPrivateKey, + ) : Ed.PrivateKey, JdkEncodableKey(key) { + final override fun encodeToByteArrayBlocking(format: Ed.PrivateKey.Format): ByteArray = when (format) { + Ed.PrivateKey.Format.JWK -> error("$format is not supported") + Ed.PrivateKey.Format.RAW -> TODO("Todo: raw encoding") +// Ed.PrivateKey.Format.RAW -> (key as XECPrivateKey).encoded + Ed.PrivateKey.Format.DER -> encodeToDer() + Ed.PrivateKey.Format.PEM -> wrapPem(PemLabel.PrivateKey, encodeToDer()) + else -> TODO() + } + } +} diff --git a/cryptography-providers/jdk/src/jvmMain/kotlin/algorithms/JdkEdDSA.kt b/cryptography-providers/jdk/src/jvmMain/kotlin/algorithms/JdkEdDSA.kt new file mode 100644 index 00000000..9e0b9455 --- /dev/null +++ b/cryptography-providers/jdk/src/jvmMain/kotlin/algorithms/JdkEdDSA.kt @@ -0,0 +1,39 @@ +package algorithms + +import dev.whyoleg.cryptography.operations.SignatureGenerator +import dev.whyoleg.cryptography.operations.SignatureVerifier +import dev.whyoleg.cryptography.providers.jdk.JKeyPair +import dev.whyoleg.cryptography.providers.jdk.JPrivateKey +import dev.whyoleg.cryptography.providers.jdk.JPublicKey +import dev.whyoleg.cryptography.providers.jdk.JdkCryptographyState +import dev.whyoleg.cryptography.providers.jdk.operations.JdkSignatureGenerator +import dev.whyoleg.cryptography.providers.jdk.operations.JdkSignatureVerifier + +internal class JdkEdDSA(state: JdkCryptographyState) : JdkEd(state), EdDSA { + override fun JPublicKey.convert(): EdDSA.PublicKey = EdDsaPublicKey(state, this) + override fun JPrivateKey.convert(): EdDSA.PrivateKey = EdDsaPrivateKey(state, this) + override fun JKeyPair.convert(): EdDSA.KeyPair = EdDsaKeyPair(public.convert(), private.convert()) + + private class EdDsaKeyPair( + override val publicKey: EdDSA.PublicKey, + override val privateKey: EdDSA.PrivateKey, + ) : EdDSA.KeyPair + + private class EdDsaPublicKey( + private val state: JdkCryptographyState, + private val key: JPublicKey, + ) : EdDSA.PublicKey, BaseEdPublicKey(key) { + override fun signatureVerifier(): SignatureVerifier { + return JdkSignatureVerifier(state, key, "EdDSA", null) + } + } + + private class EdDsaPrivateKey( + private val state: JdkCryptographyState, + private val key: JPrivateKey, + ) : EdDSA.PrivateKey, BaseEdPrivateKey(key) { + override fun signatureGenerator(): SignatureGenerator { + return JdkSignatureGenerator(state, key, "EdDSA", null) + } + } +} diff --git a/cryptography-providers/jdk/src/jvmMain/kotlin/algorithms/JdkXDH.kt b/cryptography-providers/jdk/src/jvmMain/kotlin/algorithms/JdkXDH.kt new file mode 100644 index 00000000..799e2924 --- /dev/null +++ b/cryptography-providers/jdk/src/jvmMain/kotlin/algorithms/JdkXDH.kt @@ -0,0 +1,43 @@ +package algorithms + +import dev.whyoleg.cryptography.operations.SharedSecretGenerator +import dev.whyoleg.cryptography.providers.jdk.JKeyPair +import dev.whyoleg.cryptography.providers.jdk.JPrivateKey +import dev.whyoleg.cryptography.providers.jdk.JPublicKey +import dev.whyoleg.cryptography.providers.jdk.JdkCryptographyState +import dev.whyoleg.cryptography.providers.jdk.operations.doAgreement + +internal class JdkXDH(state: JdkCryptographyState) : JdkEd(state), XDH { + override fun JPublicKey.convert(): XDH.PublicKey = XdhPublicKey(state, this) + override fun JPrivateKey.convert(): XDH.PrivateKey = XdhPrivateKey(state, this) + override fun JKeyPair.convert(): XDH.KeyPair = XdhKeyPair(public.convert(), private.convert()) + + private class XdhKeyPair( + override val publicKey: XDH.PublicKey, + override val privateKey: XDH.PrivateKey, + ) : XDH.KeyPair + + private class XdhPublicKey( + private val state: JdkCryptographyState, + val key: JPublicKey, + ) : XDH.PublicKey, BaseEdPublicKey(key), SharedSecretGenerator { + private val keyAgreement = state.keyAgreement("XDH") + override fun sharedSecretGenerator(): SharedSecretGenerator = this + override fun generateSharedSecretToByteArrayBlocking(other: XDH.PrivateKey): ByteArray { + check(other is XdhPrivateKey) { "Only key produced by JDK provider is supported" } + return keyAgreement.doAgreement(state, other.key, key) + } + } + + private class XdhPrivateKey( + private val state: JdkCryptographyState, + val key: JPrivateKey, + ) : XDH.PrivateKey, BaseEdPrivateKey(key), SharedSecretGenerator { + private val keyAgreement = state.keyAgreement("XDH") + override fun sharedSecretGenerator(): SharedSecretGenerator = this + override fun generateSharedSecretToByteArrayBlocking(other: XDH.PublicKey): ByteArray { + check(other is XdhPublicKey) { "Only key produced by JDK provider is supported" } + return keyAgreement.doAgreement(state, key, other.key) + } + } +} From 5c7dd763bcc695d0ffce8edc91e36d08f17c24f6 Mon Sep 17 00:00:00 2001 From: waltkb <68587968+waltkb@users.noreply.github.com> Date: Wed, 12 Feb 2025 21:25:30 +0100 Subject: [PATCH 03/22] Add new interfaces to JdkCryptographyProvider and format list --- .../jdk/src/jvmMain/kotlin/JdkCryptographyProvider.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cryptography-providers/jdk/src/jvmMain/kotlin/JdkCryptographyProvider.kt b/cryptography-providers/jdk/src/jvmMain/kotlin/JdkCryptographyProvider.kt index 5d978df3..7de928ce 100644 --- a/cryptography-providers/jdk/src/jvmMain/kotlin/JdkCryptographyProvider.kt +++ b/cryptography-providers/jdk/src/jvmMain/kotlin/JdkCryptographyProvider.kt @@ -4,6 +4,7 @@ package dev.whyoleg.cryptography.providers.jdk +import algorithms.* import dev.whyoleg.cryptography.* import dev.whyoleg.cryptography.algorithms.* import dev.whyoleg.cryptography.providers.jdk.algorithms.* @@ -131,7 +132,9 @@ internal class JdkCryptographyProvider(provider: Provider?) : CryptographyProvid RSA.PKCS1 -> JdkRsaPkcs1(state) RSA.RAW -> JdkRsaRaw(state) ECDSA -> JdkEcdsa(state) + EdDSA -> JdkEdDSA(state) ECDH -> JdkEcdh(state) + XDH -> JdkXDH(state) PBKDF2 -> JdkPbkdf2(state) HKDF -> JdkHkdf(state, this) else -> null From 2c9c306969ed1a18285e0a12e7354cd914378577 Mon Sep 17 00:00:00 2001 From: waltkb <68587968+waltkb@users.noreply.github.com> Date: Thu, 13 Feb 2025 12:09:57 +0100 Subject: [PATCH 04/22] Updated .api definitions --- cryptography-core/api/cryptography-core.api | 141 +++++++++++++++++ .../api/cryptography-core.klib.api | 145 ++++++++++++++++++ 2 files changed, 286 insertions(+) diff --git a/cryptography-core/api/cryptography-core.api b/cryptography-core/api/cryptography-core.api index 5ec6874f..e1a2d6a9 100644 --- a/cryptography-core/api/cryptography-core.api +++ b/cryptography-core/api/cryptography-core.api @@ -1,3 +1,144 @@ +public abstract interface class algorithms/Ed : dev/whyoleg/cryptography/CryptographyAlgorithm { + public abstract fun keyPairGenerator-48_mLeQ (Ljava/lang/String;)Ldev/whyoleg/cryptography/materials/key/KeyGenerator; + public abstract fun privateKeyDecoder-48_mLeQ (Ljava/lang/String;)Ldev/whyoleg/cryptography/materials/key/KeyDecoder; + public abstract fun publicKeyDecoder-48_mLeQ (Ljava/lang/String;)Ldev/whyoleg/cryptography/materials/key/KeyDecoder; +} + +public final class algorithms/Ed$Curve { + public static final field Companion Lalgorithms/Ed$Curve$Companion; + public static final synthetic fun box-impl (Ljava/lang/String;)Lalgorithms/Ed$Curve; + public static fun constructor-impl (Ljava/lang/String;)Ljava/lang/String; + public fun equals (Ljava/lang/Object;)Z + public static fun equals-impl (Ljava/lang/String;Ljava/lang/Object;)Z + public static final fun equals-impl0 (Ljava/lang/String;Ljava/lang/String;)Z + public final fun getName ()Ljava/lang/String; + public fun hashCode ()I + public static fun hashCode-impl (Ljava/lang/String;)I + public fun toString ()Ljava/lang/String; + public static fun toString-impl (Ljava/lang/String;)Ljava/lang/String; + public final synthetic fun unbox-impl ()Ljava/lang/String; +} + +public final class algorithms/Ed$Curve$Companion { + public final fun getEd25519-ZWrx85M ()Ljava/lang/String; + public final fun getEd448-ZWrx85M ()Ljava/lang/String; + public final fun getX25519-ZWrx85M ()Ljava/lang/String; + public final fun getX448-ZWrx85M ()Ljava/lang/String; +} + +public abstract interface class algorithms/Ed$KeyPair : dev/whyoleg/cryptography/materials/key/Key { + public abstract fun getPrivateKey ()Lalgorithms/Ed$PrivateKey; + public abstract fun getPublicKey ()Lalgorithms/Ed$PublicKey; +} + +public abstract interface class algorithms/Ed$PrivateKey : dev/whyoleg/cryptography/materials/key/EncodableKey { +} + +public abstract class algorithms/Ed$PrivateKey$Format : dev/whyoleg/cryptography/materials/key/KeyFormat { + public final fun toString ()Ljava/lang/String; +} + +public final class algorithms/Ed$PrivateKey$Format$DER : algorithms/Ed$PrivateKey$Format { + public static final field INSTANCE Lalgorithms/Ed$PrivateKey$Format$DER; + public fun equals (Ljava/lang/Object;)Z + public fun getName ()Ljava/lang/String; + public fun hashCode ()I +} + +public final class algorithms/Ed$PrivateKey$Format$JWK : algorithms/Ed$PrivateKey$Format { + public static final field INSTANCE Lalgorithms/Ed$PrivateKey$Format$JWK; + public fun equals (Ljava/lang/Object;)Z + public fun getName ()Ljava/lang/String; + public fun hashCode ()I +} + +public final class algorithms/Ed$PrivateKey$Format$PEM : algorithms/Ed$PrivateKey$Format { + public static final field INSTANCE Lalgorithms/Ed$PrivateKey$Format$PEM; + public fun equals (Ljava/lang/Object;)Z + public fun getName ()Ljava/lang/String; + public fun hashCode ()I +} + +public final class algorithms/Ed$PrivateKey$Format$RAW : algorithms/Ed$PrivateKey$Format { + public static final field INSTANCE Lalgorithms/Ed$PrivateKey$Format$RAW; + public fun equals (Ljava/lang/Object;)Z + public fun getName ()Ljava/lang/String; + public fun hashCode ()I +} + +public abstract interface class algorithms/Ed$PublicKey : dev/whyoleg/cryptography/materials/key/EncodableKey { +} + +public abstract class algorithms/Ed$PublicKey$Format : dev/whyoleg/cryptography/materials/key/KeyFormat { + public final fun toString ()Ljava/lang/String; +} + +public final class algorithms/Ed$PublicKey$Format$DER : algorithms/Ed$PublicKey$Format { + public static final field INSTANCE Lalgorithms/Ed$PublicKey$Format$DER; + public fun equals (Ljava/lang/Object;)Z + public fun getName ()Ljava/lang/String; + public fun hashCode ()I +} + +public final class algorithms/Ed$PublicKey$Format$JWK : algorithms/Ed$PublicKey$Format { + public static final field INSTANCE Lalgorithms/Ed$PublicKey$Format$JWK; + public fun equals (Ljava/lang/Object;)Z + public fun getName ()Ljava/lang/String; + public fun hashCode ()I +} + +public final class algorithms/Ed$PublicKey$Format$PEM : algorithms/Ed$PublicKey$Format { + public static final field INSTANCE Lalgorithms/Ed$PublicKey$Format$PEM; + public fun equals (Ljava/lang/Object;)Z + public fun getName ()Ljava/lang/String; + public fun hashCode ()I +} + +public final class algorithms/Ed$PublicKey$Format$RAW : algorithms/Ed$PublicKey$Format { + public static final field INSTANCE Lalgorithms/Ed$PublicKey$Format$RAW; + public fun equals (Ljava/lang/Object;)Z + public fun getName ()Ljava/lang/String; + public fun hashCode ()I +} + +public abstract interface class algorithms/EdDSA : algorithms/Ed { + public static final field Companion Lalgorithms/EdDSA$Companion; + public fun getId ()Ldev/whyoleg/cryptography/CryptographyAlgorithmId; +} + +public final class algorithms/EdDSA$Companion : dev/whyoleg/cryptography/CryptographyAlgorithmId { +} + +public abstract interface class algorithms/EdDSA$KeyPair : algorithms/Ed$KeyPair { +} + +public abstract interface class algorithms/EdDSA$PrivateKey : algorithms/Ed$PrivateKey { + public abstract fun signatureGenerator ()Ldev/whyoleg/cryptography/operations/SignatureGenerator; +} + +public abstract interface class algorithms/EdDSA$PublicKey : algorithms/Ed$PublicKey { + public abstract fun signatureVerifier ()Ldev/whyoleg/cryptography/operations/SignatureVerifier; +} + +public abstract interface class algorithms/XDH : algorithms/Ed { + public static final field Companion Lalgorithms/XDH$Companion; + public fun getId ()Ldev/whyoleg/cryptography/CryptographyAlgorithmId; +} + +public final class algorithms/XDH$Companion : dev/whyoleg/cryptography/CryptographyAlgorithmId { +} + +public abstract interface class algorithms/XDH$KeyPair : algorithms/Ed$KeyPair { +} + +public abstract interface class algorithms/XDH$PrivateKey : algorithms/Ed$PrivateKey { + public abstract fun sharedSecretGenerator ()Ldev/whyoleg/cryptography/operations/SharedSecretGenerator; +} + +public abstract interface class algorithms/XDH$PublicKey : algorithms/Ed$PublicKey { + public abstract fun sharedSecretGenerator ()Ldev/whyoleg/cryptography/operations/SharedSecretGenerator; +} + public final class dev/whyoleg/cryptography/BinarySize : java/lang/Comparable { public static final field Companion Ldev/whyoleg/cryptography/BinarySize$Companion; public static final synthetic fun box-impl (I)Ldev/whyoleg/cryptography/BinarySize; diff --git a/cryptography-core/api/cryptography-core.klib.api b/cryptography-core/api/cryptography-core.klib.api index b853e00c..fe0d67d4 100644 --- a/cryptography-core/api/cryptography-core.klib.api +++ b/cryptography-core/api/cryptography-core.klib.api @@ -14,6 +14,117 @@ open annotation class dev.whyoleg.cryptography/DelicateCryptographyApi : kotlin/ constructor () // dev.whyoleg.cryptography/DelicateCryptographyApi.|(){}[0] } +abstract interface <#A: algorithms/Ed.PublicKey, #B: algorithms/Ed.PrivateKey, #C: algorithms/Ed.KeyPair<#A, #B>> algorithms/Ed : dev.whyoleg.cryptography/CryptographyAlgorithm { // algorithms/Ed|null[0] + abstract fun keyPairGenerator(algorithms/Ed.Curve): dev.whyoleg.cryptography.materials.key/KeyGenerator<#C> // algorithms/Ed.keyPairGenerator|keyPairGenerator(algorithms.Ed.Curve){}[0] + abstract fun privateKeyDecoder(algorithms/Ed.Curve): dev.whyoleg.cryptography.materials.key/KeyDecoder // algorithms/Ed.privateKeyDecoder|privateKeyDecoder(algorithms.Ed.Curve){}[0] + abstract fun publicKeyDecoder(algorithms/Ed.Curve): dev.whyoleg.cryptography.materials.key/KeyDecoder // algorithms/Ed.publicKeyDecoder|publicKeyDecoder(algorithms.Ed.Curve){}[0] + + abstract interface <#A1: algorithms/Ed.PublicKey, #B1: algorithms/Ed.PrivateKey> KeyPair : dev.whyoleg.cryptography.materials.key/Key { // algorithms/Ed.KeyPair|null[0] + abstract val privateKey // algorithms/Ed.KeyPair.privateKey|{}privateKey[0] + abstract fun (): #B1 // algorithms/Ed.KeyPair.privateKey.|(){}[0] + abstract val publicKey // algorithms/Ed.KeyPair.publicKey|{}publicKey[0] + abstract fun (): #A1 // algorithms/Ed.KeyPair.publicKey.|(){}[0] + } + + abstract interface PrivateKey : dev.whyoleg.cryptography.materials.key/EncodableKey { // algorithms/Ed.PrivateKey|null[0] + sealed class Format : dev.whyoleg.cryptography.materials.key/KeyFormat { // algorithms/Ed.PrivateKey.Format|null[0] + final fun toString(): kotlin/String // algorithms/Ed.PrivateKey.Format.toString|toString(){}[0] + + final object DER : algorithms/Ed.PrivateKey.Format { // algorithms/Ed.PrivateKey.Format.DER|null[0] + final val name // algorithms/Ed.PrivateKey.Format.DER.name|{}name[0] + final fun (): kotlin/String // algorithms/Ed.PrivateKey.Format.DER.name.|(){}[0] + + final fun equals(kotlin/Any?): kotlin/Boolean // algorithms/Ed.PrivateKey.Format.DER.equals|equals(kotlin.Any?){}[0] + final fun hashCode(): kotlin/Int // algorithms/Ed.PrivateKey.Format.DER.hashCode|hashCode(){}[0] + } + + final object JWK : algorithms/Ed.PrivateKey.Format { // algorithms/Ed.PrivateKey.Format.JWK|null[0] + final val name // algorithms/Ed.PrivateKey.Format.JWK.name|{}name[0] + final fun (): kotlin/String // algorithms/Ed.PrivateKey.Format.JWK.name.|(){}[0] + + final fun equals(kotlin/Any?): kotlin/Boolean // algorithms/Ed.PrivateKey.Format.JWK.equals|equals(kotlin.Any?){}[0] + final fun hashCode(): kotlin/Int // algorithms/Ed.PrivateKey.Format.JWK.hashCode|hashCode(){}[0] + } + + final object PEM : algorithms/Ed.PrivateKey.Format { // algorithms/Ed.PrivateKey.Format.PEM|null[0] + final val name // algorithms/Ed.PrivateKey.Format.PEM.name|{}name[0] + final fun (): kotlin/String // algorithms/Ed.PrivateKey.Format.PEM.name.|(){}[0] + + final fun equals(kotlin/Any?): kotlin/Boolean // algorithms/Ed.PrivateKey.Format.PEM.equals|equals(kotlin.Any?){}[0] + final fun hashCode(): kotlin/Int // algorithms/Ed.PrivateKey.Format.PEM.hashCode|hashCode(){}[0] + } + + final object RAW : algorithms/Ed.PrivateKey.Format { // algorithms/Ed.PrivateKey.Format.RAW|null[0] + final val name // algorithms/Ed.PrivateKey.Format.RAW.name|{}name[0] + final fun (): kotlin/String // algorithms/Ed.PrivateKey.Format.RAW.name.|(){}[0] + + final fun equals(kotlin/Any?): kotlin/Boolean // algorithms/Ed.PrivateKey.Format.RAW.equals|equals(kotlin.Any?){}[0] + final fun hashCode(): kotlin/Int // algorithms/Ed.PrivateKey.Format.RAW.hashCode|hashCode(){}[0] + } + } + } + + abstract interface PublicKey : dev.whyoleg.cryptography.materials.key/EncodableKey { // algorithms/Ed.PublicKey|null[0] + sealed class Format : dev.whyoleg.cryptography.materials.key/KeyFormat { // algorithms/Ed.PublicKey.Format|null[0] + final fun toString(): kotlin/String // algorithms/Ed.PublicKey.Format.toString|toString(){}[0] + + final object DER : algorithms/Ed.PublicKey.Format { // algorithms/Ed.PublicKey.Format.DER|null[0] + final val name // algorithms/Ed.PublicKey.Format.DER.name|{}name[0] + final fun (): kotlin/String // algorithms/Ed.PublicKey.Format.DER.name.|(){}[0] + + final fun equals(kotlin/Any?): kotlin/Boolean // algorithms/Ed.PublicKey.Format.DER.equals|equals(kotlin.Any?){}[0] + final fun hashCode(): kotlin/Int // algorithms/Ed.PublicKey.Format.DER.hashCode|hashCode(){}[0] + } + + final object JWK : algorithms/Ed.PublicKey.Format { // algorithms/Ed.PublicKey.Format.JWK|null[0] + final val name // algorithms/Ed.PublicKey.Format.JWK.name|{}name[0] + final fun (): kotlin/String // algorithms/Ed.PublicKey.Format.JWK.name.|(){}[0] + + final fun equals(kotlin/Any?): kotlin/Boolean // algorithms/Ed.PublicKey.Format.JWK.equals|equals(kotlin.Any?){}[0] + final fun hashCode(): kotlin/Int // algorithms/Ed.PublicKey.Format.JWK.hashCode|hashCode(){}[0] + } + + final object PEM : algorithms/Ed.PublicKey.Format { // algorithms/Ed.PublicKey.Format.PEM|null[0] + final val name // algorithms/Ed.PublicKey.Format.PEM.name|{}name[0] + final fun (): kotlin/String // algorithms/Ed.PublicKey.Format.PEM.name.|(){}[0] + + final fun equals(kotlin/Any?): kotlin/Boolean // algorithms/Ed.PublicKey.Format.PEM.equals|equals(kotlin.Any?){}[0] + final fun hashCode(): kotlin/Int // algorithms/Ed.PublicKey.Format.PEM.hashCode|hashCode(){}[0] + } + + final object RAW : algorithms/Ed.PublicKey.Format { // algorithms/Ed.PublicKey.Format.RAW|null[0] + final val name // algorithms/Ed.PublicKey.Format.RAW.name|{}name[0] + final fun (): kotlin/String // algorithms/Ed.PublicKey.Format.RAW.name.|(){}[0] + + final fun equals(kotlin/Any?): kotlin/Boolean // algorithms/Ed.PublicKey.Format.RAW.equals|equals(kotlin.Any?){}[0] + final fun hashCode(): kotlin/Int // algorithms/Ed.PublicKey.Format.RAW.hashCode|hashCode(){}[0] + } + } + } + + final value class Curve { // algorithms/Ed.Curve|null[0] + constructor (kotlin/String) // algorithms/Ed.Curve.|(kotlin.String){}[0] + + final val name // algorithms/Ed.Curve.name|{}name[0] + final fun (): kotlin/String // algorithms/Ed.Curve.name.|(){}[0] + + final fun equals(kotlin/Any?): kotlin/Boolean // algorithms/Ed.Curve.equals|equals(kotlin.Any?){}[0] + final fun hashCode(): kotlin/Int // algorithms/Ed.Curve.hashCode|hashCode(){}[0] + final fun toString(): kotlin/String // algorithms/Ed.Curve.toString|toString(){}[0] + + final object Companion { // algorithms/Ed.Curve.Companion|null[0] + final val Ed25519 // algorithms/Ed.Curve.Companion.Ed25519|{}Ed25519[0] + final fun (): algorithms/Ed.Curve // algorithms/Ed.Curve.Companion.Ed25519.|(){}[0] + final val Ed448 // algorithms/Ed.Curve.Companion.Ed448|{}Ed448[0] + final fun (): algorithms/Ed.Curve // algorithms/Ed.Curve.Companion.Ed448.|(){}[0] + final val X25519 // algorithms/Ed.Curve.Companion.X25519|{}X25519[0] + final fun (): algorithms/Ed.Curve // algorithms/Ed.Curve.Companion.X25519.|(){}[0] + final val X448 // algorithms/Ed.Curve.Companion.X448|{}X448[0] + final fun (): algorithms/Ed.Curve // algorithms/Ed.Curve.Companion.X448.|(){}[0] + } + } +} + abstract interface <#A: dev.whyoleg.cryptography.algorithms/AES.Key> dev.whyoleg.cryptography.algorithms/AES : dev.whyoleg.cryptography/CryptographyAlgorithm { // dev.whyoleg.cryptography.algorithms/AES|null[0] abstract fun keyDecoder(): dev.whyoleg.cryptography.materials.key/KeyDecoder // dev.whyoleg.cryptography.algorithms/AES.keyDecoder|keyDecoder(){}[0] abstract fun keyGenerator(dev.whyoleg.cryptography/BinarySize = ...): dev.whyoleg.cryptography.materials.key/KeyGenerator<#A> // dev.whyoleg.cryptography.algorithms/AES.keyGenerator|keyGenerator(dev.whyoleg.cryptography.BinarySize){}[0] @@ -499,6 +610,40 @@ abstract interface <#A: dev.whyoleg.cryptography.materials.key/KeyFormat> dev.wh open suspend fun encodeToByteString(#A): kotlinx.io.bytestring/ByteString // dev.whyoleg.cryptography.materials.key/EncodableKey.encodeToByteString|encodeToByteString(1:0){}[0] } +abstract interface algorithms/EdDSA : algorithms/Ed { // algorithms/EdDSA|null[0] + open val id // algorithms/EdDSA.id|{}id[0] + open fun (): dev.whyoleg.cryptography/CryptographyAlgorithmId // algorithms/EdDSA.id.|(){}[0] + + abstract interface KeyPair : algorithms/Ed.KeyPair // algorithms/EdDSA.KeyPair|null[0] + + abstract interface PrivateKey : algorithms/Ed.PrivateKey { // algorithms/EdDSA.PrivateKey|null[0] + abstract fun signatureGenerator(): dev.whyoleg.cryptography.operations/SignatureGenerator // algorithms/EdDSA.PrivateKey.signatureGenerator|signatureGenerator(){}[0] + } + + abstract interface PublicKey : algorithms/Ed.PublicKey { // algorithms/EdDSA.PublicKey|null[0] + abstract fun signatureVerifier(): dev.whyoleg.cryptography.operations/SignatureVerifier // algorithms/EdDSA.PublicKey.signatureVerifier|signatureVerifier(){}[0] + } + + final object Companion : dev.whyoleg.cryptography/CryptographyAlgorithmId // algorithms/EdDSA.Companion|null[0] +} + +abstract interface algorithms/XDH : algorithms/Ed { // algorithms/XDH|null[0] + open val id // algorithms/XDH.id|{}id[0] + open fun (): dev.whyoleg.cryptography/CryptographyAlgorithmId // algorithms/XDH.id.|(){}[0] + + abstract interface KeyPair : algorithms/Ed.KeyPair // algorithms/XDH.KeyPair|null[0] + + abstract interface PrivateKey : algorithms/Ed.PrivateKey { // algorithms/XDH.PrivateKey|null[0] + abstract fun sharedSecretGenerator(): dev.whyoleg.cryptography.operations/SharedSecretGenerator // algorithms/XDH.PrivateKey.sharedSecretGenerator|sharedSecretGenerator(){}[0] + } + + abstract interface PublicKey : algorithms/Ed.PublicKey { // algorithms/XDH.PublicKey|null[0] + abstract fun sharedSecretGenerator(): dev.whyoleg.cryptography.operations/SharedSecretGenerator // algorithms/XDH.PublicKey.sharedSecretGenerator|sharedSecretGenerator(){}[0] + } + + final object Companion : dev.whyoleg.cryptography/CryptographyAlgorithmId // algorithms/XDH.Companion|null[0] +} + abstract interface dev.whyoleg.cryptography.algorithms/Digest : dev.whyoleg.cryptography/CryptographyAlgorithm { // dev.whyoleg.cryptography.algorithms/Digest|null[0] abstract val id // dev.whyoleg.cryptography.algorithms/Digest.id|{}id[0] abstract fun (): dev.whyoleg.cryptography/CryptographyAlgorithmId // dev.whyoleg.cryptography.algorithms/Digest.id.|(){}[0] From 83138af6d4958b61a2e2084c9df8f1e6939d20fe Mon Sep 17 00:00:00 2001 From: waltkb <68587968+waltkb@users.noreply.github.com> Date: Wed, 19 Feb 2025 21:52:41 +0100 Subject: [PATCH 05/22] Change package name for Ed --- cryptography-core/src/commonMain/kotlin/algorithms/Ed.kt | 2 +- .../jdk/src/jvmMain/kotlin/algorithms/JdkEd.kt | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/cryptography-core/src/commonMain/kotlin/algorithms/Ed.kt b/cryptography-core/src/commonMain/kotlin/algorithms/Ed.kt index 3f3bf50f..d9006371 100644 --- a/cryptography-core/src/commonMain/kotlin/algorithms/Ed.kt +++ b/cryptography-core/src/commonMain/kotlin/algorithms/Ed.kt @@ -1,4 +1,4 @@ -package algorithms +package dev.whyoleg.cryptography.algorithm import dev.whyoleg.cryptography.* import dev.whyoleg.cryptography.materials.key.* diff --git a/cryptography-providers/jdk/src/jvmMain/kotlin/algorithms/JdkEd.kt b/cryptography-providers/jdk/src/jvmMain/kotlin/algorithms/JdkEd.kt index a9825768..b072cf87 100644 --- a/cryptography-providers/jdk/src/jvmMain/kotlin/algorithms/JdkEd.kt +++ b/cryptography-providers/jdk/src/jvmMain/kotlin/algorithms/JdkEd.kt @@ -1,5 +1,6 @@ -package algorithms +package dev.whyoleg.cryptography.providers.jdk.algorithms +import dev.whyoleg.cryptography.algorithm.Ed import dev.whyoleg.cryptography.materials.key.KeyDecoder import dev.whyoleg.cryptography.materials.key.KeyGenerator import dev.whyoleg.cryptography.providers.jdk.JKeyPair From efb1b423642372e3aea9a7d0c040b9d85886e0f0 Mon Sep 17 00:00:00 2001 From: waltkb <68587968+waltkb@users.noreply.github.com> Date: Wed, 19 Feb 2025 21:37:58 +0100 Subject: [PATCH 06/22] Replace mentions of secp256k1 --- docs/providers/jdk.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/providers/jdk.md b/docs/providers/jdk.md index 3486aaa3..45186f69 100644 --- a/docs/providers/jdk.md +++ b/docs/providers/jdk.md @@ -7,7 +7,6 @@ For supported targets and algorithms, please consult [Supported primitives secti ## Limitations * KeyFormat: doesn't support `JWK` key format yet - ## Example ```kotlin From aae570f434d34973089d0fd8078ebce3ea6c7724 Mon Sep 17 00:00:00 2001 From: waltkb <68587968+waltkb@users.noreply.github.com> Date: Thu, 17 Apr 2025 22:02:23 +0200 Subject: [PATCH 07/22] Add brainpool exclusion to support.kt --- .../tests/src/commonMain/kotlin/support.kt | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/cryptography-providers/tests/src/commonMain/kotlin/support.kt b/cryptography-providers/tests/src/commonMain/kotlin/support.kt index f4e61b58..90850e9f 100644 --- a/cryptography-providers/tests/src/commonMain/kotlin/support.kt +++ b/cryptography-providers/tests/src/commonMain/kotlin/support.kt @@ -96,9 +96,14 @@ fun AlgorithmTestScope.supportsEncryption(): Boolean = supports { fun AlgorithmTestScope>.supportsCurve(curve: EC.Curve): Boolean = supports { when { // JDK default, WebCrypto and Apple don't support secp256k1 or brainpool - curve in listOf(EC.Curve.secp256k1, EC.Curve.brainpoolP256r1, EC.Curve.brainpoolP384r1, EC.Curve.brainpoolP512r1) && ( - provider.isJdkDefault || provider.isWebCrypto || provider.isApple || provider.isCryptoKit - ) -> "ECDSA ${curve.name}" + curve in listOf( + EC.Curve.secp256k1, + EC.Curve.brainpoolP256r1, + EC.Curve.brainpoolP384r1, + EC.Curve.brainpoolP512r1, + ) && ( + provider.isJdkDefault || provider.isWebCrypto || provider.isApple || provider.isCryptoKit + ) -> "ECDSA ${curve.name}" else -> null } From 28ba0845db7244c5cee58efe75687e21c0a1e98e Mon Sep 17 00:00:00 2001 From: waltkb <68587968+waltkb@users.noreply.github.com> Date: Thu, 17 Apr 2025 22:38:15 +0200 Subject: [PATCH 08/22] Fix package names --- .../src/commonMain/kotlin/algorithms/Ed.kt | 2 +- .../src/commonMain/kotlin/algorithms/EdDSA.kt | 8 +++----- .../src/commonMain/kotlin/algorithms/XDH.kt | 2 +- .../src/jvmMain/kotlin/JdkCryptographyProvider.kt | 1 - .../jdk/src/jvmMain/kotlin/algorithms/JdkEd.kt | 2 +- .../jdk/src/jvmMain/kotlin/algorithms/JdkEdDSA.kt | 14 +++++--------- .../jdk/src/jvmMain/kotlin/algorithms/JdkXDH.kt | 3 ++- 7 files changed, 13 insertions(+), 19 deletions(-) diff --git a/cryptography-core/src/commonMain/kotlin/algorithms/Ed.kt b/cryptography-core/src/commonMain/kotlin/algorithms/Ed.kt index d9006371..56e57e2e 100644 --- a/cryptography-core/src/commonMain/kotlin/algorithms/Ed.kt +++ b/cryptography-core/src/commonMain/kotlin/algorithms/Ed.kt @@ -1,4 +1,4 @@ -package dev.whyoleg.cryptography.algorithm +package dev.whyoleg.cryptography.algorithms import dev.whyoleg.cryptography.* import dev.whyoleg.cryptography.materials.key.* diff --git a/cryptography-core/src/commonMain/kotlin/algorithms/EdDSA.kt b/cryptography-core/src/commonMain/kotlin/algorithms/EdDSA.kt index 489be029..75e441f5 100644 --- a/cryptography-core/src/commonMain/kotlin/algorithms/EdDSA.kt +++ b/cryptography-core/src/commonMain/kotlin/algorithms/EdDSA.kt @@ -1,9 +1,7 @@ -package algorithms +package dev.whyoleg.cryptography.algorithms -import dev.whyoleg.cryptography.CryptographyAlgorithmId -import dev.whyoleg.cryptography.CryptographyProviderApi -import dev.whyoleg.cryptography.operations.SignatureGenerator -import dev.whyoleg.cryptography.operations.SignatureVerifier +import dev.whyoleg.cryptography.* +import dev.whyoleg.cryptography.operations.* @SubclassOptInRequired(CryptographyProviderApi::class) public interface EdDSA : Ed { diff --git a/cryptography-core/src/commonMain/kotlin/algorithms/XDH.kt b/cryptography-core/src/commonMain/kotlin/algorithms/XDH.kt index 91a0ca77..616f4f85 100644 --- a/cryptography-core/src/commonMain/kotlin/algorithms/XDH.kt +++ b/cryptography-core/src/commonMain/kotlin/algorithms/XDH.kt @@ -1,4 +1,4 @@ -package algorithms +package dev.whyoleg.cryptography.algorithms import dev.whyoleg.cryptography.CryptographyAlgorithmId import dev.whyoleg.cryptography.CryptographyProviderApi diff --git a/cryptography-providers/jdk/src/jvmMain/kotlin/JdkCryptographyProvider.kt b/cryptography-providers/jdk/src/jvmMain/kotlin/JdkCryptographyProvider.kt index 7de928ce..8adc7eec 100644 --- a/cryptography-providers/jdk/src/jvmMain/kotlin/JdkCryptographyProvider.kt +++ b/cryptography-providers/jdk/src/jvmMain/kotlin/JdkCryptographyProvider.kt @@ -4,7 +4,6 @@ package dev.whyoleg.cryptography.providers.jdk -import algorithms.* import dev.whyoleg.cryptography.* import dev.whyoleg.cryptography.algorithms.* import dev.whyoleg.cryptography.providers.jdk.algorithms.* diff --git a/cryptography-providers/jdk/src/jvmMain/kotlin/algorithms/JdkEd.kt b/cryptography-providers/jdk/src/jvmMain/kotlin/algorithms/JdkEd.kt index b072cf87..e5fda400 100644 --- a/cryptography-providers/jdk/src/jvmMain/kotlin/algorithms/JdkEd.kt +++ b/cryptography-providers/jdk/src/jvmMain/kotlin/algorithms/JdkEd.kt @@ -1,6 +1,6 @@ package dev.whyoleg.cryptography.providers.jdk.algorithms -import dev.whyoleg.cryptography.algorithm.Ed +import dev.whyoleg.cryptography.algorithms.Ed import dev.whyoleg.cryptography.materials.key.KeyDecoder import dev.whyoleg.cryptography.materials.key.KeyGenerator import dev.whyoleg.cryptography.providers.jdk.JKeyPair diff --git a/cryptography-providers/jdk/src/jvmMain/kotlin/algorithms/JdkEdDSA.kt b/cryptography-providers/jdk/src/jvmMain/kotlin/algorithms/JdkEdDSA.kt index 9e0b9455..141cfcdf 100644 --- a/cryptography-providers/jdk/src/jvmMain/kotlin/algorithms/JdkEdDSA.kt +++ b/cryptography-providers/jdk/src/jvmMain/kotlin/algorithms/JdkEdDSA.kt @@ -1,13 +1,9 @@ -package algorithms +package dev.whyoleg.cryptography.providers.jdk.algorithms -import dev.whyoleg.cryptography.operations.SignatureGenerator -import dev.whyoleg.cryptography.operations.SignatureVerifier -import dev.whyoleg.cryptography.providers.jdk.JKeyPair -import dev.whyoleg.cryptography.providers.jdk.JPrivateKey -import dev.whyoleg.cryptography.providers.jdk.JPublicKey -import dev.whyoleg.cryptography.providers.jdk.JdkCryptographyState -import dev.whyoleg.cryptography.providers.jdk.operations.JdkSignatureGenerator -import dev.whyoleg.cryptography.providers.jdk.operations.JdkSignatureVerifier +import dev.whyoleg.cryptography.algorithms.EdDSA +import dev.whyoleg.cryptography.operations.* +import dev.whyoleg.cryptography.providers.jdk.* +import dev.whyoleg.cryptography.providers.jdk.operations.* internal class JdkEdDSA(state: JdkCryptographyState) : JdkEd(state), EdDSA { override fun JPublicKey.convert(): EdDSA.PublicKey = EdDsaPublicKey(state, this) diff --git a/cryptography-providers/jdk/src/jvmMain/kotlin/algorithms/JdkXDH.kt b/cryptography-providers/jdk/src/jvmMain/kotlin/algorithms/JdkXDH.kt index 799e2924..e6edccd0 100644 --- a/cryptography-providers/jdk/src/jvmMain/kotlin/algorithms/JdkXDH.kt +++ b/cryptography-providers/jdk/src/jvmMain/kotlin/algorithms/JdkXDH.kt @@ -1,5 +1,6 @@ -package algorithms +package dev.whyoleg.cryptography.providers.jdk.algorithms +import dev.whyoleg.cryptography.algorithms.XDH import dev.whyoleg.cryptography.operations.SharedSecretGenerator import dev.whyoleg.cryptography.providers.jdk.JKeyPair import dev.whyoleg.cryptography.providers.jdk.JPrivateKey From c8488c9a2b9a24362bf1b5b7018a29e8e2286937 Mon Sep 17 00:00:00 2001 From: Aria Wisp Date: Wed, 3 Sep 2025 19:40:24 -0600 Subject: [PATCH 09/22] address PR comments and add apple-native approaches --- .../tests/GenerateProviderTestsTask.kt | 4 + cryptography-core/api/cryptography-core.api | 333 +++++++++------- .../api/cryptography-core.klib.api | 363 +++++++++++------- .../src/commonMain/kotlin/algorithms/Ed.kt | 76 ---- .../src/commonMain/kotlin/algorithms/EdDSA.kt | 58 ++- .../src/commonMain/kotlin/algorithms/XDH.kt | 63 ++- .../kotlin/CryptoKitCryptographyProvider.kt | 2 + .../kotlin/algorithms/CryptoKitEdDSA.kt | 182 +++++++++ .../kotlin/algorithms/CryptoKitXDH.kt | 130 +++++++ .../src/commonMain/swift/SwiftEd.swift | 87 +++++ .../src/jvmMain/kotlin/algorithms/JdkEd.kt | 108 ------ .../src/jvmMain/kotlin/algorithms/JdkEdDSA.kt | 68 +++- .../src/jvmMain/kotlin/algorithms/JdkXDH.kt | 79 +++- .../kotlin/Openssl3CryptographyProvider.kt | 2 + .../kotlin/algorithms/Openssl3EdDSA.kt | 265 +++++++++++++ .../kotlin/algorithms/Openssl3XDH.kt | 114 ++++++ .../commonMain/kotlin/default/EdDsaTest.kt | 49 +++ .../kotlin/default/SupportedAlgorithmsTest.kt | 5 + .../src/commonMain/kotlin/default/XdhTest.kt | 31 ++ .../tests/src/commonMain/kotlin/support.kt | 5 + .../kotlin/WebCryptoCryptographyProvider.kt | 24 ++ .../kotlin/algorithms/WebCryptoEdDSA.kt | 107 ++++++ .../kotlin/algorithms/WebCryptoXDH.kt | 136 +++++++ .../commonMain/kotlin/internal/Algorithms.kt | 2 + .../src/commonMain/kotlin/internal/Env.kt | 10 + .../jsMain/kotlin/internal/Algorithms.js.kt | 8 + .../src/jsMain/kotlin/internal/Env.js.kt | 28 ++ .../kotlin/internal/Algorithms.wasmJs.kt | 8 + .../wasmJsMain/kotlin/internal/Env.wasmJs.kt | 7 + docs/providers/cryptokit.md | 1 + docs/providers/index.md | 2 + docs/providers/jdk.md | 6 + docs/providers/openssl3.md | 5 + docs/providers/webcrypto.md | 11 + 34 files changed, 1877 insertions(+), 502 deletions(-) delete mode 100644 cryptography-core/src/commonMain/kotlin/algorithms/Ed.kt create mode 100644 cryptography-providers/cryptokit/src/commonMain/kotlin/algorithms/CryptoKitEdDSA.kt create mode 100644 cryptography-providers/cryptokit/src/commonMain/kotlin/algorithms/CryptoKitXDH.kt create mode 100644 cryptography-providers/cryptokit/src/commonMain/swift/SwiftEd.swift delete mode 100644 cryptography-providers/jdk/src/jvmMain/kotlin/algorithms/JdkEd.kt create mode 100644 cryptography-providers/openssl3/api/src/commonMain/kotlin/algorithms/Openssl3EdDSA.kt create mode 100644 cryptography-providers/openssl3/api/src/commonMain/kotlin/algorithms/Openssl3XDH.kt create mode 100644 cryptography-providers/tests/src/commonMain/kotlin/default/EdDsaTest.kt create mode 100644 cryptography-providers/tests/src/commonMain/kotlin/default/XdhTest.kt create mode 100644 cryptography-providers/webcrypto/src/commonMain/kotlin/algorithms/WebCryptoEdDSA.kt create mode 100644 cryptography-providers/webcrypto/src/commonMain/kotlin/algorithms/WebCryptoXDH.kt create mode 100644 cryptography-providers/webcrypto/src/commonMain/kotlin/internal/Env.kt create mode 100644 cryptography-providers/webcrypto/src/jsMain/kotlin/internal/Env.js.kt create mode 100644 cryptography-providers/webcrypto/src/wasmJsMain/kotlin/internal/Env.wasmJs.kt diff --git a/build-logic/src/main/kotlin/ckbuild/tests/GenerateProviderTestsTask.kt b/build-logic/src/main/kotlin/ckbuild/tests/GenerateProviderTestsTask.kt index d3a10432..c581f670 100644 --- a/build-logic/src/main/kotlin/ckbuild/tests/GenerateProviderTestsTask.kt +++ b/build-logic/src/main/kotlin/ckbuild/tests/GenerateProviderTestsTask.kt @@ -110,6 +110,10 @@ abstract class GenerateProviderTestsTask : DefaultTask() { "EcdsaCompatibilityTest", "EcdhCompatibilityTest", + // Edwards-family + "EdDsaTest", + "XdhTest", + "RsaOaepTest", "RsaOaepCompatibilityTest", "RsaPkcs1Test", diff --git a/cryptography-core/api/cryptography-core.api b/cryptography-core/api/cryptography-core.api index e1a2d6a9..19feee1f 100644 --- a/cryptography-core/api/cryptography-core.api +++ b/cryptography-core/api/cryptography-core.api @@ -1,144 +1,3 @@ -public abstract interface class algorithms/Ed : dev/whyoleg/cryptography/CryptographyAlgorithm { - public abstract fun keyPairGenerator-48_mLeQ (Ljava/lang/String;)Ldev/whyoleg/cryptography/materials/key/KeyGenerator; - public abstract fun privateKeyDecoder-48_mLeQ (Ljava/lang/String;)Ldev/whyoleg/cryptography/materials/key/KeyDecoder; - public abstract fun publicKeyDecoder-48_mLeQ (Ljava/lang/String;)Ldev/whyoleg/cryptography/materials/key/KeyDecoder; -} - -public final class algorithms/Ed$Curve { - public static final field Companion Lalgorithms/Ed$Curve$Companion; - public static final synthetic fun box-impl (Ljava/lang/String;)Lalgorithms/Ed$Curve; - public static fun constructor-impl (Ljava/lang/String;)Ljava/lang/String; - public fun equals (Ljava/lang/Object;)Z - public static fun equals-impl (Ljava/lang/String;Ljava/lang/Object;)Z - public static final fun equals-impl0 (Ljava/lang/String;Ljava/lang/String;)Z - public final fun getName ()Ljava/lang/String; - public fun hashCode ()I - public static fun hashCode-impl (Ljava/lang/String;)I - public fun toString ()Ljava/lang/String; - public static fun toString-impl (Ljava/lang/String;)Ljava/lang/String; - public final synthetic fun unbox-impl ()Ljava/lang/String; -} - -public final class algorithms/Ed$Curve$Companion { - public final fun getEd25519-ZWrx85M ()Ljava/lang/String; - public final fun getEd448-ZWrx85M ()Ljava/lang/String; - public final fun getX25519-ZWrx85M ()Ljava/lang/String; - public final fun getX448-ZWrx85M ()Ljava/lang/String; -} - -public abstract interface class algorithms/Ed$KeyPair : dev/whyoleg/cryptography/materials/key/Key { - public abstract fun getPrivateKey ()Lalgorithms/Ed$PrivateKey; - public abstract fun getPublicKey ()Lalgorithms/Ed$PublicKey; -} - -public abstract interface class algorithms/Ed$PrivateKey : dev/whyoleg/cryptography/materials/key/EncodableKey { -} - -public abstract class algorithms/Ed$PrivateKey$Format : dev/whyoleg/cryptography/materials/key/KeyFormat { - public final fun toString ()Ljava/lang/String; -} - -public final class algorithms/Ed$PrivateKey$Format$DER : algorithms/Ed$PrivateKey$Format { - public static final field INSTANCE Lalgorithms/Ed$PrivateKey$Format$DER; - public fun equals (Ljava/lang/Object;)Z - public fun getName ()Ljava/lang/String; - public fun hashCode ()I -} - -public final class algorithms/Ed$PrivateKey$Format$JWK : algorithms/Ed$PrivateKey$Format { - public static final field INSTANCE Lalgorithms/Ed$PrivateKey$Format$JWK; - public fun equals (Ljava/lang/Object;)Z - public fun getName ()Ljava/lang/String; - public fun hashCode ()I -} - -public final class algorithms/Ed$PrivateKey$Format$PEM : algorithms/Ed$PrivateKey$Format { - public static final field INSTANCE Lalgorithms/Ed$PrivateKey$Format$PEM; - public fun equals (Ljava/lang/Object;)Z - public fun getName ()Ljava/lang/String; - public fun hashCode ()I -} - -public final class algorithms/Ed$PrivateKey$Format$RAW : algorithms/Ed$PrivateKey$Format { - public static final field INSTANCE Lalgorithms/Ed$PrivateKey$Format$RAW; - public fun equals (Ljava/lang/Object;)Z - public fun getName ()Ljava/lang/String; - public fun hashCode ()I -} - -public abstract interface class algorithms/Ed$PublicKey : dev/whyoleg/cryptography/materials/key/EncodableKey { -} - -public abstract class algorithms/Ed$PublicKey$Format : dev/whyoleg/cryptography/materials/key/KeyFormat { - public final fun toString ()Ljava/lang/String; -} - -public final class algorithms/Ed$PublicKey$Format$DER : algorithms/Ed$PublicKey$Format { - public static final field INSTANCE Lalgorithms/Ed$PublicKey$Format$DER; - public fun equals (Ljava/lang/Object;)Z - public fun getName ()Ljava/lang/String; - public fun hashCode ()I -} - -public final class algorithms/Ed$PublicKey$Format$JWK : algorithms/Ed$PublicKey$Format { - public static final field INSTANCE Lalgorithms/Ed$PublicKey$Format$JWK; - public fun equals (Ljava/lang/Object;)Z - public fun getName ()Ljava/lang/String; - public fun hashCode ()I -} - -public final class algorithms/Ed$PublicKey$Format$PEM : algorithms/Ed$PublicKey$Format { - public static final field INSTANCE Lalgorithms/Ed$PublicKey$Format$PEM; - public fun equals (Ljava/lang/Object;)Z - public fun getName ()Ljava/lang/String; - public fun hashCode ()I -} - -public final class algorithms/Ed$PublicKey$Format$RAW : algorithms/Ed$PublicKey$Format { - public static final field INSTANCE Lalgorithms/Ed$PublicKey$Format$RAW; - public fun equals (Ljava/lang/Object;)Z - public fun getName ()Ljava/lang/String; - public fun hashCode ()I -} - -public abstract interface class algorithms/EdDSA : algorithms/Ed { - public static final field Companion Lalgorithms/EdDSA$Companion; - public fun getId ()Ldev/whyoleg/cryptography/CryptographyAlgorithmId; -} - -public final class algorithms/EdDSA$Companion : dev/whyoleg/cryptography/CryptographyAlgorithmId { -} - -public abstract interface class algorithms/EdDSA$KeyPair : algorithms/Ed$KeyPair { -} - -public abstract interface class algorithms/EdDSA$PrivateKey : algorithms/Ed$PrivateKey { - public abstract fun signatureGenerator ()Ldev/whyoleg/cryptography/operations/SignatureGenerator; -} - -public abstract interface class algorithms/EdDSA$PublicKey : algorithms/Ed$PublicKey { - public abstract fun signatureVerifier ()Ldev/whyoleg/cryptography/operations/SignatureVerifier; -} - -public abstract interface class algorithms/XDH : algorithms/Ed { - public static final field Companion Lalgorithms/XDH$Companion; - public fun getId ()Ldev/whyoleg/cryptography/CryptographyAlgorithmId; -} - -public final class algorithms/XDH$Companion : dev/whyoleg/cryptography/CryptographyAlgorithmId { -} - -public abstract interface class algorithms/XDH$KeyPair : algorithms/Ed$KeyPair { -} - -public abstract interface class algorithms/XDH$PrivateKey : algorithms/Ed$PrivateKey { - public abstract fun sharedSecretGenerator ()Ldev/whyoleg/cryptography/operations/SharedSecretGenerator; -} - -public abstract interface class algorithms/XDH$PublicKey : algorithms/Ed$PublicKey { - public abstract fun sharedSecretGenerator ()Ldev/whyoleg/cryptography/operations/SharedSecretGenerator; -} - public final class dev/whyoleg/cryptography/BinarySize : java/lang/Comparable { public static final field Companion Ldev/whyoleg/cryptography/BinarySize$Companion; public static final synthetic fun box-impl (I)Ldev/whyoleg/cryptography/BinarySize; @@ -559,6 +418,102 @@ public final class dev/whyoleg/cryptography/algorithms/ECDSA$SignatureFormat : j public static fun values ()[Ldev/whyoleg/cryptography/algorithms/ECDSA$SignatureFormat; } +public abstract interface class dev/whyoleg/cryptography/algorithms/EdDSA : dev/whyoleg/cryptography/CryptographyAlgorithm { + public static final field Companion Ldev/whyoleg/cryptography/algorithms/EdDSA$Companion; + public fun getId ()Ldev/whyoleg/cryptography/CryptographyAlgorithmId; + public abstract fun keyPairGenerator (Ldev/whyoleg/cryptography/algorithms/EdDSA$Curve;)Ldev/whyoleg/cryptography/materials/key/KeyGenerator; + public abstract fun privateKeyDecoder (Ldev/whyoleg/cryptography/algorithms/EdDSA$Curve;)Ldev/whyoleg/cryptography/materials/key/KeyDecoder; + public abstract fun publicKeyDecoder (Ldev/whyoleg/cryptography/algorithms/EdDSA$Curve;)Ldev/whyoleg/cryptography/materials/key/KeyDecoder; +} + +public final class dev/whyoleg/cryptography/algorithms/EdDSA$Companion : dev/whyoleg/cryptography/CryptographyAlgorithmId { +} + +public final class dev/whyoleg/cryptography/algorithms/EdDSA$Curve : java/lang/Enum { + public static final field Ed25519 Ldev/whyoleg/cryptography/algorithms/EdDSA$Curve; + public static final field Ed448 Ldev/whyoleg/cryptography/algorithms/EdDSA$Curve; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Ldev/whyoleg/cryptography/algorithms/EdDSA$Curve; + public static fun values ()[Ldev/whyoleg/cryptography/algorithms/EdDSA$Curve; +} + +public abstract interface class dev/whyoleg/cryptography/algorithms/EdDSA$KeyPair : dev/whyoleg/cryptography/materials/key/Key { + public abstract fun getPrivateKey ()Ldev/whyoleg/cryptography/algorithms/EdDSA$PrivateKey; + public abstract fun getPublicKey ()Ldev/whyoleg/cryptography/algorithms/EdDSA$PublicKey; +} + +public abstract interface class dev/whyoleg/cryptography/algorithms/EdDSA$PrivateKey : dev/whyoleg/cryptography/materials/key/EncodableKey { + public abstract fun signatureGenerator ()Ldev/whyoleg/cryptography/operations/SignatureGenerator; +} + +public abstract class dev/whyoleg/cryptography/algorithms/EdDSA$PrivateKey$Format : dev/whyoleg/cryptography/materials/key/KeyFormat { + public final fun toString ()Ljava/lang/String; +} + +public final class dev/whyoleg/cryptography/algorithms/EdDSA$PrivateKey$Format$DER : dev/whyoleg/cryptography/algorithms/EdDSA$PrivateKey$Format { + public static final field INSTANCE Ldev/whyoleg/cryptography/algorithms/EdDSA$PrivateKey$Format$DER; + public fun equals (Ljava/lang/Object;)Z + public fun getName ()Ljava/lang/String; + public fun hashCode ()I +} + +public final class dev/whyoleg/cryptography/algorithms/EdDSA$PrivateKey$Format$JWK : dev/whyoleg/cryptography/algorithms/EdDSA$PrivateKey$Format { + public static final field INSTANCE Ldev/whyoleg/cryptography/algorithms/EdDSA$PrivateKey$Format$JWK; + public fun equals (Ljava/lang/Object;)Z + public fun getName ()Ljava/lang/String; + public fun hashCode ()I +} + +public final class dev/whyoleg/cryptography/algorithms/EdDSA$PrivateKey$Format$PEM : dev/whyoleg/cryptography/algorithms/EdDSA$PrivateKey$Format { + public static final field INSTANCE Ldev/whyoleg/cryptography/algorithms/EdDSA$PrivateKey$Format$PEM; + public fun equals (Ljava/lang/Object;)Z + public fun getName ()Ljava/lang/String; + public fun hashCode ()I +} + +public final class dev/whyoleg/cryptography/algorithms/EdDSA$PrivateKey$Format$RAW : dev/whyoleg/cryptography/algorithms/EdDSA$PrivateKey$Format { + public static final field INSTANCE Ldev/whyoleg/cryptography/algorithms/EdDSA$PrivateKey$Format$RAW; + public fun equals (Ljava/lang/Object;)Z + public fun getName ()Ljava/lang/String; + public fun hashCode ()I +} + +public abstract interface class dev/whyoleg/cryptography/algorithms/EdDSA$PublicKey : dev/whyoleg/cryptography/materials/key/EncodableKey { + public abstract fun signatureVerifier ()Ldev/whyoleg/cryptography/operations/SignatureVerifier; +} + +public abstract class dev/whyoleg/cryptography/algorithms/EdDSA$PublicKey$Format : dev/whyoleg/cryptography/materials/key/KeyFormat { + public final fun toString ()Ljava/lang/String; +} + +public final class dev/whyoleg/cryptography/algorithms/EdDSA$PublicKey$Format$DER : dev/whyoleg/cryptography/algorithms/EdDSA$PublicKey$Format { + public static final field INSTANCE Ldev/whyoleg/cryptography/algorithms/EdDSA$PublicKey$Format$DER; + public fun equals (Ljava/lang/Object;)Z + public fun getName ()Ljava/lang/String; + public fun hashCode ()I +} + +public final class dev/whyoleg/cryptography/algorithms/EdDSA$PublicKey$Format$JWK : dev/whyoleg/cryptography/algorithms/EdDSA$PublicKey$Format { + public static final field INSTANCE Ldev/whyoleg/cryptography/algorithms/EdDSA$PublicKey$Format$JWK; + public fun equals (Ljava/lang/Object;)Z + public fun getName ()Ljava/lang/String; + public fun hashCode ()I +} + +public final class dev/whyoleg/cryptography/algorithms/EdDSA$PublicKey$Format$PEM : dev/whyoleg/cryptography/algorithms/EdDSA$PublicKey$Format { + public static final field INSTANCE Ldev/whyoleg/cryptography/algorithms/EdDSA$PublicKey$Format$PEM; + public fun equals (Ljava/lang/Object;)Z + public fun getName ()Ljava/lang/String; + public fun hashCode ()I +} + +public final class dev/whyoleg/cryptography/algorithms/EdDSA$PublicKey$Format$RAW : dev/whyoleg/cryptography/algorithms/EdDSA$PublicKey$Format { + public static final field INSTANCE Ldev/whyoleg/cryptography/algorithms/EdDSA$PublicKey$Format$RAW; + public fun equals (Ljava/lang/Object;)Z + public fun getName ()Ljava/lang/String; + public fun hashCode ()I +} + public abstract interface class dev/whyoleg/cryptography/algorithms/HKDF : dev/whyoleg/cryptography/CryptographyAlgorithm { public static final field Companion Ldev/whyoleg/cryptography/algorithms/HKDF$Companion; public fun getId ()Ldev/whyoleg/cryptography/CryptographyAlgorithmId; @@ -830,6 +785,102 @@ public final class dev/whyoleg/cryptography/algorithms/SHA512 : dev/whyoleg/cryp public static final field INSTANCE Ldev/whyoleg/cryptography/algorithms/SHA512; } +public abstract interface class dev/whyoleg/cryptography/algorithms/XDH : dev/whyoleg/cryptography/CryptographyAlgorithm { + public static final field Companion Ldev/whyoleg/cryptography/algorithms/XDH$Companion; + public fun getId ()Ldev/whyoleg/cryptography/CryptographyAlgorithmId; + public abstract fun keyPairGenerator (Ldev/whyoleg/cryptography/algorithms/XDH$Curve;)Ldev/whyoleg/cryptography/materials/key/KeyGenerator; + public abstract fun privateKeyDecoder (Ldev/whyoleg/cryptography/algorithms/XDH$Curve;)Ldev/whyoleg/cryptography/materials/key/KeyDecoder; + public abstract fun publicKeyDecoder (Ldev/whyoleg/cryptography/algorithms/XDH$Curve;)Ldev/whyoleg/cryptography/materials/key/KeyDecoder; +} + +public final class dev/whyoleg/cryptography/algorithms/XDH$Companion : dev/whyoleg/cryptography/CryptographyAlgorithmId { +} + +public final class dev/whyoleg/cryptography/algorithms/XDH$Curve : java/lang/Enum { + public static final field X25519 Ldev/whyoleg/cryptography/algorithms/XDH$Curve; + public static final field X448 Ldev/whyoleg/cryptography/algorithms/XDH$Curve; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Ldev/whyoleg/cryptography/algorithms/XDH$Curve; + public static fun values ()[Ldev/whyoleg/cryptography/algorithms/XDH$Curve; +} + +public abstract interface class dev/whyoleg/cryptography/algorithms/XDH$KeyPair : dev/whyoleg/cryptography/materials/key/Key { + public abstract fun getPrivateKey ()Ldev/whyoleg/cryptography/algorithms/XDH$PrivateKey; + public abstract fun getPublicKey ()Ldev/whyoleg/cryptography/algorithms/XDH$PublicKey; +} + +public abstract interface class dev/whyoleg/cryptography/algorithms/XDH$PrivateKey : dev/whyoleg/cryptography/materials/key/EncodableKey { + public abstract fun sharedSecretGenerator ()Ldev/whyoleg/cryptography/operations/SharedSecretGenerator; +} + +public abstract class dev/whyoleg/cryptography/algorithms/XDH$PrivateKey$Format : dev/whyoleg/cryptography/materials/key/KeyFormat { + public final fun toString ()Ljava/lang/String; +} + +public final class dev/whyoleg/cryptography/algorithms/XDH$PrivateKey$Format$DER : dev/whyoleg/cryptography/algorithms/XDH$PrivateKey$Format { + public static final field INSTANCE Ldev/whyoleg/cryptography/algorithms/XDH$PrivateKey$Format$DER; + public fun equals (Ljava/lang/Object;)Z + public fun getName ()Ljava/lang/String; + public fun hashCode ()I +} + +public final class dev/whyoleg/cryptography/algorithms/XDH$PrivateKey$Format$JWK : dev/whyoleg/cryptography/algorithms/XDH$PrivateKey$Format { + public static final field INSTANCE Ldev/whyoleg/cryptography/algorithms/XDH$PrivateKey$Format$JWK; + public fun equals (Ljava/lang/Object;)Z + public fun getName ()Ljava/lang/String; + public fun hashCode ()I +} + +public final class dev/whyoleg/cryptography/algorithms/XDH$PrivateKey$Format$PEM : dev/whyoleg/cryptography/algorithms/XDH$PrivateKey$Format { + public static final field INSTANCE Ldev/whyoleg/cryptography/algorithms/XDH$PrivateKey$Format$PEM; + public fun equals (Ljava/lang/Object;)Z + public fun getName ()Ljava/lang/String; + public fun hashCode ()I +} + +public final class dev/whyoleg/cryptography/algorithms/XDH$PrivateKey$Format$RAW : dev/whyoleg/cryptography/algorithms/XDH$PrivateKey$Format { + public static final field INSTANCE Ldev/whyoleg/cryptography/algorithms/XDH$PrivateKey$Format$RAW; + public fun equals (Ljava/lang/Object;)Z + public fun getName ()Ljava/lang/String; + public fun hashCode ()I +} + +public abstract interface class dev/whyoleg/cryptography/algorithms/XDH$PublicKey : dev/whyoleg/cryptography/materials/key/EncodableKey { + public abstract fun sharedSecretGenerator ()Ldev/whyoleg/cryptography/operations/SharedSecretGenerator; +} + +public abstract class dev/whyoleg/cryptography/algorithms/XDH$PublicKey$Format : dev/whyoleg/cryptography/materials/key/KeyFormat { + public final fun toString ()Ljava/lang/String; +} + +public final class dev/whyoleg/cryptography/algorithms/XDH$PublicKey$Format$DER : dev/whyoleg/cryptography/algorithms/XDH$PublicKey$Format { + public static final field INSTANCE Ldev/whyoleg/cryptography/algorithms/XDH$PublicKey$Format$DER; + public fun equals (Ljava/lang/Object;)Z + public fun getName ()Ljava/lang/String; + public fun hashCode ()I +} + +public final class dev/whyoleg/cryptography/algorithms/XDH$PublicKey$Format$JWK : dev/whyoleg/cryptography/algorithms/XDH$PublicKey$Format { + public static final field INSTANCE Ldev/whyoleg/cryptography/algorithms/XDH$PublicKey$Format$JWK; + public fun equals (Ljava/lang/Object;)Z + public fun getName ()Ljava/lang/String; + public fun hashCode ()I +} + +public final class dev/whyoleg/cryptography/algorithms/XDH$PublicKey$Format$PEM : dev/whyoleg/cryptography/algorithms/XDH$PublicKey$Format { + public static final field INSTANCE Ldev/whyoleg/cryptography/algorithms/XDH$PublicKey$Format$PEM; + public fun equals (Ljava/lang/Object;)Z + public fun getName ()Ljava/lang/String; + public fun hashCode ()I +} + +public final class dev/whyoleg/cryptography/algorithms/XDH$PublicKey$Format$RAW : dev/whyoleg/cryptography/algorithms/XDH$PublicKey$Format { + public static final field INSTANCE Ldev/whyoleg/cryptography/algorithms/XDH$PublicKey$Format$RAW; + public fun equals (Ljava/lang/Object;)Z + public fun getName ()Ljava/lang/String; + public fun hashCode ()I +} + public final class dev/whyoleg/cryptography/algorithms/symmetric/SymmetricKeySize { public static final field Companion Ldev/whyoleg/cryptography/algorithms/symmetric/SymmetricKeySize$Companion; public static final synthetic fun box-impl (I)Ldev/whyoleg/cryptography/algorithms/symmetric/SymmetricKeySize; diff --git a/cryptography-core/api/cryptography-core.klib.api b/cryptography-core/api/cryptography-core.klib.api index fe0d67d4..93319f4e 100644 --- a/cryptography-core/api/cryptography-core.klib.api +++ b/cryptography-core/api/cryptography-core.klib.api @@ -14,117 +14,6 @@ open annotation class dev.whyoleg.cryptography/DelicateCryptographyApi : kotlin/ constructor () // dev.whyoleg.cryptography/DelicateCryptographyApi.|(){}[0] } -abstract interface <#A: algorithms/Ed.PublicKey, #B: algorithms/Ed.PrivateKey, #C: algorithms/Ed.KeyPair<#A, #B>> algorithms/Ed : dev.whyoleg.cryptography/CryptographyAlgorithm { // algorithms/Ed|null[0] - abstract fun keyPairGenerator(algorithms/Ed.Curve): dev.whyoleg.cryptography.materials.key/KeyGenerator<#C> // algorithms/Ed.keyPairGenerator|keyPairGenerator(algorithms.Ed.Curve){}[0] - abstract fun privateKeyDecoder(algorithms/Ed.Curve): dev.whyoleg.cryptography.materials.key/KeyDecoder // algorithms/Ed.privateKeyDecoder|privateKeyDecoder(algorithms.Ed.Curve){}[0] - abstract fun publicKeyDecoder(algorithms/Ed.Curve): dev.whyoleg.cryptography.materials.key/KeyDecoder // algorithms/Ed.publicKeyDecoder|publicKeyDecoder(algorithms.Ed.Curve){}[0] - - abstract interface <#A1: algorithms/Ed.PublicKey, #B1: algorithms/Ed.PrivateKey> KeyPair : dev.whyoleg.cryptography.materials.key/Key { // algorithms/Ed.KeyPair|null[0] - abstract val privateKey // algorithms/Ed.KeyPair.privateKey|{}privateKey[0] - abstract fun (): #B1 // algorithms/Ed.KeyPair.privateKey.|(){}[0] - abstract val publicKey // algorithms/Ed.KeyPair.publicKey|{}publicKey[0] - abstract fun (): #A1 // algorithms/Ed.KeyPair.publicKey.|(){}[0] - } - - abstract interface PrivateKey : dev.whyoleg.cryptography.materials.key/EncodableKey { // algorithms/Ed.PrivateKey|null[0] - sealed class Format : dev.whyoleg.cryptography.materials.key/KeyFormat { // algorithms/Ed.PrivateKey.Format|null[0] - final fun toString(): kotlin/String // algorithms/Ed.PrivateKey.Format.toString|toString(){}[0] - - final object DER : algorithms/Ed.PrivateKey.Format { // algorithms/Ed.PrivateKey.Format.DER|null[0] - final val name // algorithms/Ed.PrivateKey.Format.DER.name|{}name[0] - final fun (): kotlin/String // algorithms/Ed.PrivateKey.Format.DER.name.|(){}[0] - - final fun equals(kotlin/Any?): kotlin/Boolean // algorithms/Ed.PrivateKey.Format.DER.equals|equals(kotlin.Any?){}[0] - final fun hashCode(): kotlin/Int // algorithms/Ed.PrivateKey.Format.DER.hashCode|hashCode(){}[0] - } - - final object JWK : algorithms/Ed.PrivateKey.Format { // algorithms/Ed.PrivateKey.Format.JWK|null[0] - final val name // algorithms/Ed.PrivateKey.Format.JWK.name|{}name[0] - final fun (): kotlin/String // algorithms/Ed.PrivateKey.Format.JWK.name.|(){}[0] - - final fun equals(kotlin/Any?): kotlin/Boolean // algorithms/Ed.PrivateKey.Format.JWK.equals|equals(kotlin.Any?){}[0] - final fun hashCode(): kotlin/Int // algorithms/Ed.PrivateKey.Format.JWK.hashCode|hashCode(){}[0] - } - - final object PEM : algorithms/Ed.PrivateKey.Format { // algorithms/Ed.PrivateKey.Format.PEM|null[0] - final val name // algorithms/Ed.PrivateKey.Format.PEM.name|{}name[0] - final fun (): kotlin/String // algorithms/Ed.PrivateKey.Format.PEM.name.|(){}[0] - - final fun equals(kotlin/Any?): kotlin/Boolean // algorithms/Ed.PrivateKey.Format.PEM.equals|equals(kotlin.Any?){}[0] - final fun hashCode(): kotlin/Int // algorithms/Ed.PrivateKey.Format.PEM.hashCode|hashCode(){}[0] - } - - final object RAW : algorithms/Ed.PrivateKey.Format { // algorithms/Ed.PrivateKey.Format.RAW|null[0] - final val name // algorithms/Ed.PrivateKey.Format.RAW.name|{}name[0] - final fun (): kotlin/String // algorithms/Ed.PrivateKey.Format.RAW.name.|(){}[0] - - final fun equals(kotlin/Any?): kotlin/Boolean // algorithms/Ed.PrivateKey.Format.RAW.equals|equals(kotlin.Any?){}[0] - final fun hashCode(): kotlin/Int // algorithms/Ed.PrivateKey.Format.RAW.hashCode|hashCode(){}[0] - } - } - } - - abstract interface PublicKey : dev.whyoleg.cryptography.materials.key/EncodableKey { // algorithms/Ed.PublicKey|null[0] - sealed class Format : dev.whyoleg.cryptography.materials.key/KeyFormat { // algorithms/Ed.PublicKey.Format|null[0] - final fun toString(): kotlin/String // algorithms/Ed.PublicKey.Format.toString|toString(){}[0] - - final object DER : algorithms/Ed.PublicKey.Format { // algorithms/Ed.PublicKey.Format.DER|null[0] - final val name // algorithms/Ed.PublicKey.Format.DER.name|{}name[0] - final fun (): kotlin/String // algorithms/Ed.PublicKey.Format.DER.name.|(){}[0] - - final fun equals(kotlin/Any?): kotlin/Boolean // algorithms/Ed.PublicKey.Format.DER.equals|equals(kotlin.Any?){}[0] - final fun hashCode(): kotlin/Int // algorithms/Ed.PublicKey.Format.DER.hashCode|hashCode(){}[0] - } - - final object JWK : algorithms/Ed.PublicKey.Format { // algorithms/Ed.PublicKey.Format.JWK|null[0] - final val name // algorithms/Ed.PublicKey.Format.JWK.name|{}name[0] - final fun (): kotlin/String // algorithms/Ed.PublicKey.Format.JWK.name.|(){}[0] - - final fun equals(kotlin/Any?): kotlin/Boolean // algorithms/Ed.PublicKey.Format.JWK.equals|equals(kotlin.Any?){}[0] - final fun hashCode(): kotlin/Int // algorithms/Ed.PublicKey.Format.JWK.hashCode|hashCode(){}[0] - } - - final object PEM : algorithms/Ed.PublicKey.Format { // algorithms/Ed.PublicKey.Format.PEM|null[0] - final val name // algorithms/Ed.PublicKey.Format.PEM.name|{}name[0] - final fun (): kotlin/String // algorithms/Ed.PublicKey.Format.PEM.name.|(){}[0] - - final fun equals(kotlin/Any?): kotlin/Boolean // algorithms/Ed.PublicKey.Format.PEM.equals|equals(kotlin.Any?){}[0] - final fun hashCode(): kotlin/Int // algorithms/Ed.PublicKey.Format.PEM.hashCode|hashCode(){}[0] - } - - final object RAW : algorithms/Ed.PublicKey.Format { // algorithms/Ed.PublicKey.Format.RAW|null[0] - final val name // algorithms/Ed.PublicKey.Format.RAW.name|{}name[0] - final fun (): kotlin/String // algorithms/Ed.PublicKey.Format.RAW.name.|(){}[0] - - final fun equals(kotlin/Any?): kotlin/Boolean // algorithms/Ed.PublicKey.Format.RAW.equals|equals(kotlin.Any?){}[0] - final fun hashCode(): kotlin/Int // algorithms/Ed.PublicKey.Format.RAW.hashCode|hashCode(){}[0] - } - } - } - - final value class Curve { // algorithms/Ed.Curve|null[0] - constructor (kotlin/String) // algorithms/Ed.Curve.|(kotlin.String){}[0] - - final val name // algorithms/Ed.Curve.name|{}name[0] - final fun (): kotlin/String // algorithms/Ed.Curve.name.|(){}[0] - - final fun equals(kotlin/Any?): kotlin/Boolean // algorithms/Ed.Curve.equals|equals(kotlin.Any?){}[0] - final fun hashCode(): kotlin/Int // algorithms/Ed.Curve.hashCode|hashCode(){}[0] - final fun toString(): kotlin/String // algorithms/Ed.Curve.toString|toString(){}[0] - - final object Companion { // algorithms/Ed.Curve.Companion|null[0] - final val Ed25519 // algorithms/Ed.Curve.Companion.Ed25519|{}Ed25519[0] - final fun (): algorithms/Ed.Curve // algorithms/Ed.Curve.Companion.Ed25519.|(){}[0] - final val Ed448 // algorithms/Ed.Curve.Companion.Ed448|{}Ed448[0] - final fun (): algorithms/Ed.Curve // algorithms/Ed.Curve.Companion.Ed448.|(){}[0] - final val X25519 // algorithms/Ed.Curve.Companion.X25519|{}X25519[0] - final fun (): algorithms/Ed.Curve // algorithms/Ed.Curve.Companion.X25519.|(){}[0] - final val X448 // algorithms/Ed.Curve.Companion.X448|{}X448[0] - final fun (): algorithms/Ed.Curve // algorithms/Ed.Curve.Companion.X448.|(){}[0] - } - } -} - abstract interface <#A: dev.whyoleg.cryptography.algorithms/AES.Key> dev.whyoleg.cryptography.algorithms/AES : dev.whyoleg.cryptography/CryptographyAlgorithm { // dev.whyoleg.cryptography.algorithms/AES|null[0] abstract fun keyDecoder(): dev.whyoleg.cryptography.materials.key/KeyDecoder // dev.whyoleg.cryptography.algorithms/AES.keyDecoder|keyDecoder(){}[0] abstract fun keyGenerator(dev.whyoleg.cryptography/BinarySize = ...): dev.whyoleg.cryptography.materials.key/KeyGenerator<#A> // dev.whyoleg.cryptography.algorithms/AES.keyGenerator|keyGenerator(dev.whyoleg.cryptography.BinarySize){}[0] @@ -610,40 +499,6 @@ abstract interface <#A: dev.whyoleg.cryptography.materials.key/KeyFormat> dev.wh open suspend fun encodeToByteString(#A): kotlinx.io.bytestring/ByteString // dev.whyoleg.cryptography.materials.key/EncodableKey.encodeToByteString|encodeToByteString(1:0){}[0] } -abstract interface algorithms/EdDSA : algorithms/Ed { // algorithms/EdDSA|null[0] - open val id // algorithms/EdDSA.id|{}id[0] - open fun (): dev.whyoleg.cryptography/CryptographyAlgorithmId // algorithms/EdDSA.id.|(){}[0] - - abstract interface KeyPair : algorithms/Ed.KeyPair // algorithms/EdDSA.KeyPair|null[0] - - abstract interface PrivateKey : algorithms/Ed.PrivateKey { // algorithms/EdDSA.PrivateKey|null[0] - abstract fun signatureGenerator(): dev.whyoleg.cryptography.operations/SignatureGenerator // algorithms/EdDSA.PrivateKey.signatureGenerator|signatureGenerator(){}[0] - } - - abstract interface PublicKey : algorithms/Ed.PublicKey { // algorithms/EdDSA.PublicKey|null[0] - abstract fun signatureVerifier(): dev.whyoleg.cryptography.operations/SignatureVerifier // algorithms/EdDSA.PublicKey.signatureVerifier|signatureVerifier(){}[0] - } - - final object Companion : dev.whyoleg.cryptography/CryptographyAlgorithmId // algorithms/EdDSA.Companion|null[0] -} - -abstract interface algorithms/XDH : algorithms/Ed { // algorithms/XDH|null[0] - open val id // algorithms/XDH.id|{}id[0] - open fun (): dev.whyoleg.cryptography/CryptographyAlgorithmId // algorithms/XDH.id.|(){}[0] - - abstract interface KeyPair : algorithms/Ed.KeyPair // algorithms/XDH.KeyPair|null[0] - - abstract interface PrivateKey : algorithms/Ed.PrivateKey { // algorithms/XDH.PrivateKey|null[0] - abstract fun sharedSecretGenerator(): dev.whyoleg.cryptography.operations/SharedSecretGenerator // algorithms/XDH.PrivateKey.sharedSecretGenerator|sharedSecretGenerator(){}[0] - } - - abstract interface PublicKey : algorithms/Ed.PublicKey { // algorithms/XDH.PublicKey|null[0] - abstract fun sharedSecretGenerator(): dev.whyoleg.cryptography.operations/SharedSecretGenerator // algorithms/XDH.PublicKey.sharedSecretGenerator|sharedSecretGenerator(){}[0] - } - - final object Companion : dev.whyoleg.cryptography/CryptographyAlgorithmId // algorithms/XDH.Companion|null[0] -} - abstract interface dev.whyoleg.cryptography.algorithms/Digest : dev.whyoleg.cryptography/CryptographyAlgorithm { // dev.whyoleg.cryptography.algorithms/Digest|null[0] abstract val id // dev.whyoleg.cryptography.algorithms/Digest.id|{}id[0] abstract fun (): dev.whyoleg.cryptography/CryptographyAlgorithmId // dev.whyoleg.cryptography.algorithms/Digest.id.|(){}[0] @@ -696,6 +551,115 @@ abstract interface dev.whyoleg.cryptography.algorithms/ECDSA : dev.whyoleg.crypt final object Companion : dev.whyoleg.cryptography/CryptographyAlgorithmId // dev.whyoleg.cryptography.algorithms/ECDSA.Companion|null[0] } +abstract interface dev.whyoleg.cryptography.algorithms/EdDSA : dev.whyoleg.cryptography/CryptographyAlgorithm { // dev.whyoleg.cryptography.algorithms/EdDSA|null[0] + open val id // dev.whyoleg.cryptography.algorithms/EdDSA.id|{}id[0] + open fun (): dev.whyoleg.cryptography/CryptographyAlgorithmId // dev.whyoleg.cryptography.algorithms/EdDSA.id.|(){}[0] + + abstract fun keyPairGenerator(dev.whyoleg.cryptography.algorithms/EdDSA.Curve): dev.whyoleg.cryptography.materials.key/KeyGenerator // dev.whyoleg.cryptography.algorithms/EdDSA.keyPairGenerator|keyPairGenerator(dev.whyoleg.cryptography.algorithms.EdDSA.Curve){}[0] + abstract fun privateKeyDecoder(dev.whyoleg.cryptography.algorithms/EdDSA.Curve): dev.whyoleg.cryptography.materials.key/KeyDecoder // dev.whyoleg.cryptography.algorithms/EdDSA.privateKeyDecoder|privateKeyDecoder(dev.whyoleg.cryptography.algorithms.EdDSA.Curve){}[0] + abstract fun publicKeyDecoder(dev.whyoleg.cryptography.algorithms/EdDSA.Curve): dev.whyoleg.cryptography.materials.key/KeyDecoder // dev.whyoleg.cryptography.algorithms/EdDSA.publicKeyDecoder|publicKeyDecoder(dev.whyoleg.cryptography.algorithms.EdDSA.Curve){}[0] + + final enum class Curve : kotlin/Enum { // dev.whyoleg.cryptography.algorithms/EdDSA.Curve|null[0] + enum entry Ed25519 // dev.whyoleg.cryptography.algorithms/EdDSA.Curve.Ed25519|null[0] + enum entry Ed448 // dev.whyoleg.cryptography.algorithms/EdDSA.Curve.Ed448|null[0] + + final val entries // dev.whyoleg.cryptography.algorithms/EdDSA.Curve.entries|#static{}entries[0] + final fun (): kotlin.enums/EnumEntries // dev.whyoleg.cryptography.algorithms/EdDSA.Curve.entries.|#static(){}[0] + + final fun valueOf(kotlin/String): dev.whyoleg.cryptography.algorithms/EdDSA.Curve // dev.whyoleg.cryptography.algorithms/EdDSA.Curve.valueOf|valueOf#static(kotlin.String){}[0] + final fun values(): kotlin/Array // dev.whyoleg.cryptography.algorithms/EdDSA.Curve.values|values#static(){}[0] + } + + abstract interface KeyPair : dev.whyoleg.cryptography.materials.key/Key { // dev.whyoleg.cryptography.algorithms/EdDSA.KeyPair|null[0] + abstract val privateKey // dev.whyoleg.cryptography.algorithms/EdDSA.KeyPair.privateKey|{}privateKey[0] + abstract fun (): dev.whyoleg.cryptography.algorithms/EdDSA.PrivateKey // dev.whyoleg.cryptography.algorithms/EdDSA.KeyPair.privateKey.|(){}[0] + abstract val publicKey // dev.whyoleg.cryptography.algorithms/EdDSA.KeyPair.publicKey|{}publicKey[0] + abstract fun (): dev.whyoleg.cryptography.algorithms/EdDSA.PublicKey // dev.whyoleg.cryptography.algorithms/EdDSA.KeyPair.publicKey.|(){}[0] + } + + abstract interface PrivateKey : dev.whyoleg.cryptography.materials.key/EncodableKey { // dev.whyoleg.cryptography.algorithms/EdDSA.PrivateKey|null[0] + abstract fun signatureGenerator(): dev.whyoleg.cryptography.operations/SignatureGenerator // dev.whyoleg.cryptography.algorithms/EdDSA.PrivateKey.signatureGenerator|signatureGenerator(){}[0] + + sealed class Format : dev.whyoleg.cryptography.materials.key/KeyFormat { // dev.whyoleg.cryptography.algorithms/EdDSA.PrivateKey.Format|null[0] + final fun toString(): kotlin/String // dev.whyoleg.cryptography.algorithms/EdDSA.PrivateKey.Format.toString|toString(){}[0] + + final object DER : dev.whyoleg.cryptography.algorithms/EdDSA.PrivateKey.Format { // dev.whyoleg.cryptography.algorithms/EdDSA.PrivateKey.Format.DER|null[0] + final val name // dev.whyoleg.cryptography.algorithms/EdDSA.PrivateKey.Format.DER.name|{}name[0] + final fun (): kotlin/String // dev.whyoleg.cryptography.algorithms/EdDSA.PrivateKey.Format.DER.name.|(){}[0] + + final fun equals(kotlin/Any?): kotlin/Boolean // dev.whyoleg.cryptography.algorithms/EdDSA.PrivateKey.Format.DER.equals|equals(kotlin.Any?){}[0] + final fun hashCode(): kotlin/Int // dev.whyoleg.cryptography.algorithms/EdDSA.PrivateKey.Format.DER.hashCode|hashCode(){}[0] + } + + final object JWK : dev.whyoleg.cryptography.algorithms/EdDSA.PrivateKey.Format { // dev.whyoleg.cryptography.algorithms/EdDSA.PrivateKey.Format.JWK|null[0] + final val name // dev.whyoleg.cryptography.algorithms/EdDSA.PrivateKey.Format.JWK.name|{}name[0] + final fun (): kotlin/String // dev.whyoleg.cryptography.algorithms/EdDSA.PrivateKey.Format.JWK.name.|(){}[0] + + final fun equals(kotlin/Any?): kotlin/Boolean // dev.whyoleg.cryptography.algorithms/EdDSA.PrivateKey.Format.JWK.equals|equals(kotlin.Any?){}[0] + final fun hashCode(): kotlin/Int // dev.whyoleg.cryptography.algorithms/EdDSA.PrivateKey.Format.JWK.hashCode|hashCode(){}[0] + } + + final object PEM : dev.whyoleg.cryptography.algorithms/EdDSA.PrivateKey.Format { // dev.whyoleg.cryptography.algorithms/EdDSA.PrivateKey.Format.PEM|null[0] + final val name // dev.whyoleg.cryptography.algorithms/EdDSA.PrivateKey.Format.PEM.name|{}name[0] + final fun (): kotlin/String // dev.whyoleg.cryptography.algorithms/EdDSA.PrivateKey.Format.PEM.name.|(){}[0] + + final fun equals(kotlin/Any?): kotlin/Boolean // dev.whyoleg.cryptography.algorithms/EdDSA.PrivateKey.Format.PEM.equals|equals(kotlin.Any?){}[0] + final fun hashCode(): kotlin/Int // dev.whyoleg.cryptography.algorithms/EdDSA.PrivateKey.Format.PEM.hashCode|hashCode(){}[0] + } + + final object RAW : dev.whyoleg.cryptography.algorithms/EdDSA.PrivateKey.Format { // dev.whyoleg.cryptography.algorithms/EdDSA.PrivateKey.Format.RAW|null[0] + final val name // dev.whyoleg.cryptography.algorithms/EdDSA.PrivateKey.Format.RAW.name|{}name[0] + final fun (): kotlin/String // dev.whyoleg.cryptography.algorithms/EdDSA.PrivateKey.Format.RAW.name.|(){}[0] + + final fun equals(kotlin/Any?): kotlin/Boolean // dev.whyoleg.cryptography.algorithms/EdDSA.PrivateKey.Format.RAW.equals|equals(kotlin.Any?){}[0] + final fun hashCode(): kotlin/Int // dev.whyoleg.cryptography.algorithms/EdDSA.PrivateKey.Format.RAW.hashCode|hashCode(){}[0] + } + } + } + + abstract interface PublicKey : dev.whyoleg.cryptography.materials.key/EncodableKey { // dev.whyoleg.cryptography.algorithms/EdDSA.PublicKey|null[0] + abstract fun signatureVerifier(): dev.whyoleg.cryptography.operations/SignatureVerifier // dev.whyoleg.cryptography.algorithms/EdDSA.PublicKey.signatureVerifier|signatureVerifier(){}[0] + + sealed class Format : dev.whyoleg.cryptography.materials.key/KeyFormat { // dev.whyoleg.cryptography.algorithms/EdDSA.PublicKey.Format|null[0] + final fun toString(): kotlin/String // dev.whyoleg.cryptography.algorithms/EdDSA.PublicKey.Format.toString|toString(){}[0] + + final object DER : dev.whyoleg.cryptography.algorithms/EdDSA.PublicKey.Format { // dev.whyoleg.cryptography.algorithms/EdDSA.PublicKey.Format.DER|null[0] + final val name // dev.whyoleg.cryptography.algorithms/EdDSA.PublicKey.Format.DER.name|{}name[0] + final fun (): kotlin/String // dev.whyoleg.cryptography.algorithms/EdDSA.PublicKey.Format.DER.name.|(){}[0] + + final fun equals(kotlin/Any?): kotlin/Boolean // dev.whyoleg.cryptography.algorithms/EdDSA.PublicKey.Format.DER.equals|equals(kotlin.Any?){}[0] + final fun hashCode(): kotlin/Int // dev.whyoleg.cryptography.algorithms/EdDSA.PublicKey.Format.DER.hashCode|hashCode(){}[0] + } + + final object JWK : dev.whyoleg.cryptography.algorithms/EdDSA.PublicKey.Format { // dev.whyoleg.cryptography.algorithms/EdDSA.PublicKey.Format.JWK|null[0] + final val name // dev.whyoleg.cryptography.algorithms/EdDSA.PublicKey.Format.JWK.name|{}name[0] + final fun (): kotlin/String // dev.whyoleg.cryptography.algorithms/EdDSA.PublicKey.Format.JWK.name.|(){}[0] + + final fun equals(kotlin/Any?): kotlin/Boolean // dev.whyoleg.cryptography.algorithms/EdDSA.PublicKey.Format.JWK.equals|equals(kotlin.Any?){}[0] + final fun hashCode(): kotlin/Int // dev.whyoleg.cryptography.algorithms/EdDSA.PublicKey.Format.JWK.hashCode|hashCode(){}[0] + } + + final object PEM : dev.whyoleg.cryptography.algorithms/EdDSA.PublicKey.Format { // dev.whyoleg.cryptography.algorithms/EdDSA.PublicKey.Format.PEM|null[0] + final val name // dev.whyoleg.cryptography.algorithms/EdDSA.PublicKey.Format.PEM.name|{}name[0] + final fun (): kotlin/String // dev.whyoleg.cryptography.algorithms/EdDSA.PublicKey.Format.PEM.name.|(){}[0] + + final fun equals(kotlin/Any?): kotlin/Boolean // dev.whyoleg.cryptography.algorithms/EdDSA.PublicKey.Format.PEM.equals|equals(kotlin.Any?){}[0] + final fun hashCode(): kotlin/Int // dev.whyoleg.cryptography.algorithms/EdDSA.PublicKey.Format.PEM.hashCode|hashCode(){}[0] + } + + final object RAW : dev.whyoleg.cryptography.algorithms/EdDSA.PublicKey.Format { // dev.whyoleg.cryptography.algorithms/EdDSA.PublicKey.Format.RAW|null[0] + final val name // dev.whyoleg.cryptography.algorithms/EdDSA.PublicKey.Format.RAW.name|{}name[0] + final fun (): kotlin/String // dev.whyoleg.cryptography.algorithms/EdDSA.PublicKey.Format.RAW.name.|(){}[0] + + final fun equals(kotlin/Any?): kotlin/Boolean // dev.whyoleg.cryptography.algorithms/EdDSA.PublicKey.Format.RAW.equals|equals(kotlin.Any?){}[0] + final fun hashCode(): kotlin/Int // dev.whyoleg.cryptography.algorithms/EdDSA.PublicKey.Format.RAW.hashCode|hashCode(){}[0] + } + } + } + + final object Companion : dev.whyoleg.cryptography/CryptographyAlgorithmId // dev.whyoleg.cryptography.algorithms/EdDSA.Companion|null[0] +} + abstract interface dev.whyoleg.cryptography.algorithms/HKDF : dev.whyoleg.cryptography/CryptographyAlgorithm { // dev.whyoleg.cryptography.algorithms/HKDF|null[0] open val id // dev.whyoleg.cryptography.algorithms/HKDF.id|{}id[0] open fun (): dev.whyoleg.cryptography/CryptographyAlgorithmId // dev.whyoleg.cryptography.algorithms/HKDF.id.|(){}[0] @@ -742,6 +706,115 @@ abstract interface dev.whyoleg.cryptography.algorithms/PBKDF2 : dev.whyoleg.cryp final object Companion : dev.whyoleg.cryptography/CryptographyAlgorithmId // dev.whyoleg.cryptography.algorithms/PBKDF2.Companion|null[0] } +abstract interface dev.whyoleg.cryptography.algorithms/XDH : dev.whyoleg.cryptography/CryptographyAlgorithm { // dev.whyoleg.cryptography.algorithms/XDH|null[0] + open val id // dev.whyoleg.cryptography.algorithms/XDH.id|{}id[0] + open fun (): dev.whyoleg.cryptography/CryptographyAlgorithmId // dev.whyoleg.cryptography.algorithms/XDH.id.|(){}[0] + + abstract fun keyPairGenerator(dev.whyoleg.cryptography.algorithms/XDH.Curve): dev.whyoleg.cryptography.materials.key/KeyGenerator // dev.whyoleg.cryptography.algorithms/XDH.keyPairGenerator|keyPairGenerator(dev.whyoleg.cryptography.algorithms.XDH.Curve){}[0] + abstract fun privateKeyDecoder(dev.whyoleg.cryptography.algorithms/XDH.Curve): dev.whyoleg.cryptography.materials.key/KeyDecoder // dev.whyoleg.cryptography.algorithms/XDH.privateKeyDecoder|privateKeyDecoder(dev.whyoleg.cryptography.algorithms.XDH.Curve){}[0] + abstract fun publicKeyDecoder(dev.whyoleg.cryptography.algorithms/XDH.Curve): dev.whyoleg.cryptography.materials.key/KeyDecoder // dev.whyoleg.cryptography.algorithms/XDH.publicKeyDecoder|publicKeyDecoder(dev.whyoleg.cryptography.algorithms.XDH.Curve){}[0] + + final enum class Curve : kotlin/Enum { // dev.whyoleg.cryptography.algorithms/XDH.Curve|null[0] + enum entry X25519 // dev.whyoleg.cryptography.algorithms/XDH.Curve.X25519|null[0] + enum entry X448 // dev.whyoleg.cryptography.algorithms/XDH.Curve.X448|null[0] + + final val entries // dev.whyoleg.cryptography.algorithms/XDH.Curve.entries|#static{}entries[0] + final fun (): kotlin.enums/EnumEntries // dev.whyoleg.cryptography.algorithms/XDH.Curve.entries.|#static(){}[0] + + final fun valueOf(kotlin/String): dev.whyoleg.cryptography.algorithms/XDH.Curve // dev.whyoleg.cryptography.algorithms/XDH.Curve.valueOf|valueOf#static(kotlin.String){}[0] + final fun values(): kotlin/Array // dev.whyoleg.cryptography.algorithms/XDH.Curve.values|values#static(){}[0] + } + + abstract interface KeyPair : dev.whyoleg.cryptography.materials.key/Key { // dev.whyoleg.cryptography.algorithms/XDH.KeyPair|null[0] + abstract val privateKey // dev.whyoleg.cryptography.algorithms/XDH.KeyPair.privateKey|{}privateKey[0] + abstract fun (): dev.whyoleg.cryptography.algorithms/XDH.PrivateKey // dev.whyoleg.cryptography.algorithms/XDH.KeyPair.privateKey.|(){}[0] + abstract val publicKey // dev.whyoleg.cryptography.algorithms/XDH.KeyPair.publicKey|{}publicKey[0] + abstract fun (): dev.whyoleg.cryptography.algorithms/XDH.PublicKey // dev.whyoleg.cryptography.algorithms/XDH.KeyPair.publicKey.|(){}[0] + } + + abstract interface PrivateKey : dev.whyoleg.cryptography.materials.key/EncodableKey { // dev.whyoleg.cryptography.algorithms/XDH.PrivateKey|null[0] + abstract fun sharedSecretGenerator(): dev.whyoleg.cryptography.operations/SharedSecretGenerator // dev.whyoleg.cryptography.algorithms/XDH.PrivateKey.sharedSecretGenerator|sharedSecretGenerator(){}[0] + + sealed class Format : dev.whyoleg.cryptography.materials.key/KeyFormat { // dev.whyoleg.cryptography.algorithms/XDH.PrivateKey.Format|null[0] + final fun toString(): kotlin/String // dev.whyoleg.cryptography.algorithms/XDH.PrivateKey.Format.toString|toString(){}[0] + + final object DER : dev.whyoleg.cryptography.algorithms/XDH.PrivateKey.Format { // dev.whyoleg.cryptography.algorithms/XDH.PrivateKey.Format.DER|null[0] + final val name // dev.whyoleg.cryptography.algorithms/XDH.PrivateKey.Format.DER.name|{}name[0] + final fun (): kotlin/String // dev.whyoleg.cryptography.algorithms/XDH.PrivateKey.Format.DER.name.|(){}[0] + + final fun equals(kotlin/Any?): kotlin/Boolean // dev.whyoleg.cryptography.algorithms/XDH.PrivateKey.Format.DER.equals|equals(kotlin.Any?){}[0] + final fun hashCode(): kotlin/Int // dev.whyoleg.cryptography.algorithms/XDH.PrivateKey.Format.DER.hashCode|hashCode(){}[0] + } + + final object JWK : dev.whyoleg.cryptography.algorithms/XDH.PrivateKey.Format { // dev.whyoleg.cryptography.algorithms/XDH.PrivateKey.Format.JWK|null[0] + final val name // dev.whyoleg.cryptography.algorithms/XDH.PrivateKey.Format.JWK.name|{}name[0] + final fun (): kotlin/String // dev.whyoleg.cryptography.algorithms/XDH.PrivateKey.Format.JWK.name.|(){}[0] + + final fun equals(kotlin/Any?): kotlin/Boolean // dev.whyoleg.cryptography.algorithms/XDH.PrivateKey.Format.JWK.equals|equals(kotlin.Any?){}[0] + final fun hashCode(): kotlin/Int // dev.whyoleg.cryptography.algorithms/XDH.PrivateKey.Format.JWK.hashCode|hashCode(){}[0] + } + + final object PEM : dev.whyoleg.cryptography.algorithms/XDH.PrivateKey.Format { // dev.whyoleg.cryptography.algorithms/XDH.PrivateKey.Format.PEM|null[0] + final val name // dev.whyoleg.cryptography.algorithms/XDH.PrivateKey.Format.PEM.name|{}name[0] + final fun (): kotlin/String // dev.whyoleg.cryptography.algorithms/XDH.PrivateKey.Format.PEM.name.|(){}[0] + + final fun equals(kotlin/Any?): kotlin/Boolean // dev.whyoleg.cryptography.algorithms/XDH.PrivateKey.Format.PEM.equals|equals(kotlin.Any?){}[0] + final fun hashCode(): kotlin/Int // dev.whyoleg.cryptography.algorithms/XDH.PrivateKey.Format.PEM.hashCode|hashCode(){}[0] + } + + final object RAW : dev.whyoleg.cryptography.algorithms/XDH.PrivateKey.Format { // dev.whyoleg.cryptography.algorithms/XDH.PrivateKey.Format.RAW|null[0] + final val name // dev.whyoleg.cryptography.algorithms/XDH.PrivateKey.Format.RAW.name|{}name[0] + final fun (): kotlin/String // dev.whyoleg.cryptography.algorithms/XDH.PrivateKey.Format.RAW.name.|(){}[0] + + final fun equals(kotlin/Any?): kotlin/Boolean // dev.whyoleg.cryptography.algorithms/XDH.PrivateKey.Format.RAW.equals|equals(kotlin.Any?){}[0] + final fun hashCode(): kotlin/Int // dev.whyoleg.cryptography.algorithms/XDH.PrivateKey.Format.RAW.hashCode|hashCode(){}[0] + } + } + } + + abstract interface PublicKey : dev.whyoleg.cryptography.materials.key/EncodableKey { // dev.whyoleg.cryptography.algorithms/XDH.PublicKey|null[0] + abstract fun sharedSecretGenerator(): dev.whyoleg.cryptography.operations/SharedSecretGenerator // dev.whyoleg.cryptography.algorithms/XDH.PublicKey.sharedSecretGenerator|sharedSecretGenerator(){}[0] + + sealed class Format : dev.whyoleg.cryptography.materials.key/KeyFormat { // dev.whyoleg.cryptography.algorithms/XDH.PublicKey.Format|null[0] + final fun toString(): kotlin/String // dev.whyoleg.cryptography.algorithms/XDH.PublicKey.Format.toString|toString(){}[0] + + final object DER : dev.whyoleg.cryptography.algorithms/XDH.PublicKey.Format { // dev.whyoleg.cryptography.algorithms/XDH.PublicKey.Format.DER|null[0] + final val name // dev.whyoleg.cryptography.algorithms/XDH.PublicKey.Format.DER.name|{}name[0] + final fun (): kotlin/String // dev.whyoleg.cryptography.algorithms/XDH.PublicKey.Format.DER.name.|(){}[0] + + final fun equals(kotlin/Any?): kotlin/Boolean // dev.whyoleg.cryptography.algorithms/XDH.PublicKey.Format.DER.equals|equals(kotlin.Any?){}[0] + final fun hashCode(): kotlin/Int // dev.whyoleg.cryptography.algorithms/XDH.PublicKey.Format.DER.hashCode|hashCode(){}[0] + } + + final object JWK : dev.whyoleg.cryptography.algorithms/XDH.PublicKey.Format { // dev.whyoleg.cryptography.algorithms/XDH.PublicKey.Format.JWK|null[0] + final val name // dev.whyoleg.cryptography.algorithms/XDH.PublicKey.Format.JWK.name|{}name[0] + final fun (): kotlin/String // dev.whyoleg.cryptography.algorithms/XDH.PublicKey.Format.JWK.name.|(){}[0] + + final fun equals(kotlin/Any?): kotlin/Boolean // dev.whyoleg.cryptography.algorithms/XDH.PublicKey.Format.JWK.equals|equals(kotlin.Any?){}[0] + final fun hashCode(): kotlin/Int // dev.whyoleg.cryptography.algorithms/XDH.PublicKey.Format.JWK.hashCode|hashCode(){}[0] + } + + final object PEM : dev.whyoleg.cryptography.algorithms/XDH.PublicKey.Format { // dev.whyoleg.cryptography.algorithms/XDH.PublicKey.Format.PEM|null[0] + final val name // dev.whyoleg.cryptography.algorithms/XDH.PublicKey.Format.PEM.name|{}name[0] + final fun (): kotlin/String // dev.whyoleg.cryptography.algorithms/XDH.PublicKey.Format.PEM.name.|(){}[0] + + final fun equals(kotlin/Any?): kotlin/Boolean // dev.whyoleg.cryptography.algorithms/XDH.PublicKey.Format.PEM.equals|equals(kotlin.Any?){}[0] + final fun hashCode(): kotlin/Int // dev.whyoleg.cryptography.algorithms/XDH.PublicKey.Format.PEM.hashCode|hashCode(){}[0] + } + + final object RAW : dev.whyoleg.cryptography.algorithms/XDH.PublicKey.Format { // dev.whyoleg.cryptography.algorithms/XDH.PublicKey.Format.RAW|null[0] + final val name // dev.whyoleg.cryptography.algorithms/XDH.PublicKey.Format.RAW.name|{}name[0] + final fun (): kotlin/String // dev.whyoleg.cryptography.algorithms/XDH.PublicKey.Format.RAW.name.|(){}[0] + + final fun equals(kotlin/Any?): kotlin/Boolean // dev.whyoleg.cryptography.algorithms/XDH.PublicKey.Format.RAW.equals|equals(kotlin.Any?){}[0] + final fun hashCode(): kotlin/Int // dev.whyoleg.cryptography.algorithms/XDH.PublicKey.Format.RAW.hashCode|hashCode(){}[0] + } + } + } + + final object Companion : dev.whyoleg.cryptography/CryptographyAlgorithmId // dev.whyoleg.cryptography.algorithms/XDH.Companion|null[0] +} + abstract interface dev.whyoleg.cryptography.materials.key/Key // dev.whyoleg.cryptography.materials.key/Key|null[0] abstract interface dev.whyoleg.cryptography.materials.key/KeyFormat { // dev.whyoleg.cryptography.materials.key/KeyFormat|null[0] diff --git a/cryptography-core/src/commonMain/kotlin/algorithms/Ed.kt b/cryptography-core/src/commonMain/kotlin/algorithms/Ed.kt deleted file mode 100644 index 56e57e2e..00000000 --- a/cryptography-core/src/commonMain/kotlin/algorithms/Ed.kt +++ /dev/null @@ -1,76 +0,0 @@ -package dev.whyoleg.cryptography.algorithms - -import dev.whyoleg.cryptography.* -import dev.whyoleg.cryptography.materials.key.* -import kotlin.jvm.* - -@SubclassOptInRequired(CryptographyProviderApi::class) -public interface Ed> : CryptographyAlgorithm { - public fun publicKeyDecoder(curve: Curve): KeyDecoder - public fun privateKeyDecoder(curve: Curve): KeyDecoder - public fun keyPairGenerator(curve: Curve): KeyGenerator - - @JvmInline - public value class Curve(public val name: String) { - public companion object { - public val Ed25519: Curve get() = Curve("Ed25519") - public val X25519: Curve get() = Curve("X25519") - - public val Ed448: Curve get() = Curve("Ed448") - public val X448: Curve get() = Curve("X448") - } - } - - // Similar key interfaces but with Edwards-specific formats - @SubclassOptInRequired(CryptographyProviderApi::class) - public interface KeyPair : Key { - public val publicKey: PublicK - public val privateKey: PrivateK - } - - @SubclassOptInRequired(CryptographyProviderApi::class) - public interface PublicKey : EncodableKey { - public sealed class Format : KeyFormat { - final override fun toString(): String = name - - public data object JWK : Format() { - override val name: String get() = "JWK" - } - - public data object RAW : Format() { - override val name: String get() = "RAW" - } - - public data object DER : Format() { - override val name: String get() = "DER" - } - - public data object PEM : Format() { - override val name: String get() = "PEM" - } - } - } - - @SubclassOptInRequired(CryptographyProviderApi::class) - public interface PrivateKey : EncodableKey { - public sealed class Format : KeyFormat { - final override fun toString(): String = name - - public data object JWK : Format() { - override val name: String get() = "JWK" - } - - public data object RAW : Format() { - override val name: String get() = "RAW" - } - - public data object DER : Format() { - override val name: String get() = "DER" - } - - public data object PEM : Format() { - override val name: String get() = "PEM" - } - } - } -} diff --git a/cryptography-core/src/commonMain/kotlin/algorithms/EdDSA.kt b/cryptography-core/src/commonMain/kotlin/algorithms/EdDSA.kt index 75e441f5..c9b20fee 100644 --- a/cryptography-core/src/commonMain/kotlin/algorithms/EdDSA.kt +++ b/cryptography-core/src/commonMain/kotlin/algorithms/EdDSA.kt @@ -1,24 +1,74 @@ package dev.whyoleg.cryptography.algorithms import dev.whyoleg.cryptography.* +import dev.whyoleg.cryptography.materials.key.* import dev.whyoleg.cryptography.operations.* @SubclassOptInRequired(CryptographyProviderApi::class) -public interface EdDSA : Ed { +public interface EdDSA : CryptographyAlgorithm { override val id: CryptographyAlgorithmId get() = Companion public companion object : CryptographyAlgorithmId("EdDSA") + public enum class Curve { Ed25519, Ed448 } + + public fun publicKeyDecoder(curve: Curve): KeyDecoder + public fun privateKeyDecoder(curve: Curve): KeyDecoder + public fun keyPairGenerator(curve: Curve): KeyGenerator + @SubclassOptInRequired(CryptographyProviderApi::class) - public interface KeyPair : Ed.KeyPair + public interface KeyPair : Key { + public val publicKey: PublicKey + public val privateKey: PrivateKey + } @SubclassOptInRequired(CryptographyProviderApi::class) - public interface PublicKey : Ed.PublicKey { + public interface PublicKey : EncodableKey { public fun signatureVerifier(): SignatureVerifier + + public sealed class Format : KeyFormat { + final override fun toString(): String = name + + public data object JWK : Format() { + override val name: String get() = "JWK" + } + + public data object RAW : Format() { + override val name: String get() = "RAW" + } + + public data object DER : Format() { + override val name: String get() = "DER" + } + + public data object PEM : Format() { + override val name: String get() = "PEM" + } + } } @SubclassOptInRequired(CryptographyProviderApi::class) - public interface PrivateKey : Ed.PrivateKey { + public interface PrivateKey : EncodableKey { public fun signatureGenerator(): SignatureGenerator + + public sealed class Format : KeyFormat { + final override fun toString(): String = name + + public data object JWK : Format() { + override val name: String get() = "JWK" + } + + public data object RAW : Format() { + override val name: String get() = "RAW" + } + + public data object DER : Format() { + override val name: String get() = "DER" + } + + public data object PEM : Format() { + override val name: String get() = "PEM" + } + } } } diff --git a/cryptography-core/src/commonMain/kotlin/algorithms/XDH.kt b/cryptography-core/src/commonMain/kotlin/algorithms/XDH.kt index 616f4f85..0f7ddd34 100644 --- a/cryptography-core/src/commonMain/kotlin/algorithms/XDH.kt +++ b/cryptography-core/src/commonMain/kotlin/algorithms/XDH.kt @@ -1,25 +1,74 @@ package dev.whyoleg.cryptography.algorithms -import dev.whyoleg.cryptography.CryptographyAlgorithmId -import dev.whyoleg.cryptography.CryptographyProviderApi -import dev.whyoleg.cryptography.operations.SharedSecretGenerator +import dev.whyoleg.cryptography.* +import dev.whyoleg.cryptography.materials.key.* +import dev.whyoleg.cryptography.operations.* @SubclassOptInRequired(CryptographyProviderApi::class) -public interface XDH : Ed { +public interface XDH : CryptographyAlgorithm { override val id: CryptographyAlgorithmId get() = Companion public companion object : CryptographyAlgorithmId("XDH") + public enum class Curve { X25519, X448 } + + public fun publicKeyDecoder(curve: Curve): KeyDecoder + public fun privateKeyDecoder(curve: Curve): KeyDecoder + public fun keyPairGenerator(curve: Curve): KeyGenerator + @SubclassOptInRequired(CryptographyProviderApi::class) - public interface KeyPair : Ed.KeyPair + public interface KeyPair : Key { + public val publicKey: PublicKey + public val privateKey: PrivateKey + } @SubclassOptInRequired(CryptographyProviderApi::class) - public interface PublicKey : Ed.PublicKey { + public interface PublicKey : EncodableKey { public fun sharedSecretGenerator(): SharedSecretGenerator + + public sealed class Format : KeyFormat { + final override fun toString(): String = name + + public data object JWK : Format() { + override val name: String get() = "JWK" + } + + public data object RAW : Format() { + override val name: String get() = "RAW" + } + + public data object DER : Format() { + override val name: String get() = "DER" + } + + public data object PEM : Format() { + override val name: String get() = "PEM" + } + } } @SubclassOptInRequired(CryptographyProviderApi::class) - public interface PrivateKey : Ed.PrivateKey { + public interface PrivateKey : EncodableKey { public fun sharedSecretGenerator(): SharedSecretGenerator + + public sealed class Format : KeyFormat { + final override fun toString(): String = name + + public data object JWK : Format() { + override val name: String get() = "JWK" + } + + public data object RAW : Format() { + override val name: String get() = "RAW" + } + + public data object DER : Format() { + override val name: String get() = "DER" + } + + public data object PEM : Format() { + override val name: String get() = "PEM" + } + } } } diff --git a/cryptography-providers/cryptokit/src/commonMain/kotlin/CryptoKitCryptographyProvider.kt b/cryptography-providers/cryptokit/src/commonMain/kotlin/CryptoKitCryptographyProvider.kt index a7eeb254..e93ddba6 100644 --- a/cryptography-providers/cryptokit/src/commonMain/kotlin/CryptoKitCryptographyProvider.kt +++ b/cryptography-providers/cryptokit/src/commonMain/kotlin/CryptoKitCryptographyProvider.kt @@ -30,6 +30,8 @@ internal object CryptoKitCryptographyProvider : CryptographyProvider() { AES.GCM -> CryptoKitAesGcm ECDSA -> CryptoKitEcdsa ECDH -> CryptoKitEcdh + EdDSA -> CryptoKitEdDSA + XDH -> CryptoKitXDH else -> null } as A? } diff --git a/cryptography-providers/cryptokit/src/commonMain/kotlin/algorithms/CryptoKitEdDSA.kt b/cryptography-providers/cryptokit/src/commonMain/kotlin/algorithms/CryptoKitEdDSA.kt new file mode 100644 index 00000000..73ed9300 --- /dev/null +++ b/cryptography-providers/cryptokit/src/commonMain/kotlin/algorithms/CryptoKitEdDSA.kt @@ -0,0 +1,182 @@ +/* + * Copyright (c) 2025 Oleg Yukhnevich. Use of this source code is governed by the Apache 2.0 license. + */ + +package dev.whyoleg.cryptography.providers.cryptokit.algorithms + +import dev.whyoleg.cryptography.algorithms.* +import dev.whyoleg.cryptography.materials.key.* +import dev.whyoleg.cryptography.operations.* +import dev.whyoleg.cryptography.providers.cryptokit.internal.* +import dev.whyoleg.cryptography.providers.cryptokit.internal.swiftinterop.* +import dev.whyoleg.cryptography.providers.base.* +import dev.whyoleg.cryptography.providers.base.materials.* +import dev.whyoleg.cryptography.serialization.asn1.* +import dev.whyoleg.cryptography.serialization.asn1.modules.* +import dev.whyoleg.cryptography.serialization.pem.* +import kotlinx.cinterop.* +import platform.Foundation.* + +internal object CryptoKitEdDSA : EdDSA { + override fun publicKeyDecoder(curve: EdDSA.Curve): KeyDecoder { + check(curve == EdDSA.Curve.Ed25519) { "CryptoKit supports Ed25519 only" } + return object : KeyDecoder { + override fun decodeFromByteArrayBlocking(format: EdDSA.PublicKey.Format, bytes: ByteArray): EdDSA.PublicKey = when (format) { + EdDSA.PublicKey.Format.RAW -> EdPublic( + swiftTry { error -> bytes.useNSData { SwiftEdDsaPublicKey.decodeRawWithRawRepresentation(it, error) } } + ) + EdDSA.PublicKey.Format.DER -> { + val raw = unwrapSubjectPublicKeyInfo(ObjectIdentifier("1.3.101.112"), bytes) + EdPublic(swiftTry { error -> raw.useNSData { SwiftEdDsaPublicKey.decodeRawWithRawRepresentation(it, error) } }) + } + EdDSA.PublicKey.Format.PEM -> { + val der = unwrapPem(PemLabel.PublicKey, bytes) + val raw = unwrapSubjectPublicKeyInfo(ObjectIdentifier("1.3.101.112"), der) + EdPublic(swiftTry { error -> raw.useNSData { SwiftEdDsaPublicKey.decodeRawWithRawRepresentation(it, error) } }) + } + else -> error("$format is not supported by CryptoKit EdDSA") + } + } + } + + override fun privateKeyDecoder(curve: EdDSA.Curve): KeyDecoder { + check(curve == EdDSA.Curve.Ed25519) { "CryptoKit supports Ed25519 only" } + return object : KeyDecoder { + override fun decodeFromByteArrayBlocking(format: EdDSA.PrivateKey.Format, bytes: ByteArray): EdDSA.PrivateKey = when (format) { + EdDSA.PrivateKey.Format.RAW -> EdPrivate( + swiftTry { error -> bytes.useNSData { SwiftEdDsaPrivateKey.decodeRawWithRawRepresentation(it, error) } } + ) + EdDSA.PrivateKey.Format.DER -> { + val raw = unwrapPrivateKeyInfo(ObjectIdentifier("1.3.101.112"), bytes) + EdPrivate(swiftTry { error -> raw.useNSData { SwiftEdDsaPrivateKey.decodeRawWithRawRepresentation(it, error) } }) + } + EdDSA.PrivateKey.Format.PEM -> { + val der = unwrapPem(PemLabel.PrivateKey, bytes) + val raw = unwrapPrivateKeyInfo(ObjectIdentifier("1.3.101.112"), der) + EdPrivate(swiftTry { error -> raw.useNSData { SwiftEdDsaPrivateKey.decodeRawWithRawRepresentation(it, error) } }) + } + else -> error("$format is not supported by CryptoKit EdDSA") + } + } + } + + override fun keyPairGenerator(curve: EdDSA.Curve): KeyGenerator { + check(curve == EdDSA.Curve.Ed25519) { "CryptoKit supports Ed25519 only" } + return object : KeyGenerator { + override fun generateKeyBlocking(): EdDSA.KeyPair { + val p = SwiftEdDsaPrivateKey.generate() + return EdKeyPair(EdPublic(p.publicKey()), EdPrivate(p)) + } + } + } + + private class EdKeyPair( + override val publicKey: EdDSA.PublicKey, + override val privateKey: EdDSA.PrivateKey, + ) : EdDSA.KeyPair + + private class EdPublic( + val key: SwiftEdDsaPublicKey, + ) : EdDSA.PublicKey { + override fun encodeToByteArrayBlocking(format: EdDSA.PublicKey.Format): ByteArray = when (format) { + EdDSA.PublicKey.Format.RAW -> key.rawRepresentation().toByteArray() + EdDSA.PublicKey.Format.DER -> wrapSubjectPublicKeyInfo( + UnknownKeyAlgorithmIdentifier(ObjectIdentifier("1.3.101.112")), + key.rawRepresentation().toByteArray() + ) + EdDSA.PublicKey.Format.PEM -> wrapPem( + PemLabel.PublicKey, + wrapSubjectPublicKeyInfo( + UnknownKeyAlgorithmIdentifier(ObjectIdentifier("1.3.101.112")), + key.rawRepresentation().toByteArray() + ) + ) + else -> error("$format is not supported by CryptoKit EdDSA") + } + + override fun signatureVerifier(): SignatureVerifier = object : SignatureVerifier { + override fun createVerifyFunction(): VerifyFunction = EdVerifyFunction(key) + } + } + + private class EdPrivate( + val key: SwiftEdDsaPrivateKey, + ) : EdDSA.PrivateKey { + override fun encodeToByteArrayBlocking(format: EdDSA.PrivateKey.Format): ByteArray = when (format) { + EdDSA.PrivateKey.Format.RAW -> key.rawRepresentation().toByteArray() + EdDSA.PrivateKey.Format.DER -> wrapPrivateKeyInfo( + 0, + UnknownKeyAlgorithmIdentifier(ObjectIdentifier("1.3.101.112")), + key.rawRepresentation().toByteArray() + ) + EdDSA.PrivateKey.Format.PEM -> wrapPem( + PemLabel.PrivateKey, + wrapPrivateKeyInfo( + 0, + UnknownKeyAlgorithmIdentifier(ObjectIdentifier("1.3.101.112")), + key.rawRepresentation().toByteArray() + ) + ) + else -> error("$format is not supported by CryptoKit EdDSA") + } + + override fun signatureGenerator(): SignatureGenerator = object : SignatureGenerator { + override fun createSignFunction(): SignFunction = EdSignFunction(key) + override fun generateSignatureBlocking(data: ByteArray): ByteArray = + swiftTry { error -> data.useNSData { key.signWithMessage(it, error) } }.toByteArray() + } + } +} + +private class EdSignFunction( + private val key: SwiftEdDsaPrivateKey, +) : SignFunction { + private var closed = false + private val buffer = ArrayList(4) + override fun update(source: ByteArray, startIndex: Int, endIndex: Int) { + check(!closed) { "Already closed" } + buffer += source.copyOfRange(startIndex, endIndex) + } + override fun signIntoByteArray(destination: ByteArray, destinationOffset: Int): Int { + val sig = signToByteArray() + sig.copyInto(destination, destinationOffset) + return sig.size + } + override fun signToByteArray(): ByteArray { + check(!closed) { "Already closed" } + val data = buffer.fold(ByteArray(0)) { acc, arr -> acc + arr } + val out = swiftTry { error -> data.useNSData { key.signWithMessage(it, error) } } + reset() + return out.toByteArray() + } + override fun reset() { buffer.clear() } + override fun close() { closed = true; buffer.clear() } +} + +private class EdVerifyFunction( + private val key: SwiftEdDsaPublicKey, +) : VerifyFunction { + private var closed = false + private val buffer = ArrayList(4) + override fun update(source: ByteArray, startIndex: Int, endIndex: Int) { + check(!closed) { "Already closed" } + buffer += source.copyOfRange(startIndex, endIndex) + } + override fun tryVerify(signature: ByteArray, startIndex: Int, endIndex: Int): Boolean { + check(!closed) { "Already closed" } + val data = buffer.fold(ByteArray(0)) { acc, arr -> acc + arr } + val sig = signature.copyOfRange(startIndex, endIndex) + val ok = data.useNSData { dataNs -> sig.useNSData { sigNs -> + key.verifyWithSignature(sigNs, message = dataNs) + } } as Boolean + reset() + return ok + } + override fun verify(signature: ByteArray, startIndex: Int, endIndex: Int) { + check(tryVerify(signature, startIndex, endIndex)) { "Invalid signature" } + } + override fun reset() { buffer.clear() } + override fun close() { closed = true; buffer.clear() } +} + +// NSData helpers provided by cryptography-providers-base diff --git a/cryptography-providers/cryptokit/src/commonMain/kotlin/algorithms/CryptoKitXDH.kt b/cryptography-providers/cryptokit/src/commonMain/kotlin/algorithms/CryptoKitXDH.kt new file mode 100644 index 00000000..88dc417b --- /dev/null +++ b/cryptography-providers/cryptokit/src/commonMain/kotlin/algorithms/CryptoKitXDH.kt @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2025 Oleg Yukhnevich. Use of this source code is governed by the Apache 2.0 license. + */ + +package dev.whyoleg.cryptography.providers.cryptokit.algorithms + +import dev.whyoleg.cryptography.algorithms.* +import dev.whyoleg.cryptography.materials.key.* +import dev.whyoleg.cryptography.operations.* +import dev.whyoleg.cryptography.providers.cryptokit.internal.* +import dev.whyoleg.cryptography.providers.cryptokit.internal.swiftinterop.* +import dev.whyoleg.cryptography.providers.base.* +import dev.whyoleg.cryptography.providers.base.materials.* +import dev.whyoleg.cryptography.serialization.asn1.* +import dev.whyoleg.cryptography.serialization.asn1.modules.* +import dev.whyoleg.cryptography.serialization.pem.* +import kotlinx.cinterop.* +import platform.Foundation.* + +internal object CryptoKitXDH : XDH { + override fun publicKeyDecoder(curve: XDH.Curve): KeyDecoder { + check(curve == XDH.Curve.X25519) { "CryptoKit supports X25519 only" } + return object : KeyDecoder { + override fun decodeFromByteArrayBlocking(format: XDH.PublicKey.Format, bytes: ByteArray): XDH.PublicKey = when (format) { + XDH.PublicKey.Format.RAW -> XPublic( + swiftTry { error -> bytes.useNSData { SwiftXdhPublicKey.decodeRawWithRawRepresentation(it, error) } } + ) + XDH.PublicKey.Format.DER -> { + val raw = unwrapSubjectPublicKeyInfo(ObjectIdentifier("1.3.101.110"), bytes) + XPublic(swiftTry { error -> raw.useNSData { SwiftXdhPublicKey.decodeRawWithRawRepresentation(it, error) } }) + } + XDH.PublicKey.Format.PEM -> { + val der = unwrapPem(PemLabel.PublicKey, bytes) + val raw = unwrapSubjectPublicKeyInfo(ObjectIdentifier("1.3.101.110"), der) + XPublic(swiftTry { error -> raw.useNSData { SwiftXdhPublicKey.decodeRawWithRawRepresentation(it, error) } }) + } + else -> error("$format is not supported by CryptoKit XDH") + } + } + } + + override fun privateKeyDecoder(curve: XDH.Curve): KeyDecoder { + check(curve == XDH.Curve.X25519) { "CryptoKit supports X25519 only" } + return object : KeyDecoder { + override fun decodeFromByteArrayBlocking(format: XDH.PrivateKey.Format, bytes: ByteArray): XDH.PrivateKey = when (format) { + XDH.PrivateKey.Format.RAW -> XPrivate( + swiftTry { error -> bytes.useNSData { SwiftXdhPrivateKey.decodeRawWithRawRepresentation(it, error) } } + ) + XDH.PrivateKey.Format.DER -> { + val raw = unwrapPrivateKeyInfo(ObjectIdentifier("1.3.101.110"), bytes) + XPrivate(swiftTry { error -> raw.useNSData { SwiftXdhPrivateKey.decodeRawWithRawRepresentation(it, error) } }) + } + XDH.PrivateKey.Format.PEM -> { + val der = unwrapPem(PemLabel.PrivateKey, bytes) + val raw = unwrapPrivateKeyInfo(ObjectIdentifier("1.3.101.110"), der) + XPrivate(swiftTry { error -> raw.useNSData { SwiftXdhPrivateKey.decodeRawWithRawRepresentation(it, error) } }) + } + else -> error("$format is not supported by CryptoKit XDH") + } + } + } + + override fun keyPairGenerator(curve: XDH.Curve): KeyGenerator { + check(curve == XDH.Curve.X25519) { "CryptoKit supports X25519 only" } + return object : KeyGenerator { + override fun generateKeyBlocking(): XDH.KeyPair { + val p = SwiftXdhPrivateKey.generate() + return XKeyPair(XPublic(p.publicKey()), XPrivate(p)) + } + } + } + + private class XKeyPair( + override val publicKey: XDH.PublicKey, + override val privateKey: XDH.PrivateKey, + ) : XDH.KeyPair + + private class XPublic( + val key: SwiftXdhPublicKey, + ) : XDH.PublicKey, SharedSecretGenerator { + override fun encodeToByteArrayBlocking(format: XDH.PublicKey.Format): ByteArray = when (format) { + XDH.PublicKey.Format.RAW -> key.rawRepresentation().toByteArray() + XDH.PublicKey.Format.DER -> wrapSubjectPublicKeyInfo( + UnknownKeyAlgorithmIdentifier(ObjectIdentifier("1.3.101.110")), + key.rawRepresentation().toByteArray() + ) + XDH.PublicKey.Format.PEM -> wrapPem( + PemLabel.PublicKey, + wrapSubjectPublicKeyInfo( + UnknownKeyAlgorithmIdentifier(ObjectIdentifier("1.3.101.110")), + key.rawRepresentation().toByteArray() + ) + ) + else -> error("$format is not supported by CryptoKit XDH") + } + override fun sharedSecretGenerator(): SharedSecretGenerator = this + override fun generateSharedSecretToByteArrayBlocking(other: XDH.PrivateKey): ByteArray { + require(other is XPrivate) + return swiftTry { error -> other.key.deriveSecretWith(key, error) }.toByteArray() + } + } + + private class XPrivate( + val key: SwiftXdhPrivateKey, + ) : XDH.PrivateKey, SharedSecretGenerator { + override fun encodeToByteArrayBlocking(format: XDH.PrivateKey.Format): ByteArray = when (format) { + XDH.PrivateKey.Format.RAW -> key.rawRepresentation().toByteArray() + XDH.PrivateKey.Format.DER -> wrapPrivateKeyInfo( + 0, + UnknownKeyAlgorithmIdentifier(ObjectIdentifier("1.3.101.110")), + key.rawRepresentation().toByteArray() + ) + XDH.PrivateKey.Format.PEM -> wrapPem( + PemLabel.PrivateKey, + wrapPrivateKeyInfo( + 0, + UnknownKeyAlgorithmIdentifier(ObjectIdentifier("1.3.101.110")), + key.rawRepresentation().toByteArray() + ) + ) + else -> error("$format is not supported by CryptoKit XDH") + } + override fun sharedSecretGenerator(): SharedSecretGenerator = this + override fun generateSharedSecretToByteArrayBlocking(other: XDH.PublicKey): ByteArray { + require(other is XPublic) + return swiftTry { error -> key.deriveSecretWith(other.key, error) }.toByteArray() + } + } +} +// NSData helpers provided by cryptography-providers-base diff --git a/cryptography-providers/cryptokit/src/commonMain/swift/SwiftEd.swift b/cryptography-providers/cryptokit/src/commonMain/swift/SwiftEd.swift new file mode 100644 index 00000000..829e166b --- /dev/null +++ b/cryptography-providers/cryptokit/src/commonMain/swift/SwiftEd.swift @@ -0,0 +1,87 @@ +import CryptoKit +import Foundation + +@objc public class SwiftEdDsaPublicKey: NSObject { + private let key: Curve25519.Signing.PublicKey + + internal init(_ key: Curve25519.Signing.PublicKey) { self.key = key } + + @objc public static func decodeRaw( + rawRepresentation raw: NSData + ) throws -> SwiftEdDsaPublicKey { + return SwiftEdDsaPublicKey(try Curve25519.Signing.PublicKey(rawRepresentation: raw as Data)) + } + + @objc public func verify( + signature: NSData, + message: NSData + ) -> Bool { + return key.isValidSignature(signature as Data, for: message as Data) + } + + @objc public func rawRepresentation() -> Data { key.rawRepresentation } +} + +@objc public class SwiftEdDsaPrivateKey: NSObject { + private let key: Curve25519.Signing.PrivateKey + + private override init() { self.key = Curve25519.Signing.PrivateKey() } + private init(key: Curve25519.Signing.PrivateKey) { self.key = key } + + @objc public static func generate() -> SwiftEdDsaPrivateKey { SwiftEdDsaPrivateKey() } + + @objc public static func decodeRaw( + rawRepresentation raw: NSData + ) throws -> SwiftEdDsaPrivateKey { + return SwiftEdDsaPrivateKey(key: try Curve25519.Signing.PrivateKey(rawRepresentation: raw as Data)) + } + + @objc public func publicKey() -> SwiftEdDsaPublicKey { SwiftEdDsaPublicKey(key.publicKey) } + + @objc public func sign( + message: NSData + ) throws -> Data { + try key.signature(for: message as Data) + } + + @objc public func rawRepresentation() -> Data { key.rawRepresentation } +} + +@objc public class SwiftXdhPublicKey: NSObject { + let key: Curve25519.KeyAgreement.PublicKey + internal init(_ key: Curve25519.KeyAgreement.PublicKey) { self.key = key } + + @objc public static func decodeRaw( + rawRepresentation raw: NSData + ) throws -> SwiftXdhPublicKey { + return SwiftXdhPublicKey(try Curve25519.KeyAgreement.PublicKey(rawRepresentation: raw as Data)) + } + + @objc public func rawRepresentation() -> Data { key.rawRepresentation } +} + +@objc public class SwiftXdhPrivateKey: NSObject { + let key: Curve25519.KeyAgreement.PrivateKey + private override init() { self.key = Curve25519.KeyAgreement.PrivateKey() } + private init(_ key: Curve25519.KeyAgreement.PrivateKey) { self.key = key } + + @objc public static func generate() -> SwiftXdhPrivateKey { SwiftXdhPrivateKey() } + + @objc public static func decodeRaw( + rawRepresentation raw: NSData + ) throws -> SwiftXdhPrivateKey { + return SwiftXdhPrivateKey(try Curve25519.KeyAgreement.PrivateKey(rawRepresentation: raw as Data)) + } + + @objc public func publicKey() -> SwiftXdhPublicKey { SwiftXdhPublicKey(key.publicKey) } + + @objc public func deriveSecret( + with publicKey: SwiftXdhPublicKey + ) throws -> Data { + let ss = try key.sharedSecretFromKeyAgreement(with: publicKey.key) + return ss.withUnsafeBytes { Data($0) } + } + + @objc public func rawRepresentation() -> Data { key.rawRepresentation } +} + diff --git a/cryptography-providers/jdk/src/jvmMain/kotlin/algorithms/JdkEd.kt b/cryptography-providers/jdk/src/jvmMain/kotlin/algorithms/JdkEd.kt deleted file mode 100644 index e5fda400..00000000 --- a/cryptography-providers/jdk/src/jvmMain/kotlin/algorithms/JdkEd.kt +++ /dev/null @@ -1,108 +0,0 @@ -package dev.whyoleg.cryptography.providers.jdk.algorithms - -import dev.whyoleg.cryptography.algorithms.Ed -import dev.whyoleg.cryptography.materials.key.KeyDecoder -import dev.whyoleg.cryptography.materials.key.KeyGenerator -import dev.whyoleg.cryptography.providers.jdk.JKeyPair -import dev.whyoleg.cryptography.providers.jdk.JKeyPairGenerator -import dev.whyoleg.cryptography.providers.jdk.JPrivateKey -import dev.whyoleg.cryptography.providers.jdk.JPublicKey -import dev.whyoleg.cryptography.providers.jdk.JdkCryptographyState -import dev.whyoleg.cryptography.providers.jdk.materials.JdkEncodableKey -import dev.whyoleg.cryptography.providers.jdk.materials.JdkKeyPairGenerator -import dev.whyoleg.cryptography.providers.jdk.materials.JdkPrivateKeyDecoder -import dev.whyoleg.cryptography.providers.jdk.materials.JdkPublicKeyDecoder -import dev.whyoleg.cryptography.providers.jdk.materials.unwrapPem -import dev.whyoleg.cryptography.providers.jdk.materials.wrapPem -import dev.whyoleg.cryptography.serialization.pem.PemLabel -//import java.security.spec.XECPublicKeySpec // TODO: for raw encoding - -internal sealed class JdkEd>( - protected val state: JdkCryptographyState, -) : Ed { - protected abstract fun JPublicKey.convert(): PublicK - protected abstract fun JPrivateKey.convert(): PrivateK - protected abstract fun JKeyPair.convert(): KP - - final override fun publicKeyDecoder(curve: Ed.Curve): KeyDecoder { - return EdPublicKeyDecoder(curve.jdkName) - } - - final override fun privateKeyDecoder(curve: Ed.Curve): KeyDecoder { - return EdPrivateKeyDecoder(curve.jdkName) - } - - final override fun keyPairGenerator(curve: Ed.Curve): KeyGenerator { - return EdKeyPairGenerator(curve.jdkName) - } - - private val Ed.Curve.jdkName: String get() = name - - private inner class EdKeyPairGenerator( - private val algorithm: String, - ) : JdkKeyPairGenerator(state, algorithm) { - override fun JKeyPairGenerator.init() { - //initialize(null, state.secureRandom) // TODO - } - - override fun JKeyPair.convert(): KP = with(this@JdkEd) { convert() } - } - - private inner class EdPublicKeyDecoder( - private val algorithm: String, - ) : JdkPublicKeyDecoder(state, algorithm) { - override fun JPublicKey.convert(): PublicK = with(this@JdkEd) { convert() } - - override fun decodeFromByteArrayBlocking(format: Ed.PublicKey.Format, bytes: ByteArray): PublicK = when (format) { - Ed.PublicKey.Format.JWK -> error("$format is not supported") - /* Ed.PublicKey.Format.RAW -> keyFactory.use { - it.generatePublic(XECPublicKeySpec(NamedParameterSpec(algorithm), bytes)) - }.convert()*/ // TODO: for raw encoding - Ed.PublicKey.Format.RAW -> TODO("Todo: raw encoding") - Ed.PublicKey.Format.DER -> decodeFromDer(bytes) - Ed.PublicKey.Format.PEM -> decodeFromDer(unwrapPem(PemLabel.PublicKey, bytes)) - } - } - - private inner class EdPrivateKeyDecoder( - private val algorithm: String, - ) : JdkPrivateKeyDecoder(state, algorithm) { - override fun JPrivateKey.convert(): PrivateK = with(this@JdkEd) { convert() } - - override fun decodeFromByteArrayBlocking(format: Ed.PrivateKey.Format, bytes: ByteArray): PrivateK = when (format) { - Ed.PrivateKey.Format.JWK -> error("$format is not supported") - Ed.PrivateKey.Format.RAW -> TODO("Todo: raw encoding") - /*Ed.PrivateKey.Format.RAW -> keyFactory.use { - it.generatePrivate(XECPrivateKeySpec(NamedParameterSpec(algorithm), bytes)) - }.convert()*/ // TODO: for raw encoding - Ed.PrivateKey.Format.DER -> decodeFromDer(bytes) - Ed.PrivateKey.Format.PEM -> decodeFromDer(unwrapPem(PemLabel.PrivateKey, bytes)) - } - } - - protected abstract class BaseEdPublicKey( - private val key: JPublicKey, - ) : Ed.PublicKey, JdkEncodableKey(key) { - final override fun encodeToByteArrayBlocking(format: Ed.PublicKey.Format): ByteArray = when (format) { - Ed.PublicKey.Format.JWK -> error("$format is not supported") - Ed.PublicKey.Format.RAW -> TODO("Todo: raw encoding") -// Ed.PublicKey.Format.RAW -> (key as XECPublicKey).encoded - Ed.PublicKey.Format.DER -> encodeToDer() - Ed.PublicKey.Format.PEM -> wrapPem(PemLabel.PublicKey, encodeToDer()) - else -> TODO() - } - } - - protected abstract class BaseEdPrivateKey( - private val key: JPrivateKey, - ) : Ed.PrivateKey, JdkEncodableKey(key) { - final override fun encodeToByteArrayBlocking(format: Ed.PrivateKey.Format): ByteArray = when (format) { - Ed.PrivateKey.Format.JWK -> error("$format is not supported") - Ed.PrivateKey.Format.RAW -> TODO("Todo: raw encoding") -// Ed.PrivateKey.Format.RAW -> (key as XECPrivateKey).encoded - Ed.PrivateKey.Format.DER -> encodeToDer() - Ed.PrivateKey.Format.PEM -> wrapPem(PemLabel.PrivateKey, encodeToDer()) - else -> TODO() - } - } -} diff --git a/cryptography-providers/jdk/src/jvmMain/kotlin/algorithms/JdkEdDSA.kt b/cryptography-providers/jdk/src/jvmMain/kotlin/algorithms/JdkEdDSA.kt index 141cfcdf..5ff2473b 100644 --- a/cryptography-providers/jdk/src/jvmMain/kotlin/algorithms/JdkEdDSA.kt +++ b/cryptography-providers/jdk/src/jvmMain/kotlin/algorithms/JdkEdDSA.kt @@ -1,14 +1,54 @@ package dev.whyoleg.cryptography.providers.jdk.algorithms -import dev.whyoleg.cryptography.algorithms.EdDSA +import dev.whyoleg.cryptography.algorithms.* +import dev.whyoleg.cryptography.materials.key.* import dev.whyoleg.cryptography.operations.* import dev.whyoleg.cryptography.providers.jdk.* +import dev.whyoleg.cryptography.providers.jdk.materials.* +import dev.whyoleg.cryptography.providers.base.materials.* import dev.whyoleg.cryptography.providers.jdk.operations.* +import dev.whyoleg.cryptography.serialization.pem.* -internal class JdkEdDSA(state: JdkCryptographyState) : JdkEd(state), EdDSA { - override fun JPublicKey.convert(): EdDSA.PublicKey = EdDsaPublicKey(state, this) - override fun JPrivateKey.convert(): EdDSA.PrivateKey = EdDsaPrivateKey(state, this) - override fun JKeyPair.convert(): EdDSA.KeyPair = EdDsaKeyPair(public.convert(), private.convert()) +internal class JdkEdDSA(private val state: JdkCryptographyState) : EdDSA { + private fun curveName(curve: EdDSA.Curve): String = when (curve) { + EdDSA.Curve.Ed25519 -> "Ed25519" + EdDSA.Curve.Ed448 -> "Ed448" + } + + override fun publicKeyDecoder(curve: EdDSA.Curve): KeyDecoder = + object : JdkPublicKeyDecoder(state, curveName(curve)) { + override fun JPublicKey.convert(): EdDSA.PublicKey = EdDsaPublicKey(state, this) + + override fun decodeFromByteArrayBlocking(format: EdDSA.PublicKey.Format, bytes: ByteArray): EdDSA.PublicKey = when (format) { + EdDSA.PublicKey.Format.JWK -> error("JWK is not supported") + EdDSA.PublicKey.Format.RAW -> TODO("RAW encoding is not supported yet") + EdDSA.PublicKey.Format.DER -> decodeFromDer(bytes) + EdDSA.PublicKey.Format.PEM -> decodeFromDer(unwrapPem(PemLabel.PublicKey, bytes)) + } + } + + override fun privateKeyDecoder(curve: EdDSA.Curve): KeyDecoder = + object : JdkPrivateKeyDecoder(state, curveName(curve)) { + override fun JPrivateKey.convert(): EdDSA.PrivateKey = EdDsaPrivateKey(state, this) + + override fun decodeFromByteArrayBlocking(format: EdDSA.PrivateKey.Format, bytes: ByteArray): EdDSA.PrivateKey = when (format) { + EdDSA.PrivateKey.Format.JWK -> error("JWK is not supported") + EdDSA.PrivateKey.Format.RAW -> TODO("RAW encoding is not supported yet") + EdDSA.PrivateKey.Format.DER -> decodeFromDer(bytes) + EdDSA.PrivateKey.Format.PEM -> decodeFromDer(unwrapPem(PemLabel.PrivateKey, bytes)) + } + } + + override fun keyPairGenerator(curve: EdDSA.Curve): KeyGenerator = object : JdkKeyPairGenerator(state, curveName(curve)) { + override fun JKeyPairGenerator.init() { + // no additional init required + } + + override fun JKeyPair.convert(): EdDSA.KeyPair = EdDsaKeyPair( + EdDsaPublicKey(state, public), + EdDsaPrivateKey(state, private), + ) + } private class EdDsaKeyPair( override val publicKey: EdDSA.PublicKey, @@ -18,18 +58,32 @@ internal class JdkEdDSA(state: JdkCryptographyState) : JdkEd(key) { override fun signatureVerifier(): SignatureVerifier { return JdkSignatureVerifier(state, key, "EdDSA", null) } + + override fun encodeToByteArrayBlocking(format: EdDSA.PublicKey.Format): ByteArray = when (format) { + EdDSA.PublicKey.Format.JWK -> error("JWK is not supported") + EdDSA.PublicKey.Format.RAW -> TODO("RAW encoding is not supported yet") + EdDSA.PublicKey.Format.DER -> encodeToDer() + EdDSA.PublicKey.Format.PEM -> wrapPem(PemLabel.PublicKey, encodeToDer()) + } } private class EdDsaPrivateKey( private val state: JdkCryptographyState, private val key: JPrivateKey, - ) : EdDSA.PrivateKey, BaseEdPrivateKey(key) { + ) : EdDSA.PrivateKey, JdkEncodableKey(key) { override fun signatureGenerator(): SignatureGenerator { return JdkSignatureGenerator(state, key, "EdDSA", null) } + + override fun encodeToByteArrayBlocking(format: EdDSA.PrivateKey.Format): ByteArray = when (format) { + EdDSA.PrivateKey.Format.JWK -> error("JWK is not supported") + EdDSA.PrivateKey.Format.RAW -> TODO("RAW encoding is not supported yet") + EdDSA.PrivateKey.Format.DER -> encodeToDer() + EdDSA.PrivateKey.Format.PEM -> wrapPem(PemLabel.PrivateKey, encodeToDer()) + } } } diff --git a/cryptography-providers/jdk/src/jvmMain/kotlin/algorithms/JdkXDH.kt b/cryptography-providers/jdk/src/jvmMain/kotlin/algorithms/JdkXDH.kt index e6edccd0..63a90d01 100644 --- a/cryptography-providers/jdk/src/jvmMain/kotlin/algorithms/JdkXDH.kt +++ b/cryptography-providers/jdk/src/jvmMain/kotlin/algorithms/JdkXDH.kt @@ -1,17 +1,54 @@ package dev.whyoleg.cryptography.providers.jdk.algorithms -import dev.whyoleg.cryptography.algorithms.XDH -import dev.whyoleg.cryptography.operations.SharedSecretGenerator -import dev.whyoleg.cryptography.providers.jdk.JKeyPair -import dev.whyoleg.cryptography.providers.jdk.JPrivateKey -import dev.whyoleg.cryptography.providers.jdk.JPublicKey -import dev.whyoleg.cryptography.providers.jdk.JdkCryptographyState -import dev.whyoleg.cryptography.providers.jdk.operations.doAgreement - -internal class JdkXDH(state: JdkCryptographyState) : JdkEd(state), XDH { - override fun JPublicKey.convert(): XDH.PublicKey = XdhPublicKey(state, this) - override fun JPrivateKey.convert(): XDH.PrivateKey = XdhPrivateKey(state, this) - override fun JKeyPair.convert(): XDH.KeyPair = XdhKeyPair(public.convert(), private.convert()) +import dev.whyoleg.cryptography.algorithms.* +import dev.whyoleg.cryptography.materials.key.* +import dev.whyoleg.cryptography.operations.* +import dev.whyoleg.cryptography.providers.jdk.* +import dev.whyoleg.cryptography.providers.jdk.materials.* +import dev.whyoleg.cryptography.providers.base.materials.* +import dev.whyoleg.cryptography.providers.jdk.operations.* +import dev.whyoleg.cryptography.serialization.pem.* + +internal class JdkXDH(private val state: JdkCryptographyState) : XDH { + private fun curveName(curve: XDH.Curve): String = when (curve) { + XDH.Curve.X25519 -> "X25519" + XDH.Curve.X448 -> "X448" + } + + override fun publicKeyDecoder(curve: XDH.Curve): KeyDecoder = + object : JdkPublicKeyDecoder(state, curveName(curve)) { + override fun JPublicKey.convert(): XDH.PublicKey = XdhPublicKey(state, this) + + override fun decodeFromByteArrayBlocking(format: XDH.PublicKey.Format, bytes: ByteArray): XDH.PublicKey = when (format) { + XDH.PublicKey.Format.JWK -> error("JWK is not supported") + XDH.PublicKey.Format.RAW -> TODO("RAW encoding is not supported yet") + XDH.PublicKey.Format.DER -> decodeFromDer(bytes) + XDH.PublicKey.Format.PEM -> decodeFromDer(unwrapPem(PemLabel.PublicKey, bytes)) + } + } + + override fun privateKeyDecoder(curve: XDH.Curve): KeyDecoder = + object : JdkPrivateKeyDecoder(state, curveName(curve)) { + override fun JPrivateKey.convert(): XDH.PrivateKey = XdhPrivateKey(state, this) + + override fun decodeFromByteArrayBlocking(format: XDH.PrivateKey.Format, bytes: ByteArray): XDH.PrivateKey = when (format) { + XDH.PrivateKey.Format.JWK -> error("JWK is not supported") + XDH.PrivateKey.Format.RAW -> TODO("RAW encoding is not supported yet") + XDH.PrivateKey.Format.DER -> decodeFromDer(bytes) + XDH.PrivateKey.Format.PEM -> decodeFromDer(unwrapPem(PemLabel.PrivateKey, bytes)) + } + } + + override fun keyPairGenerator(curve: XDH.Curve): KeyGenerator = object : JdkKeyPairGenerator(state, curveName(curve)) { + override fun JKeyPairGenerator.init() { + // no additional init required + } + + override fun JKeyPair.convert(): XDH.KeyPair = XdhKeyPair( + XdhPublicKey(state, public), + XdhPrivateKey(state, private), + ) + } private class XdhKeyPair( override val publicKey: XDH.PublicKey, @@ -21,24 +58,38 @@ internal class JdkXDH(state: JdkCryptographyState) : JdkEd { + ) : XDH.PublicKey, JdkEncodableKey(key), SharedSecretGenerator { private val keyAgreement = state.keyAgreement("XDH") override fun sharedSecretGenerator(): SharedSecretGenerator = this override fun generateSharedSecretToByteArrayBlocking(other: XDH.PrivateKey): ByteArray { check(other is XdhPrivateKey) { "Only key produced by JDK provider is supported" } return keyAgreement.doAgreement(state, other.key, key) } + + override fun encodeToByteArrayBlocking(format: XDH.PublicKey.Format): ByteArray = when (format) { + XDH.PublicKey.Format.JWK -> error("JWK is not supported") + XDH.PublicKey.Format.RAW -> TODO("RAW encoding is not supported yet") + XDH.PublicKey.Format.DER -> encodeToDer() + XDH.PublicKey.Format.PEM -> wrapPem(PemLabel.PublicKey, encodeToDer()) + } } private class XdhPrivateKey( private val state: JdkCryptographyState, val key: JPrivateKey, - ) : XDH.PrivateKey, BaseEdPrivateKey(key), SharedSecretGenerator { + ) : XDH.PrivateKey, JdkEncodableKey(key), SharedSecretGenerator { private val keyAgreement = state.keyAgreement("XDH") override fun sharedSecretGenerator(): SharedSecretGenerator = this override fun generateSharedSecretToByteArrayBlocking(other: XDH.PublicKey): ByteArray { check(other is XdhPublicKey) { "Only key produced by JDK provider is supported" } return keyAgreement.doAgreement(state, key, other.key) } + + override fun encodeToByteArrayBlocking(format: XDH.PrivateKey.Format): ByteArray = when (format) { + XDH.PrivateKey.Format.JWK -> error("JWK is not supported") + XDH.PrivateKey.Format.RAW -> TODO("RAW encoding is not supported yet") + XDH.PrivateKey.Format.DER -> encodeToDer() + XDH.PrivateKey.Format.PEM -> wrapPem(PemLabel.PrivateKey, encodeToDer()) + } } } diff --git a/cryptography-providers/openssl3/api/src/commonMain/kotlin/Openssl3CryptographyProvider.kt b/cryptography-providers/openssl3/api/src/commonMain/kotlin/Openssl3CryptographyProvider.kt index 426fb6b9..192cf3b8 100644 --- a/cryptography-providers/openssl3/api/src/commonMain/kotlin/Openssl3CryptographyProvider.kt +++ b/cryptography-providers/openssl3/api/src/commonMain/kotlin/Openssl3CryptographyProvider.kt @@ -38,6 +38,8 @@ internal object Openssl3CryptographyProvider : CryptographyProvider() { AES.GCM -> Openssl3AesGcm ECDSA -> Openssl3Ecdsa ECDH -> Openssl3Ecdh + EdDSA -> Openssl3EdDSA + XDH -> Openssl3XDH RSA.PSS -> Openssl3RsaPss RSA.PKCS1 -> Openssl3RsaPkcs1 RSA.OAEP -> Openssl3RsaOaep diff --git a/cryptography-providers/openssl3/api/src/commonMain/kotlin/algorithms/Openssl3EdDSA.kt b/cryptography-providers/openssl3/api/src/commonMain/kotlin/algorithms/Openssl3EdDSA.kt new file mode 100644 index 00000000..fc13cd47 --- /dev/null +++ b/cryptography-providers/openssl3/api/src/commonMain/kotlin/algorithms/Openssl3EdDSA.kt @@ -0,0 +1,265 @@ +/* + * Copyright (c) 2025 Oleg Yukhnevich. Use of this source code is governed by the Apache 2.0 license. + */ + +package dev.whyoleg.cryptography.providers.openssl3.algorithms + +import dev.whyoleg.cryptography.algorithms.* +import dev.whyoleg.cryptography.materials.key.* +import dev.whyoleg.cryptography.operations.* +import dev.whyoleg.cryptography.providers.base.* +import dev.whyoleg.cryptography.providers.openssl3.internal.* +import dev.whyoleg.cryptography.providers.openssl3.internal.cinterop.* +import dev.whyoleg.cryptography.providers.openssl3.materials.* +import kotlinx.cinterop.* +import platform.posix.* +import kotlin.experimental.* + +internal object Openssl3EdDSA : EdDSA { + private fun algorithmName(curve: EdDSA.Curve): String = when (curve) { + EdDSA.Curve.Ed25519 -> "ED25519" + EdDSA.Curve.Ed448 -> "ED448" + } + + override fun publicKeyDecoder(curve: EdDSA.Curve): KeyDecoder = + object : Openssl3PublicKeyDecoder(algorithmName(curve)) { + override fun inputType(format: EdDSA.PublicKey.Format): String = when (format) { + EdDSA.PublicKey.Format.DER -> "DER" + EdDSA.PublicKey.Format.PEM -> "PEM" + EdDSA.PublicKey.Format.JWK, + EdDSA.PublicKey.Format.RAW -> error("$format format is not supported") + } + + override fun wrapKey(key: CPointer): EdDSA.PublicKey = EdDsaPublicKey(key) + } + + override fun privateKeyDecoder(curve: EdDSA.Curve): KeyDecoder = + object : Openssl3PrivateKeyDecoder(algorithmName(curve)) { + override fun inputType(format: EdDSA.PrivateKey.Format): String = when (format) { + EdDSA.PrivateKey.Format.DER -> "DER" + EdDSA.PrivateKey.Format.PEM -> "PEM" + EdDSA.PrivateKey.Format.JWK, + EdDSA.PrivateKey.Format.RAW -> error("$format format is not supported") + } + + override fun wrapKey(key: CPointer): EdDSA.PrivateKey = EdDsaPrivateKey(key) + } + + override fun keyPairGenerator(curve: EdDSA.Curve): KeyGenerator = + object : Openssl3KeyPairGenerator(algorithmName(curve)) { + override fun MemScope.createParams(): CValuesRef? = null + override fun wrapKeyPair(keyPair: CPointer): EdDSA.KeyPair = EdDsaKeyPair( + publicKey = EdDsaPublicKey(keyPair), + privateKey = EdDsaPrivateKey(keyPair) + ) + } + + private class EdDsaKeyPair( + override val publicKey: EdDSA.PublicKey, + override val privateKey: EdDSA.PrivateKey, + ) : EdDSA.KeyPair + + private class EdDsaPublicKey( + key: CPointer, + ) : EdDSA.PublicKey, Openssl3PublicKeyEncodable(key) { + override fun outputType(format: EdDSA.PublicKey.Format): String = when (format) { + EdDSA.PublicKey.Format.DER -> "DER" + EdDSA.PublicKey.Format.PEM -> "PEM" + EdDSA.PublicKey.Format.JWK, + EdDSA.PublicKey.Format.RAW -> error("$format format is not supported") + } + + override fun signatureVerifier(): SignatureVerifier = EdDsaSignatureVerifier(key) + } + + private class EdDsaPrivateKey( + key: CPointer, + ) : EdDSA.PrivateKey, Openssl3PrivateKeyEncodable(key) { + override fun outputType(format: EdDSA.PrivateKey.Format): String = when (format) { + EdDSA.PrivateKey.Format.DER -> "DER" + EdDSA.PrivateKey.Format.PEM -> "PEM" + EdDSA.PrivateKey.Format.JWK, + EdDSA.PrivateKey.Format.RAW -> error("$format format is not supported") + } + + override fun signatureGenerator(): SignatureGenerator = EdDsaSignatureGenerator(key) + } +} + +@OptIn(ExperimentalNativeApi::class, UnsafeNumber::class) +private class EdDsaSignatureGenerator( + private val privateKey: CPointer, +) : SignatureGenerator { + private val cleaner = privateKey.upRef().cleaner() + + override fun createSignFunction(): SignFunction = EdDsaSignFunction(privateKey) + + override fun generateSignatureBlocking(data: ByteArray): ByteArray = memScoped { + val ctx = checkError(EVP_MD_CTX_new()) + try { + checkError( + EVP_DigestSignInit_ex( + ctx = ctx, + pctx = null, + mdname = null, // must be null for EdDSA one-shot + libctx = null, + props = null, + pkey = privateKey, + params = null + ) + ) + + data.usePinned { + val siglen = alloc() + checkError(EVP_DigestSign(ctx, null, siglen.ptr, it.safeAddressOfU(0), data.size.convert())) + val out = ByteArray(siglen.value.convert()) + out.usePinned { outPin -> + checkError(EVP_DigestSign(ctx, outPin.safeAddressOfU(0), siglen.ptr, it.safeAddressOfU(0), data.size.convert())) + } + out.ensureSizeExactly(siglen.value.convert()) + } + } finally { + EVP_MD_CTX_free(ctx) + } + } +} + +@OptIn(ExperimentalNativeApi::class, UnsafeNumber::class) +private class EdDsaSignatureVerifier( + private val publicKey: CPointer, +) : SignatureVerifier { + private val cleaner = publicKey.upRef().cleaner() + + override fun createVerifyFunction(): VerifyFunction = EdDsaVerifyFunction(publicKey) +} + +@OptIn(UnsafeNumber::class) +private class EdDsaSignFunction( + private val privateKey: CPointer, +) : SignFunction { + private var isClosed = false + private var accumulator = EmptyByteArray + + private fun ensureOpen() = check(!isClosed) { "Already closed" } + + override fun update(source: ByteArray, startIndex: Int, endIndex: Int) { + ensureOpen() + checkBounds(source.size, startIndex, endIndex) + accumulator += source.copyOfRange(startIndex, endIndex) + } + + override fun signIntoByteArray(destination: ByteArray, destinationOffset: Int): Int { + val sig = signToByteArray() + sig.copyInto(destination, destinationOffset) + return sig.size + } + + override fun signToByteArray(): ByteArray { + ensureOpen() + return memScoped { + val ctx = checkError(EVP_MD_CTX_new()) + try { + checkError( + EVP_DigestSignInit_ex( + ctx = ctx, + pctx = null, + mdname = null, + libctx = null, + props = null, + pkey = privateKey, + params = null + ) + ) + accumulator.usePinned { pin -> + val siglen = alloc() + checkError(EVP_DigestSign(ctx, null, siglen.ptr, pin.safeAddressOfU(0), accumulator.size.convert())) + val out = ByteArray(siglen.value.convert()) + out.usePinned { outPin -> + checkError(EVP_DigestSign(ctx, outPin.safeAddressOfU(0), siglen.ptr, pin.safeAddressOfU(0), accumulator.size.convert())) + } + out.ensureSizeExactly(siglen.value.convert()) + } + } finally { + EVP_MD_CTX_free(ctx) + reset() + } + } + } + + override fun reset() { + ensureOpen() + accumulator = EmptyByteArray + } + + override fun close() { + isClosed = true + accumulator = EmptyByteArray + } +} + +@OptIn(UnsafeNumber::class) +private class EdDsaVerifyFunction( + private val publicKey: CPointer, +) : VerifyFunction { + private var isClosed = false + private var accumulator = EmptyByteArray + + private fun ensureOpen() = check(!isClosed) { "Already closed" } + + override fun update(source: ByteArray, startIndex: Int, endIndex: Int) { + ensureOpen() + checkBounds(source.size, startIndex, endIndex) + accumulator += source.copyOfRange(startIndex, endIndex) + } + + override fun tryVerify(signature: ByteArray, startIndex: Int, endIndex: Int): Boolean { + ensureOpen() + checkBounds(signature.size, startIndex, endIndex) + return memScoped { + val ctx = checkError(EVP_MD_CTX_new()) + try { + checkError( + EVP_DigestVerifyInit_ex( + ctx = ctx, + pctx = null, + mdname = null, + libctx = null, + props = null, + pkey = publicKey, + params = null + ) + ) + signature.usePinned { sigPin -> + accumulator.usePinned { dataPin -> + val res = EVP_DigestVerify( + ctx, + sigPin.safeAddressOfU(startIndex), + (endIndex - startIndex).convert(), + dataPin.safeAddressOfU(0), + accumulator.size.convert() + ) + if (res != 0) checkError(res) + res == 1 + } + } + } finally { + EVP_MD_CTX_free(ctx) + reset() + } + } + } + + override fun verify(signature: ByteArray, startIndex: Int, endIndex: Int) { + check(tryVerify(signature, startIndex, endIndex)) { "Invalid signature" } + } + + override fun reset() { + ensureOpen() + accumulator = EmptyByteArray + } + + override fun close() { + isClosed = true + accumulator = EmptyByteArray + } +} diff --git a/cryptography-providers/openssl3/api/src/commonMain/kotlin/algorithms/Openssl3XDH.kt b/cryptography-providers/openssl3/api/src/commonMain/kotlin/algorithms/Openssl3XDH.kt new file mode 100644 index 00000000..4eedcf90 --- /dev/null +++ b/cryptography-providers/openssl3/api/src/commonMain/kotlin/algorithms/Openssl3XDH.kt @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2025 Oleg Yukhnevich. Use of this source code is governed by the Apache 2.0 license. + */ + +package dev.whyoleg.cryptography.providers.openssl3.algorithms + +import dev.whyoleg.cryptography.algorithms.* +import dev.whyoleg.cryptography.materials.key.* +import dev.whyoleg.cryptography.operations.* +import dev.whyoleg.cryptography.providers.base.* +import dev.whyoleg.cryptography.providers.openssl3.internal.* +import dev.whyoleg.cryptography.providers.openssl3.internal.cinterop.* +import dev.whyoleg.cryptography.providers.openssl3.materials.* +import kotlinx.cinterop.* +import platform.posix.* +import kotlin.experimental.* + +internal object Openssl3XDH : XDH { + private fun algorithmName(curve: XDH.Curve): String = when (curve) { + XDH.Curve.X25519 -> "X25519" + XDH.Curve.X448 -> "X448" + } + + override fun publicKeyDecoder(curve: XDH.Curve): KeyDecoder = + object : Openssl3PublicKeyDecoder(algorithmName(curve)) { + override fun inputType(format: XDH.PublicKey.Format): String = when (format) { + XDH.PublicKey.Format.DER -> "DER" + XDH.PublicKey.Format.PEM -> "PEM" + XDH.PublicKey.Format.JWK, + XDH.PublicKey.Format.RAW -> error("$format format is not supported") + } + + override fun wrapKey(key: CPointer): XDH.PublicKey = XdhPublicKey(key) + } + + override fun privateKeyDecoder(curve: XDH.Curve): KeyDecoder = + object : Openssl3PrivateKeyDecoder(algorithmName(curve)) { + override fun inputType(format: XDH.PrivateKey.Format): String = when (format) { + XDH.PrivateKey.Format.DER -> "DER" + XDH.PrivateKey.Format.PEM -> "PEM" + XDH.PrivateKey.Format.JWK, + XDH.PrivateKey.Format.RAW -> error("$format format is not supported") + } + + override fun wrapKey(key: CPointer): XDH.PrivateKey = XdhPrivateKey(key) + } + + override fun keyPairGenerator(curve: XDH.Curve): KeyGenerator = + object : Openssl3KeyPairGenerator(algorithmName(curve)) { + override fun MemScope.createParams(): CValuesRef? = null + override fun wrapKeyPair(keyPair: CPointer): XDH.KeyPair = XdhKeyPair( + publicKey = XdhPublicKey(keyPair), + privateKey = XdhPrivateKey(keyPair) + ) + } + + private class XdhKeyPair( + override val publicKey: XDH.PublicKey, + override val privateKey: XDH.PrivateKey, + ) : XDH.KeyPair + + private class XdhPublicKey( + key: CPointer, + ) : XDH.PublicKey, Openssl3PublicKeyEncodable(key), SharedSecretGenerator { + override fun outputType(format: XDH.PublicKey.Format): String = when (format) { + XDH.PublicKey.Format.DER -> "DER" + XDH.PublicKey.Format.PEM -> "PEM" + XDH.PublicKey.Format.JWK, + XDH.PublicKey.Format.RAW -> error("$format format is not supported") + } + + override fun sharedSecretGenerator(): SharedSecretGenerator = this + override fun generateSharedSecretToByteArrayBlocking(other: XDH.PrivateKey): ByteArray { + check(other is XdhPrivateKey) + return deriveSharedSecret(publicKey = key, privateKey = other.key) + } + } + + private class XdhPrivateKey( + key: CPointer, + ) : XDH.PrivateKey, Openssl3PrivateKeyEncodable(key), SharedSecretGenerator { + override fun outputType(format: XDH.PrivateKey.Format): String = when (format) { + XDH.PrivateKey.Format.DER -> "DER" + XDH.PrivateKey.Format.PEM -> "PEM" + XDH.PrivateKey.Format.JWK, + XDH.PrivateKey.Format.RAW -> error("$format format is not supported") + } + + override fun sharedSecretGenerator(): SharedSecretGenerator = this + override fun generateSharedSecretToByteArrayBlocking(other: XDH.PublicKey): ByteArray { + check(other is XdhPublicKey) + return deriveSharedSecret(publicKey = other.key, privateKey = key) + } + } +} + +@OptIn(UnsafeNumber::class) +private fun deriveSharedSecret( + publicKey: CPointer, + privateKey: CPointer, +): ByteArray = memScoped { + val context = checkError(EVP_PKEY_CTX_new_from_pkey(null, privateKey, null)) + try { + checkError(EVP_PKEY_derive_init(context)) + checkError(EVP_PKEY_derive_set_peer(context, publicKey)) + val secretSize = alloc() + checkError(EVP_PKEY_derive(context, null, secretSize.ptr)) + val secret = ByteArray(secretSize.value.toInt()) + checkError(EVP_PKEY_derive(context, secret.refToU(0), secretSize.ptr)) + secret + } finally { + EVP_PKEY_CTX_free(context) + } +} diff --git a/cryptography-providers/tests/src/commonMain/kotlin/default/EdDsaTest.kt b/cryptography-providers/tests/src/commonMain/kotlin/default/EdDsaTest.kt new file mode 100644 index 00000000..5c60d100 --- /dev/null +++ b/cryptography-providers/tests/src/commonMain/kotlin/default/EdDsaTest.kt @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2025 Oleg Yukhnevich. Use of this source code is governed by the Apache 2.0 license. + */ + +package dev.whyoleg.cryptography.providers.tests.default + +import dev.whyoleg.cryptography.* +import dev.whyoleg.cryptography.algorithms.* +import dev.whyoleg.cryptography.providers.tests.* +import dev.whyoleg.cryptography.random.* +import kotlinx.io.bytestring.* +import kotlin.test.* + +abstract class EdDsaTest(provider: CryptographyProvider) : AlgorithmTest(EdDSA, provider), SignatureTest { + + @Test + fun testSignVerify() = testWithAlgorithm { + listOf(EdDSA.Curve.Ed25519, EdDSA.Curve.Ed448).forEach { curve -> + val keyPair = algorithm.keyPairGenerator(curve).generateKey() + + val dataSets = listOf( + ByteArray(0), + CryptographyRandom.nextBytes(32), + CryptographyRandom.nextBytes(1024) + ) + val signer = keyPair.privateKey.signatureGenerator() + val verifier = keyPair.publicKey.signatureVerifier() + dataSets.forEach { data -> + val signature = signer.generateSignature(data) + assertTrue(verifier.tryVerifySignature(data, signature)) + } + } + } + + @Test + fun testFunctions() = testWithAlgorithm { + if (!supportsFunctions()) return@testWithAlgorithm + + listOf(EdDSA.Curve.Ed25519, EdDSA.Curve.Ed448).forEach { curve -> + val keyPair = algorithm.keyPairGenerator(curve).generateKey() + repeat(5) { + val size = CryptographyRandom.nextInt(256, 4096) + val data = ByteString(CryptographyRandom.nextBytes(size)) + assertSignaturesViaFunction(keyPair.privateKey.signatureGenerator(), keyPair.publicKey.signatureVerifier(), data) + } + } + } +} + diff --git a/cryptography-providers/tests/src/commonMain/kotlin/default/SupportedAlgorithmsTest.kt b/cryptography-providers/tests/src/commonMain/kotlin/default/SupportedAlgorithmsTest.kt index 2922e417..edf9b64d 100644 --- a/cryptography-providers/tests/src/commonMain/kotlin/default/SupportedAlgorithmsTest.kt +++ b/cryptography-providers/tests/src/commonMain/kotlin/default/SupportedAlgorithmsTest.kt @@ -51,6 +51,11 @@ abstract class SupportedAlgorithmsTest(provider: CryptographyProvider) : Provide assertSupports(ECDSA) assertSupports(ECDH, !context.provider.isApple) + // Edwards-family + // WebCrypto availability depends on engine; skip expecting it there + assertSupports(EdDSA, !context.provider.isApple && !context.provider.isWebCrypto) + assertSupports(XDH, !context.provider.isApple && !context.provider.isWebCrypto) + assertSupports(RSA.PSS, !context.provider.isCryptoKit) assertSupports(RSA.OAEP, !context.provider.isCryptoKit) assertSupports(RSA.PKCS1, !context.provider.isCryptoKit) diff --git a/cryptography-providers/tests/src/commonMain/kotlin/default/XdhTest.kt b/cryptography-providers/tests/src/commonMain/kotlin/default/XdhTest.kt new file mode 100644 index 00000000..90c59ad1 --- /dev/null +++ b/cryptography-providers/tests/src/commonMain/kotlin/default/XdhTest.kt @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2025 Oleg Yukhnevich. Use of this source code is governed by the Apache 2.0 license. + */ + +package dev.whyoleg.cryptography.providers.tests.default + +import dev.whyoleg.cryptography.* +import dev.whyoleg.cryptography.algorithms.* +import dev.whyoleg.cryptography.providers.tests.* +import kotlin.test.* + +abstract class XdhTest(provider: CryptographyProvider) : AlgorithmTest(XDH, provider) { + + @Test + fun testDeriveSharedSecret() = testWithAlgorithm { + listOf(XDH.Curve.X25519, XDH.Curve.X448).forEach { curve -> + val a = algorithm.keyPairGenerator(curve).generateKey() + val b = algorithm.keyPairGenerator(curve).generateKey() + + val aSecret = a.privateKey.sharedSecretGenerator().generateSharedSecretToByteArray(b.publicKey) + val bSecret = b.privateKey.sharedSecretGenerator().generateSharedSecretToByteArray(a.publicKey) + assertContentEquals(aSecret, bSecret) + val expectedSize = when (curve) { + XDH.Curve.X25519 -> 32 + XDH.Curve.X448 -> 56 + } + assertEquals(expectedSize, aSecret.size) + } + } +} + diff --git a/cryptography-providers/tests/src/commonMain/kotlin/support.kt b/cryptography-providers/tests/src/commonMain/kotlin/support.kt index 90850e9f..e5f54ac7 100644 --- a/cryptography-providers/tests/src/commonMain/kotlin/support.kt +++ b/cryptography-providers/tests/src/commonMain/kotlin/support.kt @@ -146,6 +146,11 @@ fun ProviderTestScope.supports(algorithmId: CryptographyAlgorithmId<*>): Boolean when (algorithmId) { AES.CMAC if provider.isJdkDefault -> "Default JDK provider doesn't support AES-CMAC, only supported with BouncyCastle" RSA.PSS if provider.isJdkDefault && platform.isAndroid -> "JDK provider on Android doesn't support RSASSA-PSS" + EdDSA if provider.isWebCrypto -> "WebCrypto EdDSA not yet supported in this engine" + XDH if provider.isWebCrypto -> "WebCrypto X25519/X448 not yet supported in this engine" + // Some JDKs used in CI (jvm / jvm11) lack these algorithms + EdDSA if provider.isJdkDefault -> "Default JDK may not support EdDSA on this JDK version" + XDH if provider.isJdkDefault && platform.isJdk { major < 11 } -> "Default JDK doesn't support XDH before JDK 11" else -> null } } diff --git a/cryptography-providers/webcrypto/src/commonMain/kotlin/WebCryptoCryptographyProvider.kt b/cryptography-providers/webcrypto/src/commonMain/kotlin/WebCryptoCryptographyProvider.kt index d92b5908..f927a450 100644 --- a/cryptography-providers/webcrypto/src/commonMain/kotlin/WebCryptoCryptographyProvider.kt +++ b/cryptography-providers/webcrypto/src/commonMain/kotlin/WebCryptoCryptographyProvider.kt @@ -7,6 +7,8 @@ package dev.whyoleg.cryptography.providers.webcrypto import dev.whyoleg.cryptography.* import dev.whyoleg.cryptography.algorithms.* import dev.whyoleg.cryptography.providers.webcrypto.algorithms.* +import dev.whyoleg.cryptography.providers.webcrypto.internal.Engine +import dev.whyoleg.cryptography.providers.webcrypto.internal.detectEngine internal val defaultProvider = lazy { WebCryptoCryptographyProvider } @@ -15,6 +17,18 @@ public val CryptographyProvider.Companion.WebCrypto: CryptographyProvider by def internal object WebCryptoCryptographyProvider : CryptographyProvider() { override val name: String get() = "WebCrypto" + private val engine: Engine = detectEngine() + private var experimentalEdwardsEnabled: Boolean = false + + internal fun setExperimentalEdwards(value: Boolean) { + experimentalEdwardsEnabled = value + } + + private fun supportsEdwards(): Boolean = when (engine) { + Engine.Node, Engine.Firefox, Engine.Safari -> true + Engine.Chromium, Engine.Unknown -> experimentalEdwardsEnabled + } + @Suppress("UNCHECKED_CAST") override fun getOrNull(identifier: CryptographyAlgorithmId): A? = when (identifier) { SHA1 -> WebCryptoDigest.sha1 @@ -29,9 +43,19 @@ internal object WebCryptoCryptographyProvider : CryptographyProvider() { RSA.PSS -> WebCryptoRsaPss RSA.PKCS1 -> WebCryptoRsaPkcs1 ECDSA -> WebCryptoEcdsa + EdDSA -> if (supportsEdwards()) WebCryptoEdDSA else null ECDH -> WebCryptoEcdh + XDH -> if (supportsEdwards()) WebCryptoXDH else null PBKDF2 -> WebCryptoPbkdf2 HKDF -> WebCryptoHkdf else -> null } as A? } + +@Suppress("FunctionName") +public fun CryptographyProvider.Companion.WebCrypto( + enableExperimentalEdwards: Boolean, +): CryptographyProvider { + WebCryptoCryptographyProvider.setExperimentalEdwards(enableExperimentalEdwards) + return WebCryptoCryptographyProvider +} diff --git a/cryptography-providers/webcrypto/src/commonMain/kotlin/algorithms/WebCryptoEdDSA.kt b/cryptography-providers/webcrypto/src/commonMain/kotlin/algorithms/WebCryptoEdDSA.kt new file mode 100644 index 00000000..f6f98252 --- /dev/null +++ b/cryptography-providers/webcrypto/src/commonMain/kotlin/algorithms/WebCryptoEdDSA.kt @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2025 Oleg Yukhnevich. Use of this source code is governed by the Apache 2.0 license. + */ + +package dev.whyoleg.cryptography.providers.webcrypto.algorithms + +import dev.whyoleg.cryptography.algorithms.* +import dev.whyoleg.cryptography.materials.key.* +import dev.whyoleg.cryptography.operations.* +import dev.whyoleg.cryptography.providers.webcrypto.internal.* +import dev.whyoleg.cryptography.providers.webcrypto.materials.* +import dev.whyoleg.cryptography.providers.webcrypto.operations.* +import dev.whyoleg.cryptography.providers.base.materials.* +import dev.whyoleg.cryptography.serialization.pem.* + +internal object WebCryptoEdDSA : EdDSA { + private fun curveName(curve: EdDSA.Curve): String = when (curve) { + EdDSA.Curve.Ed25519 -> "Ed25519" + EdDSA.Curve.Ed448 -> "Ed448" + } + + override fun publicKeyDecoder(curve: EdDSA.Curve): KeyDecoder = WebCryptoKeyDecoder( + algorithm = Algorithm(curveName(curve)), + keyProcessor = EdPublicKeyProcessor, + keyWrapper = WebCryptoKeyWrapper(arrayOf("verify")) { EdDsaPublicKey(it) }, + ) + + override fun privateKeyDecoder(curve: EdDSA.Curve): KeyDecoder = WebCryptoKeyDecoder( + algorithm = Algorithm(curveName(curve)), + keyProcessor = EdPrivateKeyProcessor, + keyWrapper = WebCryptoKeyWrapper(arrayOf("sign")) { EdDsaPrivateKey(it) }, + ) + + override fun keyPairGenerator(curve: EdDSA.Curve): KeyGenerator = WebCryptoAsymmetricKeyGenerator( + algorithm = Algorithm(curveName(curve)), + keyUsages = arrayOf("verify", "sign"), + keyPairWrapper = { EdDsaKeyPair(EdDsaPublicKey(it.publicKey), EdDsaPrivateKey(it.privateKey)) }, + ) + + private class EdDsaKeyPair( + override val publicKey: EdDSA.PublicKey, + override val privateKey: EdDSA.PrivateKey, + ) : EdDSA.KeyPair + + private class EdDsaPublicKey( + val publicKey: CryptoKey, + ) : WebCryptoEncodableKey(publicKey, EdPublicKeyProcessor), EdDSA.PublicKey { + override fun signatureVerifier(): SignatureVerifier { + return WebCryptoSignatureVerifier(Algorithm(publicKey.algorithm.algorithmName), publicKey) + } + } + + private class EdDsaPrivateKey( + val privateKey: CryptoKey, + ) : WebCryptoEncodableKey(privateKey, EdPrivateKeyProcessor), EdDSA.PrivateKey { + override fun signatureGenerator(): SignatureGenerator { + return WebCryptoSignatureGenerator(Algorithm(privateKey.algorithm.algorithmName), privateKey) + } + } +} + +private object EdPublicKeyProcessor : WebCryptoKeyProcessor() { + override fun stringFormat(format: EdDSA.PublicKey.Format): String = when (format) { + EdDSA.PublicKey.Format.JWK -> "jwk" + EdDSA.PublicKey.Format.RAW -> "raw" + EdDSA.PublicKey.Format.DER, + EdDSA.PublicKey.Format.PEM -> "spki" + } + + override fun beforeDecoding(algorithm: Algorithm, format: EdDSA.PublicKey.Format, key: ByteArray): ByteArray = when (format) { + EdDSA.PublicKey.Format.JWK -> key + EdDSA.PublicKey.Format.RAW -> key + EdDSA.PublicKey.Format.DER -> key + EdDSA.PublicKey.Format.PEM -> unwrapPem(PemLabel.PublicKey, key) + } + + override fun afterEncoding(format: EdDSA.PublicKey.Format, key: ByteArray): ByteArray = when (format) { + EdDSA.PublicKey.Format.JWK -> key + EdDSA.PublicKey.Format.RAW -> key + EdDSA.PublicKey.Format.DER -> key + EdDSA.PublicKey.Format.PEM -> wrapPem(PemLabel.PublicKey, key) + } +} + +private object EdPrivateKeyProcessor : WebCryptoKeyProcessor() { + override fun stringFormat(format: EdDSA.PrivateKey.Format): String = when (format) { + EdDSA.PrivateKey.Format.JWK, + EdDSA.PrivateKey.Format.RAW, + EdDSA.PrivateKey.Format.DER, + EdDSA.PrivateKey.Format.PEM, + -> "pkcs8" + } + + override fun beforeDecoding(algorithm: Algorithm, format: EdDSA.PrivateKey.Format, key: ByteArray): ByteArray = when (format) { + EdDSA.PrivateKey.Format.JWK -> key + EdDSA.PrivateKey.Format.RAW -> key // treat as already PKCS8 if user passes raw bytes; no wrap + EdDSA.PrivateKey.Format.DER -> key + EdDSA.PrivateKey.Format.PEM -> unwrapPem(PemLabel.PrivateKey, key) + } + + override fun afterEncoding(format: EdDSA.PrivateKey.Format, key: ByteArray): ByteArray = when (format) { + EdDSA.PrivateKey.Format.JWK -> key + EdDSA.PrivateKey.Format.RAW -> key + EdDSA.PrivateKey.Format.DER -> key + EdDSA.PrivateKey.Format.PEM -> wrapPem(PemLabel.PrivateKey, key) + } +} diff --git a/cryptography-providers/webcrypto/src/commonMain/kotlin/algorithms/WebCryptoXDH.kt b/cryptography-providers/webcrypto/src/commonMain/kotlin/algorithms/WebCryptoXDH.kt new file mode 100644 index 00000000..6201aed3 --- /dev/null +++ b/cryptography-providers/webcrypto/src/commonMain/kotlin/algorithms/WebCryptoXDH.kt @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2025 Oleg Yukhnevich. Use of this source code is governed by the Apache 2.0 license. + */ + +package dev.whyoleg.cryptography.providers.webcrypto.algorithms + +import dev.whyoleg.cryptography.algorithms.* +import dev.whyoleg.cryptography.materials.key.* +import dev.whyoleg.cryptography.operations.* +import dev.whyoleg.cryptography.providers.webcrypto.internal.* +import dev.whyoleg.cryptography.providers.webcrypto.materials.* +import dev.whyoleg.cryptography.providers.base.materials.* +import dev.whyoleg.cryptography.serialization.pem.* + +internal object WebCryptoXDH : XDH { + private fun curveName(curve: XDH.Curve): String = when (curve) { + XDH.Curve.X25519 -> "X25519" + XDH.Curve.X448 -> "X448" + } + + override fun publicKeyDecoder(curve: XDH.Curve): KeyDecoder = WebCryptoKeyDecoder( + algorithm = Algorithm(curveName(curve)), + keyProcessor = XdhPublicKeyProcessor, + keyWrapper = WebCryptoKeyWrapper(arrayOf()) { XdhPublicKey(it) }, + ) + + override fun privateKeyDecoder(curve: XDH.Curve): KeyDecoder = WebCryptoKeyDecoder( + algorithm = Algorithm(curveName(curve)), + keyProcessor = XdhPrivateKeyProcessor, + keyWrapper = WebCryptoKeyWrapper(arrayOf("deriveBits")) { XdhPrivateKey(it) }, + ) + + override fun keyPairGenerator(curve: XDH.Curve): KeyGenerator = WebCryptoAsymmetricKeyGenerator( + algorithm = Algorithm(curveName(curve)), + keyUsages = arrayOf("deriveBits"), + keyPairWrapper = { XdhKeyPair(XdhPublicKey(it.publicKey), XdhPrivateKey(it.privateKey)) }, + ) + + private class XdhKeyPair( + override val publicKey: XDH.PublicKey, + override val privateKey: XDH.PrivateKey, + ) : XDH.KeyPair + + private class XdhPublicKey( + val publicKey: CryptoKey, + ) : WebCryptoEncodableKey(publicKey, XdhPublicKeyProcessor), XDH.PublicKey, SharedSecretGenerator { + override fun sharedSecretGenerator(): SharedSecretGenerator = this + + private fun deriveLengthBits(): Int = when (publicKey.algorithm.algorithmName) { + "X25519" -> 32 * 8 + "X448" -> 56 * 8 + else -> error("Unknown XDH algorithm: ${publicKey.algorithm.algorithmName}") + } + + override suspend fun generateSharedSecretToByteArray(other: XDH.PrivateKey): ByteArray { + check(other is XdhPrivateKey) + return WebCrypto.deriveBits( + algorithm = KeyDeriveAlgorithm(publicKey.algorithm.algorithmName, publicKey), + baseKey = other.privateKey, + length = deriveLengthBits() + ) + } + + override fun generateSharedSecretToByteArrayBlocking(other: XDH.PrivateKey): ByteArray = nonBlocking() + } + + private class XdhPrivateKey( + val privateKey: CryptoKey, + ) : WebCryptoEncodableKey(privateKey, XdhPrivateKeyProcessor), XDH.PrivateKey, SharedSecretGenerator { + override fun sharedSecretGenerator(): SharedSecretGenerator = this + + private fun deriveLengthBits(): Int = when (privateKey.algorithm.algorithmName) { + "X25519" -> 32 * 8 + "X448" -> 56 * 8 + else -> error("Unknown XDH algorithm: ${privateKey.algorithm.algorithmName}") + } + + override suspend fun generateSharedSecretToByteArray(other: XDH.PublicKey): ByteArray { + check(other is XdhPublicKey) + return WebCrypto.deriveBits( + algorithm = KeyDeriveAlgorithm(privateKey.algorithm.algorithmName, other.publicKey), + baseKey = privateKey, + length = deriveLengthBits() + ) + } + + override fun generateSharedSecretToByteArrayBlocking(other: XDH.PublicKey): ByteArray = nonBlocking() + } +} + +private object XdhPublicKeyProcessor : WebCryptoKeyProcessor() { + override fun stringFormat(format: XDH.PublicKey.Format): String = when (format) { + XDH.PublicKey.Format.JWK -> "jwk" + XDH.PublicKey.Format.RAW -> "raw" + XDH.PublicKey.Format.DER, + XDH.PublicKey.Format.PEM -> "spki" + } + + override fun beforeDecoding(algorithm: Algorithm, format: XDH.PublicKey.Format, key: ByteArray): ByteArray = when (format) { + XDH.PublicKey.Format.JWK -> key + XDH.PublicKey.Format.RAW -> key + XDH.PublicKey.Format.DER -> key + XDH.PublicKey.Format.PEM -> unwrapPem(PemLabel.PublicKey, key) + } + + override fun afterEncoding(format: XDH.PublicKey.Format, key: ByteArray): ByteArray = when (format) { + XDH.PublicKey.Format.JWK -> key + XDH.PublicKey.Format.RAW -> key + XDH.PublicKey.Format.DER -> key + XDH.PublicKey.Format.PEM -> wrapPem(PemLabel.PublicKey, key) + } +} + +private object XdhPrivateKeyProcessor : WebCryptoKeyProcessor() { + override fun stringFormat(format: XDH.PrivateKey.Format): String = when (format) { + XDH.PrivateKey.Format.JWK, + XDH.PrivateKey.Format.RAW, + XDH.PrivateKey.Format.DER, + XDH.PrivateKey.Format.PEM, + -> "pkcs8" + } + + override fun beforeDecoding(algorithm: Algorithm, format: XDH.PrivateKey.Format, key: ByteArray): ByteArray = when (format) { + XDH.PrivateKey.Format.JWK -> key + XDH.PrivateKey.Format.RAW -> key + XDH.PrivateKey.Format.DER -> key + XDH.PrivateKey.Format.PEM -> unwrapPem(PemLabel.PrivateKey, key) + } + + override fun afterEncoding(format: XDH.PrivateKey.Format, key: ByteArray): ByteArray = when (format) { + XDH.PrivateKey.Format.JWK -> key + XDH.PrivateKey.Format.RAW -> key + XDH.PrivateKey.Format.DER -> key + XDH.PrivateKey.Format.PEM -> wrapPem(PemLabel.PrivateKey, key) + } +} diff --git a/cryptography-providers/webcrypto/src/commonMain/kotlin/internal/Algorithms.kt b/cryptography-providers/webcrypto/src/commonMain/kotlin/internal/Algorithms.kt index d1d0ef03..d2d03401 100644 --- a/cryptography-providers/webcrypto/src/commonMain/kotlin/internal/Algorithms.kt +++ b/cryptography-providers/webcrypto/src/commonMain/kotlin/internal/Algorithms.kt @@ -31,6 +31,7 @@ internal expect val Algorithm.ecKeyAlgorithmNamedCurve: String internal expect fun EcdsaSignatureAlgorithm(hash: String): Algorithm internal expect fun EcdhKeyDeriveAlgorithm(publicKey: CryptoKey): Algorithm +internal expect fun KeyDeriveAlgorithm(name: String, publicKey: CryptoKey): Algorithm internal expect fun Pbkdf2DeriveAlgorithm(hash: String, iterations: Int, salt: ByteArray): Algorithm @@ -53,3 +54,4 @@ internal expect fun RsaOaepCipherAlgorithm(label: ByteArray?): Algorithm internal expect fun RsaPssSignatureAlgorithm(saltLength: Int): Algorithm internal expect val Algorithm.rsaKeyAlgorithmHashName: String +internal expect val Algorithm.algorithmName: String diff --git a/cryptography-providers/webcrypto/src/commonMain/kotlin/internal/Env.kt b/cryptography-providers/webcrypto/src/commonMain/kotlin/internal/Env.kt new file mode 100644 index 00000000..f03a103d --- /dev/null +++ b/cryptography-providers/webcrypto/src/commonMain/kotlin/internal/Env.kt @@ -0,0 +1,10 @@ +/* + * Copyright (c) 2025 Oleg Yukhnevich. Use of this source code is governed by the Apache 2.0 license. + */ + +package dev.whyoleg.cryptography.providers.webcrypto.internal + +internal expect fun detectEngine(): Engine + +internal enum class Engine { Node, Chromium, Firefox, Safari, Unknown } + diff --git a/cryptography-providers/webcrypto/src/jsMain/kotlin/internal/Algorithms.js.kt b/cryptography-providers/webcrypto/src/jsMain/kotlin/internal/Algorithms.js.kt index 413d12d5..9cde7011 100644 --- a/cryptography-providers/webcrypto/src/jsMain/kotlin/internal/Algorithms.js.kt +++ b/cryptography-providers/webcrypto/src/jsMain/kotlin/internal/Algorithms.js.kt @@ -51,6 +51,9 @@ internal actual fun EcdsaSignatureAlgorithm(hash: String): Algorithm = internal actual fun EcdhKeyDeriveAlgorithm(publicKey: CryptoKey): Algorithm = js("{ name: 'ECDH', public: publicKey }").unsafeCast() +internal actual fun KeyDeriveAlgorithm(name: String, publicKey: CryptoKey): Algorithm = + js("{ name: name, public: publicKey }").unsafeCast() + internal actual fun Pbkdf2DeriveAlgorithm(hash: String, iterations: Int, salt: ByteArray): Algorithm = js("{ name: 'PBKDF2', hash: hash, iterations: iterations, salt: salt }").unsafeCast() @@ -77,3 +80,8 @@ internal actual val Algorithm.rsaKeyAlgorithmHashName: String get() = rsaKeyAlgo @Suppress("UNUSED_PARAMETER") private fun rsaKeyAlgorithmHashName(algorithm: Algorithm): String = js("algorithm.hash.name").unsafeCast() + +internal actual val Algorithm.algorithmName: String get() = algorithmName(this) + +@Suppress("UNUSED_PARAMETER") +private fun algorithmName(algorithm: Algorithm): String = js("algorithm.name").unsafeCast() diff --git a/cryptography-providers/webcrypto/src/jsMain/kotlin/internal/Env.js.kt b/cryptography-providers/webcrypto/src/jsMain/kotlin/internal/Env.js.kt new file mode 100644 index 00000000..51392b16 --- /dev/null +++ b/cryptography-providers/webcrypto/src/jsMain/kotlin/internal/Env.js.kt @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2025 Oleg Yukhnevich. Use of this source code is governed by the Apache 2.0 license. + */ + +package dev.whyoleg.cryptography.providers.webcrypto.internal + +internal actual fun detectEngine(): Engine { + // Node.js detection + val isNode = js("typeof process !== 'undefined' && process.versions && process.versions.node") as Boolean? ?: false + if (isNode) return Engine.Node + + // Browser detection via userAgent + val ua = try { + js("navigator.userAgent") as String + } catch (_: dynamic) { + "" + } + + return when { + ua.contains("Firefox", ignoreCase = true) -> Engine.Firefox + // Chromium derivatives first + ua.contains("Edg", ignoreCase = true) || ua.contains("OPR", ignoreCase = true) || ua.contains("Chrome", ignoreCase = true) || ua.contains("Chromium", ignoreCase = true) -> Engine.Chromium + // Safari (exclude Chrome/Chromium matches above) + ua.contains("Safari", ignoreCase = true) && ua.contains("AppleWebKit", ignoreCase = true) -> Engine.Safari + else -> Engine.Unknown + } +} + diff --git a/cryptography-providers/webcrypto/src/wasmJsMain/kotlin/internal/Algorithms.wasmJs.kt b/cryptography-providers/webcrypto/src/wasmJsMain/kotlin/internal/Algorithms.wasmJs.kt index d1c8c65e..eaad1e0f 100644 --- a/cryptography-providers/webcrypto/src/wasmJsMain/kotlin/internal/Algorithms.wasmJs.kt +++ b/cryptography-providers/webcrypto/src/wasmJsMain/kotlin/internal/Algorithms.wasmJs.kt @@ -64,6 +64,9 @@ internal actual fun EcdsaSignatureAlgorithm(hash: String): Algorithm = internal actual fun EcdhKeyDeriveAlgorithm(publicKey: CryptoKey): Algorithm = js("({ name: 'ECDH', public: publicKey })") +internal actual fun KeyDeriveAlgorithm(name: String, publicKey: CryptoKey): Algorithm = + js("({ name: name, public: publicKey })") + internal actual fun Pbkdf2DeriveAlgorithm(hash: String, iterations: Int, salt: ByteArray): Algorithm = jsPbkdf2DeriveAlgorithm(hash, iterations, salt.toInt8Array()) @@ -105,3 +108,8 @@ internal actual val Algorithm.rsaKeyAlgorithmHashName: String get() = rsaKeyAlgo @Suppress("UNUSED_PARAMETER") private fun rsaKeyAlgorithmHashName(algorithm: Algorithm): String = js("algorithm.hash.name") + +internal actual val Algorithm.algorithmName: String get() = algorithmName(this) + +@Suppress("UNUSED_PARAMETER") +private fun algorithmName(algorithm: Algorithm): String = js("algorithm.name") diff --git a/cryptography-providers/webcrypto/src/wasmJsMain/kotlin/internal/Env.wasmJs.kt b/cryptography-providers/webcrypto/src/wasmJsMain/kotlin/internal/Env.wasmJs.kt new file mode 100644 index 00000000..f4c8d660 --- /dev/null +++ b/cryptography-providers/webcrypto/src/wasmJsMain/kotlin/internal/Env.wasmJs.kt @@ -0,0 +1,7 @@ +/* + * Copyright (c) 2025 Oleg Yukhnevich. Use of this source code is governed by the Apache 2.0 license. + */ + +package dev.whyoleg.cryptography.providers.webcrypto.internal + +internal actual fun detectEngine(): Engine = Engine.Chromium diff --git a/docs/providers/cryptokit.md b/docs/providers/cryptokit.md index 61ba9d2d..44ccb6ad 100644 --- a/docs/providers/cryptokit.md +++ b/docs/providers/cryptokit.md @@ -8,6 +8,7 @@ For supported targets and algorithms, please consult [Supported primitives secti * KeyFormat: doesn't support `JWK` key format yet * AES.GCM supports only a default tag size of 96 bits +* EdDSA/XDH: Ed25519 and X25519 are supported via RAW key formats. DER/PEM for these curves are not exposed in CryptoKit and are not supported here. ## Example diff --git a/docs/providers/index.md b/docs/providers/index.md index 502d4756..037a1bed 100644 --- a/docs/providers/index.md +++ b/docs/providers/index.md @@ -89,9 +89,11 @@ For additional limitation please consult provider specific documentation. | | ⚠️ RSA-PKS1-v1_5 | ✅ | ❌ | ✅ | ❌ | ✅ | | | ⚠️ RSA-RAW | ✅ | ❌ | ✅ | ❌ | ✅ | | **Digital Signatures** | ECDSA | ✅ | ✅ | ✅ | ✅ | ✅ | +| | EdDSA | ✅ | ✅ | ❌ | ✅ | ✅ | | | RSA-SSA-PSS | ✅ | ✅ | ✅ | ❌ | ✅ | | | RSA-PKS1-v1_5 | ✅ | ✅ | ✅ | ❌ | ✅ | | **Key Agreement** | ECDH | ✅ | ✅ | ❌ | ✅ | ✅ | +| | XDH | ✅ | ✅ | ❌ | ✅ | ✅ | | **PRF/KDF** | PBKDF2 | ✅ | ✅ | ✅ | ❌ | ✅ | | | HKDF | ✅ | ✅ | ✅ | ✅ | ✅ | diff --git a/docs/providers/jdk.md b/docs/providers/jdk.md index 45186f69..93b2ddac 100644 --- a/docs/providers/jdk.md +++ b/docs/providers/jdk.md @@ -7,6 +7,12 @@ For supported targets and algorithms, please consult [Supported primitives secti ## Limitations * KeyFormat: doesn't support `JWK` key format yet + +### EdDSA and XDH + +EdDSA (Ed25519/Ed448) and XDH (X25519/X448) are exposed when supported by the underlying JDK. +Availability depends on JDK version and provider configuration. If your default JDK provider lacks them, +consider supplying a custom provider (e.g., BouncyCastle) as described below. ## Example ```kotlin diff --git a/docs/providers/openssl3.md b/docs/providers/openssl3.md index 192cb97d..7bc5829c 100644 --- a/docs/providers/openssl3.md +++ b/docs/providers/openssl3.md @@ -21,6 +21,11 @@ For supported targets and algorithms, please consult [Supported primitives secti * KeyFormat: doesn't support `JWK` key format yet +### EdDSA and XDH + +OpenSSL 3.x supports EdDSA (Ed25519/Ed448) and XDH (X25519/X448). These algorithms are available via the OpenSSL3 provider modules +(`api`, `shared`, or `prebuilt`). + ## Example ```kotlin diff --git a/docs/providers/webcrypto.md b/docs/providers/webcrypto.md index 7fc4a11b..3d2f4b4d 100644 --- a/docs/providers/webcrypto.md +++ b/docs/providers/webcrypto.md @@ -9,16 +9,27 @@ For supported targets and algorithms, please consult [Supported primitives secti * only `suspend` functions are supported, because `WebCrypto` API is async by default * AES.* (browser only): may not support `192 bit` keys * AES.CBC: only `padding=true` is supported +* EdDSA/XDH availability depends on the engine: + - Node.js, Firefox, Safari: supported (Ed25519/Ed448, X25519/X448) + - Chromium-based (Chrome/Edge/Opera): requires enabling experimental web platform features; otherwise not exposed by the provider ## Example ```kotlin +// default provider val provider = CryptographyProvider.WebCrypto // or CryptographyProvider.Default // get some algorithm provider.get(SHA512) ``` +To opt-in to EdDSA/XDH on Chromium-based engines (with experimental web platform features enabled), +explicitly enable experimental Edwards algorithms: + +```kotlin +val provider = CryptographyProvider.WebCrypto(enableExperimentalEdwards = true) +``` + ## Using in your projects ```kotlin From c68be06eecc0c22464e7f965db4bc318e5ec5b3b Mon Sep 17 00:00:00 2001 From: Aria Wisp Date: Thu, 4 Sep 2025 15:11:51 -0600 Subject: [PATCH 10/22] Address PR #105 review: - Docs: remove duplicated EdDSA/XDH sections (JDK/OpenSSL3); fix CryptoKit notes; clarify WebCrypto availability wording and remove opt-in flag. - WebCrypto: drop engine gating + experimental flag; pass key.algorithm to EdDSA ops; fix private key JWK/RAW mapping; use KeyDeriveAlgorithm for ECDH. - JDK EdDSA: support RAW via PKCS8/SPKI wrap/unwrap. - OpenSSL3 EdDSA/XDH: add RAW key format via DER wrap/unwrap; pass curve through wrappers; extract shared derive to operations. - Tests: remove WebCrypto skips; align JDK EdDSA gating to JDK<11; add EdDSA/XDH compatibility tests; include in generator. --- .gh-pr-105-comments.jsonl | 26 ++++ .../tests/GenerateProviderTestsTask.kt | 2 + .../src/jvmMain/kotlin/algorithms/JdkEdDSA.kt | 34 ++++- .../kotlin/algorithms/Openssl3Ecdh.kt | 20 +-- .../kotlin/algorithms/Openssl3EdDSA.kt | 59 +++++++- .../kotlin/algorithms/Openssl3XDH.kt | 81 +++++++---- .../kotlin/operations/KeyAgreement.kt | 31 ++++ .../compatibility/EdDsaCompatibilityTest.kt | 137 ++++++++++++++++++ .../compatibility/XdhCompatibilityTest.kt | 134 +++++++++++++++++ .../kotlin/default/SupportedAlgorithmsTest.kt | 5 +- .../tests/src/commonMain/kotlin/support.kt | 6 +- .../kotlin/WebCryptoCryptographyProvider.kt | 26 +--- .../kotlin/algorithms/WebCryptoEcdh.kt | 4 +- .../kotlin/algorithms/WebCryptoEdDSA.kt | 15 +- .../kotlin/algorithms/WebCryptoXDH.kt | 9 +- .../commonMain/kotlin/internal/Algorithms.kt | 1 - .../jsMain/kotlin/internal/Algorithms.js.kt | 3 - .../kotlin/internal/Algorithms.wasmJs.kt | 3 - docs/providers/cryptokit.md | 2 +- docs/providers/jdk.md | 5 - docs/providers/openssl3.md | 4 - docs/providers/webcrypto.md | 11 +- 22 files changed, 489 insertions(+), 129 deletions(-) create mode 100644 .gh-pr-105-comments.jsonl create mode 100644 cryptography-providers/openssl3/api/src/commonMain/kotlin/operations/KeyAgreement.kt create mode 100644 cryptography-providers/tests/src/commonMain/kotlin/compatibility/EdDsaCompatibilityTest.kt create mode 100644 cryptography-providers/tests/src/commonMain/kotlin/compatibility/XdhCompatibilityTest.kt diff --git a/.gh-pr-105-comments.jsonl b/.gh-pr-105-comments.jsonl new file mode 100644 index 00000000..9f62dbc0 --- /dev/null +++ b/.gh-pr-105-comments.jsonl @@ -0,0 +1,26 @@ +{"author":"whyoleg","body":"this could be removed in favor of `Custom Java providers` section above ","diff_hunk":"@@ -8,6 +8,11 @@ For supported targets and algorithms, please consult [Supported primitives secti\n \n * KeyFormat: doesn't support `JWK` key format yet\n \n+### EdDSA and XDH","id":2322958544,"in_reply_to_id":null,"line":11,"original_commit_id":"c8488c9a2b9a24362bf1b5b7018a29e8e2286937","original_line":11,"path":"docs/providers/jdk.md","side":"RIGHT","start_line":null} +{"author":"whyoleg","body":"this should be removed","diff_hunk":"@@ -21,6 +21,11 @@ For supported targets and algorithms, please consult [Supported primitives secti\n \n * KeyFormat: doesn't support `JWK` key format yet\n \n+### EdDSA and XDH","id":2322959257,"in_reply_to_id":null,"line":24,"original_commit_id":"c8488c9a2b9a24362bf1b5b7018a29e8e2286937","original_line":24,"path":"docs/providers/openssl3.md","side":"RIGHT","start_line":null} +{"author":"whyoleg","body":"it's not true: we could, and should support those, as we have both DER and PEM machinery implemented and used there.","diff_hunk":"@@ -8,6 +8,7 @@ For supported targets and algorithms, please consult [Supported primitives secti\n \n * KeyFormat: doesn't support `JWK` key format yet\n * AES.GCM supports only a default tag size of 96 bits\n+* EdDSA/XDH: Ed25519 and X25519 are supported via RAW key formats. DER/PEM for these curves are not exposed in CryptoKit and are not supported here.","id":2322963075,"in_reply_to_id":null,"line":11,"original_commit_id":"c8488c9a2b9a24362bf1b5b7018a29e8e2286937","original_line":11,"path":"docs/providers/cryptokit.md","side":"RIGHT","start_line":null} +{"author":"whyoleg","body":"it might be better, to mention instead, that those algorithms were added later (maybe add link to RFC?) and so might not be supported by all browsers","diff_hunk":"@@ -9,16 +9,27 @@ For supported targets and algorithms, please consult [Supported primitives secti\n * only `suspend` functions are supported, because `WebCrypto` API is async by default\n * AES.* (browser only): may not support `192 bit` keys\n * AES.CBC: only `padding=true` is supported\n+* EdDSA/XDH availability depends on the engine:","id":2322965396,"in_reply_to_id":null,"line":12,"original_commit_id":"c8488c9a2b9a24362bf1b5b7018a29e8e2286937","original_line":12,"path":"docs/providers/webcrypto.md","side":"RIGHT","start_line":null} +{"author":"whyoleg","body":"no need to have this, as we should not have this flag","diff_hunk":"@@ -9,16 +9,27 @@ For supported targets and algorithms, please consult [Supported primitives secti\n * only `suspend` functions are supported, because `WebCrypto` API is async by default\n * AES.* (browser only): may not support `192 bit` keys\n * AES.CBC: only `padding=true` is supported\n+* EdDSA/XDH availability depends on the engine:\n+ - Node.js, Firefox, Safari: supported (Ed25519/Ed448, X25519/X448)\n+ - Chromium-based (Chrome/Edge/Opera): requires enabling experimental web platform features; otherwise not exposed by the provider\n \n ## Example\n \n ```kotlin\n+// default provider\n val provider = CryptographyProvider.WebCrypto // or CryptographyProvider.Default\n \n // get some algorithm\n provider.get(SHA512)\n ```\n \n+To opt-in to EdDSA/XDH on Chromium-based engines (with experimental web platform features enabled),","id":2322966182,"in_reply_to_id":null,"line":26,"original_commit_id":"c8488c9a2b9a24362bf1b5b7018a29e8e2286937","original_line":26,"path":"docs/providers/webcrypto.md","side":"RIGHT","start_line":null} +{"author":"whyoleg","body":"I don't think, that it's correct to do it like this.\nIf Chromium will at some point enable this support, or some other environment - we will need to release a new version.\nInstead, it's fine to fail at runtime, that algorithm is not supported with an exception coming from WebCrypto.\nIt's the same to how JDK provider fails, if algorithm is not supported by the security provider\n\nLet's remove this machinery (including `detectEngine`)","diff_hunk":"@@ -15,6 +17,18 @@ public val CryptographyProvider.Companion.WebCrypto: CryptographyProvider by def\n internal object WebCryptoCryptographyProvider : CryptographyProvider() {\n override val name: String get() = \"WebCrypto\"\n \n+ private val engine: Engine = detectEngine()","id":2322973682,"in_reply_to_id":null,"line":20,"original_commit_id":"c8488c9a2b9a24362bf1b5b7018a29e8e2286937","original_line":20,"path":"cryptography-providers/webcrypto/src/commonMain/kotlin/WebCryptoCryptographyProvider.kt","side":"RIGHT","start_line":null} +{"author":"whyoleg","body":"can't we just pass `publicKey.algorithm` there?\nsame for `privateKey.algorithm` below","diff_hunk":"@@ -0,0 +1,107 @@\n+/*\n+ * Copyright (c) 2025 Oleg Yukhnevich. Use of this source code is governed by the Apache 2.0 license.\n+ */\n+\n+package dev.whyoleg.cryptography.providers.webcrypto.algorithms\n+\n+import dev.whyoleg.cryptography.algorithms.*\n+import dev.whyoleg.cryptography.materials.key.*\n+import dev.whyoleg.cryptography.operations.*\n+import dev.whyoleg.cryptography.providers.webcrypto.internal.*\n+import dev.whyoleg.cryptography.providers.webcrypto.materials.*\n+import dev.whyoleg.cryptography.providers.webcrypto.operations.*\n+import dev.whyoleg.cryptography.providers.base.materials.*\n+import dev.whyoleg.cryptography.serialization.pem.*\n+\n+internal object WebCryptoEdDSA : EdDSA {\n+ private fun curveName(curve: EdDSA.Curve): String = when (curve) {\n+ EdDSA.Curve.Ed25519 -\u003e \"Ed25519\"\n+ EdDSA.Curve.Ed448 -\u003e \"Ed448\"\n+ }\n+\n+ override fun publicKeyDecoder(curve: EdDSA.Curve): KeyDecoder\u003cEdDSA.PublicKey.Format, EdDSA.PublicKey\u003e = WebCryptoKeyDecoder(\n+ algorithm = Algorithm(curveName(curve)),\n+ keyProcessor = EdPublicKeyProcessor,\n+ keyWrapper = WebCryptoKeyWrapper(arrayOf(\"verify\")) { EdDsaPublicKey(it) },\n+ )\n+\n+ override fun privateKeyDecoder(curve: EdDSA.Curve): KeyDecoder\u003cEdDSA.PrivateKey.Format, EdDSA.PrivateKey\u003e = WebCryptoKeyDecoder(\n+ algorithm = Algorithm(curveName(curve)),\n+ keyProcessor = EdPrivateKeyProcessor,\n+ keyWrapper = WebCryptoKeyWrapper(arrayOf(\"sign\")) { EdDsaPrivateKey(it) },\n+ )\n+\n+ override fun keyPairGenerator(curve: EdDSA.Curve): KeyGenerator\u003cEdDSA.KeyPair\u003e = WebCryptoAsymmetricKeyGenerator(\n+ algorithm = Algorithm(curveName(curve)),\n+ keyUsages = arrayOf(\"verify\", \"sign\"),\n+ keyPairWrapper = { EdDsaKeyPair(EdDsaPublicKey(it.publicKey), EdDsaPrivateKey(it.privateKey)) },\n+ )\n+\n+ private class EdDsaKeyPair(\n+ override val publicKey: EdDSA.PublicKey,\n+ override val privateKey: EdDSA.PrivateKey,\n+ ) : EdDSA.KeyPair\n+\n+ private class EdDsaPublicKey(\n+ val publicKey: CryptoKey,\n+ ) : WebCryptoEncodableKey\u003cEdDSA.PublicKey.Format\u003e(publicKey, EdPublicKeyProcessor), EdDSA.PublicKey {\n+ override fun signatureVerifier(): SignatureVerifier {\n+ return WebCryptoSignatureVerifier(Algorithm(publicKey.algorithm.algorithmName), publicKey)","id":2322986311,"in_reply_to_id":null,"line":49,"original_commit_id":"c8488c9a2b9a24362bf1b5b7018a29e8e2286937","original_line":49,"path":"cryptography-providers/webcrypto/src/commonMain/kotlin/algorithms/WebCryptoEdDSA.kt","side":"RIGHT","start_line":null} +{"author":"whyoleg","body":"Something is wrong here: we should have both `jwk` and `raw` here too. Also it should be tested","diff_hunk":"@@ -0,0 +1,107 @@\n+/*\n+ * Copyright (c) 2025 Oleg Yukhnevich. Use of this source code is governed by the Apache 2.0 license.\n+ */\n+\n+package dev.whyoleg.cryptography.providers.webcrypto.algorithms\n+\n+import dev.whyoleg.cryptography.algorithms.*\n+import dev.whyoleg.cryptography.materials.key.*\n+import dev.whyoleg.cryptography.operations.*\n+import dev.whyoleg.cryptography.providers.webcrypto.internal.*\n+import dev.whyoleg.cryptography.providers.webcrypto.materials.*\n+import dev.whyoleg.cryptography.providers.webcrypto.operations.*\n+import dev.whyoleg.cryptography.providers.base.materials.*\n+import dev.whyoleg.cryptography.serialization.pem.*\n+\n+internal object WebCryptoEdDSA : EdDSA {\n+ private fun curveName(curve: EdDSA.Curve): String = when (curve) {\n+ EdDSA.Curve.Ed25519 -\u003e \"Ed25519\"\n+ EdDSA.Curve.Ed448 -\u003e \"Ed448\"\n+ }\n+\n+ override fun publicKeyDecoder(curve: EdDSA.Curve): KeyDecoder\u003cEdDSA.PublicKey.Format, EdDSA.PublicKey\u003e = WebCryptoKeyDecoder(\n+ algorithm = Algorithm(curveName(curve)),\n+ keyProcessor = EdPublicKeyProcessor,\n+ keyWrapper = WebCryptoKeyWrapper(arrayOf(\"verify\")) { EdDsaPublicKey(it) },\n+ )\n+\n+ override fun privateKeyDecoder(curve: EdDSA.Curve): KeyDecoder\u003cEdDSA.PrivateKey.Format, EdDSA.PrivateKey\u003e = WebCryptoKeyDecoder(\n+ algorithm = Algorithm(curveName(curve)),\n+ keyProcessor = EdPrivateKeyProcessor,\n+ keyWrapper = WebCryptoKeyWrapper(arrayOf(\"sign\")) { EdDsaPrivateKey(it) },\n+ )\n+\n+ override fun keyPairGenerator(curve: EdDSA.Curve): KeyGenerator\u003cEdDSA.KeyPair\u003e = WebCryptoAsymmetricKeyGenerator(\n+ algorithm = Algorithm(curveName(curve)),\n+ keyUsages = arrayOf(\"verify\", \"sign\"),\n+ keyPairWrapper = { EdDsaKeyPair(EdDsaPublicKey(it.publicKey), EdDsaPrivateKey(it.privateKey)) },\n+ )\n+\n+ private class EdDsaKeyPair(\n+ override val publicKey: EdDSA.PublicKey,\n+ override val privateKey: EdDSA.PrivateKey,\n+ ) : EdDSA.KeyPair\n+\n+ private class EdDsaPublicKey(\n+ val publicKey: CryptoKey,\n+ ) : WebCryptoEncodableKey\u003cEdDSA.PublicKey.Format\u003e(publicKey, EdPublicKeyProcessor), EdDSA.PublicKey {\n+ override fun signatureVerifier(): SignatureVerifier {\n+ return WebCryptoSignatureVerifier(Algorithm(publicKey.algorithm.algorithmName), publicKey)\n+ }\n+ }\n+\n+ private class EdDsaPrivateKey(\n+ val privateKey: CryptoKey,\n+ ) : WebCryptoEncodableKey\u003cEdDSA.PrivateKey.Format\u003e(privateKey, EdPrivateKeyProcessor), EdDSA.PrivateKey {\n+ override fun signatureGenerator(): SignatureGenerator {\n+ return WebCryptoSignatureGenerator(Algorithm(privateKey.algorithm.algorithmName), privateKey)\n+ }\n+ }\n+}\n+\n+private object EdPublicKeyProcessor : WebCryptoKeyProcessor\u003cEdDSA.PublicKey.Format\u003e() {\n+ override fun stringFormat(format: EdDSA.PublicKey.Format): String = when (format) {\n+ EdDSA.PublicKey.Format.JWK -\u003e \"jwk\"\n+ EdDSA.PublicKey.Format.RAW -\u003e \"raw\"\n+ EdDSA.PublicKey.Format.DER,\n+ EdDSA.PublicKey.Format.PEM -\u003e \"spki\"\n+ }\n+\n+ override fun beforeDecoding(algorithm: Algorithm, format: EdDSA.PublicKey.Format, key: ByteArray): ByteArray = when (format) {\n+ EdDSA.PublicKey.Format.JWK -\u003e key\n+ EdDSA.PublicKey.Format.RAW -\u003e key\n+ EdDSA.PublicKey.Format.DER -\u003e key\n+ EdDSA.PublicKey.Format.PEM -\u003e unwrapPem(PemLabel.PublicKey, key)\n+ }\n+\n+ override fun afterEncoding(format: EdDSA.PublicKey.Format, key: ByteArray): ByteArray = when (format) {\n+ EdDSA.PublicKey.Format.JWK -\u003e key\n+ EdDSA.PublicKey.Format.RAW -\u003e key\n+ EdDSA.PublicKey.Format.DER -\u003e key\n+ EdDSA.PublicKey.Format.PEM -\u003e wrapPem(PemLabel.PublicKey, key)\n+ }\n+}\n+\n+private object EdPrivateKeyProcessor : WebCryptoKeyProcessor\u003cEdDSA.PrivateKey.Format\u003e() {\n+ override fun stringFormat(format: EdDSA.PrivateKey.Format): String = when (format) {\n+ EdDSA.PrivateKey.Format.JWK,","id":2322988363,"in_reply_to_id":null,"line":87,"original_commit_id":"c8488c9a2b9a24362bf1b5b7018a29e8e2286937","original_line":87,"path":"cryptography-providers/webcrypto/src/commonMain/kotlin/algorithms/WebCryptoEdDSA.kt","side":"RIGHT","start_line":null} +{"author":"whyoleg","body":"all the same comments from `WebCryptoEdDSA` are applied here","diff_hunk":"@@ -0,0 +1,136 @@\n+/*\n+ * Copyright (c) 2025 Oleg Yukhnevich. Use of this source code is governed by the Apache 2.0 license.\n+ */\n+\n+package dev.whyoleg.cryptography.providers.webcrypto.algorithms\n+\n+import dev.whyoleg.cryptography.algorithms.*\n+import dev.whyoleg.cryptography.materials.key.*\n+import dev.whyoleg.cryptography.operations.*\n+import dev.whyoleg.cryptography.providers.webcrypto.internal.*\n+import dev.whyoleg.cryptography.providers.webcrypto.materials.*\n+import dev.whyoleg.cryptography.providers.base.materials.*\n+import dev.whyoleg.cryptography.serialization.pem.*\n+\n+internal object WebCryptoXDH : XDH {","id":2322993298,"in_reply_to_id":null,"line":15,"original_commit_id":"c8488c9a2b9a24362bf1b5b7018a29e8e2286937","original_line":15,"path":"cryptography-providers/webcrypto/src/commonMain/kotlin/algorithms/WebCryptoXDH.kt","side":"RIGHT","start_line":null} +{"author":"whyoleg","body":"it looks like we can just remove `EcdhKeyDeriveAlgorithm` and use `KeyDeriveAlgorithm` everywhere?","diff_hunk":"@@ -31,6 +31,7 @@ internal expect val Algorithm.ecKeyAlgorithmNamedCurve: String\n internal expect fun EcdsaSignatureAlgorithm(hash: String): Algorithm\n \n internal expect fun EcdhKeyDeriveAlgorithm(publicKey: CryptoKey): Algorithm","id":2322999331,"in_reply_to_id":null,"line":33,"original_commit_id":"c8488c9a2b9a24362bf1b5b7018a29e8e2286937","original_line":33,"path":"cryptography-providers/webcrypto/src/commonMain/kotlin/internal/Algorithms.kt","side":"RIGHT","start_line":null} +{"author":"whyoleg","body":"Those should be definitely dropped. If there is some problem with this on CI and locally, I could take a look. probably we can just pass experimental argument in karma somewhere.","diff_hunk":"@@ -141,6 +146,11 @@ fun ProviderTestScope.supports(algorithmId: CryptographyAlgorithmId\u003c*\u003e): Boolean\n when (algorithmId) {\n AES.CMAC if provider.isJdkDefault -\u003e \"Default JDK provider doesn't support AES-CMAC, only supported with BouncyCastle\"\n RSA.PSS if provider.isJdkDefault \u0026\u0026 platform.isAndroid -\u003e \"JDK provider on Android doesn't support RSASSA-PSS\"\n+ EdDSA if provider.isWebCrypto -\u003e \"WebCrypto EdDSA not yet supported in this engine\"\n+ XDH if provider.isWebCrypto -\u003e \"WebCrypto X25519/X448 not yet supported in this engine\"","id":2323007111,"in_reply_to_id":null,"line":150,"original_commit_id":"c8488c9a2b9a24362bf1b5b7018a29e8e2286937","original_line":150,"path":"cryptography-providers/tests/src/commonMain/kotlin/support.kt","side":"RIGHT","start_line":149} +{"author":"whyoleg","body":"I think that it should be the same, as for `XDH` - those are supported from JDK 11","diff_hunk":"@@ -141,6 +146,11 @@ fun ProviderTestScope.supports(algorithmId: CryptographyAlgorithmId\u003c*\u003e): Boolean\n when (algorithmId) {\n AES.CMAC if provider.isJdkDefault -\u003e \"Default JDK provider doesn't support AES-CMAC, only supported with BouncyCastle\"\n RSA.PSS if provider.isJdkDefault \u0026\u0026 platform.isAndroid -\u003e \"JDK provider on Android doesn't support RSASSA-PSS\"\n+ EdDSA if provider.isWebCrypto -\u003e \"WebCrypto EdDSA not yet supported in this engine\"\n+ XDH if provider.isWebCrypto -\u003e \"WebCrypto X25519/X448 not yet supported in this engine\"\n+ // Some JDKs used in CI (jvm / jvm11) lack these algorithms\n+ EdDSA if provider.isJdkDefault -\u003e \"Default JDK may not support EdDSA on this JDK version\"","id":2323016503,"in_reply_to_id":null,"line":152,"original_commit_id":"c8488c9a2b9a24362bf1b5b7018a29e8e2286937","original_line":152,"path":"cryptography-providers/tests/src/commonMain/kotlin/support.kt","side":"RIGHT","start_line":null} +{"author":"whyoleg","body":"should be just `!context.provider.isApple` after the engine machinery is removed","diff_hunk":"@@ -51,6 +51,11 @@ abstract class SupportedAlgorithmsTest(provider: CryptographyProvider) : Provide\n assertSupports(ECDSA)\n assertSupports(ECDH, !context.provider.isApple)\n \n+ // Edwards-family\n+ // WebCrypto availability depends on engine; skip expecting it there\n+ assertSupports(EdDSA, !context.provider.isApple \u0026\u0026 !context.provider.isWebCrypto)","id":2323019510,"in_reply_to_id":null,"line":56,"original_commit_id":"c8488c9a2b9a24362bf1b5b7018a29e8e2286937","original_line":56,"path":"cryptography-providers/tests/src/commonMain/kotlin/default/SupportedAlgorithmsTest.kt","side":"RIGHT","start_line":null} +{"author":"whyoleg","body":"We should also add compatibility tests for both EdDSA and XDH, similar to the ECDSA and ECDH","diff_hunk":"@@ -0,0 +1,49 @@\n+/*\n+ * Copyright (c) 2025 Oleg Yukhnevich. Use of this source code is governed by the Apache 2.0 license.\n+ */\n+\n+package dev.whyoleg.cryptography.providers.tests.default\n+\n+import dev.whyoleg.cryptography.*\n+import dev.whyoleg.cryptography.algorithms.*\n+import dev.whyoleg.cryptography.providers.tests.*\n+import dev.whyoleg.cryptography.random.*\n+import kotlinx.io.bytestring.*\n+import kotlin.test.*\n+\n+abstract class EdDsaTest(provider: CryptographyProvider) : AlgorithmTest\u003cEdDSA\u003e(EdDSA, provider), SignatureTest {","id":2323024591,"in_reply_to_id":null,"line":14,"original_commit_id":"c8488c9a2b9a24362bf1b5b7018a29e8e2286937","original_line":14,"path":"cryptography-providers/tests/src/commonMain/kotlin/default/EdDsaTest.kt","side":"RIGHT","start_line":null} +{"author":"whyoleg","body":"we should support `RAW` format (openSSL should support it)","diff_hunk":"@@ -0,0 +1,114 @@\n+/*\n+ * Copyright (c) 2025 Oleg Yukhnevich. Use of this source code is governed by the Apache 2.0 license.\n+ */\n+\n+package dev.whyoleg.cryptography.providers.openssl3.algorithms\n+\n+import dev.whyoleg.cryptography.algorithms.*\n+import dev.whyoleg.cryptography.materials.key.*\n+import dev.whyoleg.cryptography.operations.*\n+import dev.whyoleg.cryptography.providers.base.*\n+import dev.whyoleg.cryptography.providers.openssl3.internal.*\n+import dev.whyoleg.cryptography.providers.openssl3.internal.cinterop.*\n+import dev.whyoleg.cryptography.providers.openssl3.materials.*\n+import kotlinx.cinterop.*\n+import platform.posix.*\n+import kotlin.experimental.*\n+\n+internal object Openssl3XDH : XDH {\n+ private fun algorithmName(curve: XDH.Curve): String = when (curve) {\n+ XDH.Curve.X25519 -\u003e \"X25519\"\n+ XDH.Curve.X448 -\u003e \"X448\"\n+ }\n+\n+ override fun publicKeyDecoder(curve: XDH.Curve): KeyDecoder\u003cXDH.PublicKey.Format, XDH.PublicKey\u003e =\n+ object : Openssl3PublicKeyDecoder\u003cXDH.PublicKey.Format, XDH.PublicKey\u003e(algorithmName(curve)) {\n+ override fun inputType(format: XDH.PublicKey.Format): String = when (format) {\n+ XDH.PublicKey.Format.DER -\u003e \"DER\"\n+ XDH.PublicKey.Format.PEM -\u003e \"PEM\"\n+ XDH.PublicKey.Format.JWK,\n+ XDH.PublicKey.Format.RAW -\u003e error(\"$format format is not supported\")","id":2323027291,"in_reply_to_id":null,"line":30,"original_commit_id":"c8488c9a2b9a24362bf1b5b7018a29e8e2286937","original_line":30,"path":"cryptography-providers/openssl3/api/src/commonMain/kotlin/algorithms/Openssl3XDH.kt","side":"RIGHT","start_line":null} +{"author":"whyoleg","body":"we should support `RAW` format (openSSL should support it)","diff_hunk":"@@ -0,0 +1,114 @@\n+/*\n+ * Copyright (c) 2025 Oleg Yukhnevich. Use of this source code is governed by the Apache 2.0 license.\n+ */\n+\n+package dev.whyoleg.cryptography.providers.openssl3.algorithms\n+\n+import dev.whyoleg.cryptography.algorithms.*\n+import dev.whyoleg.cryptography.materials.key.*\n+import dev.whyoleg.cryptography.operations.*\n+import dev.whyoleg.cryptography.providers.base.*\n+import dev.whyoleg.cryptography.providers.openssl3.internal.*\n+import dev.whyoleg.cryptography.providers.openssl3.internal.cinterop.*\n+import dev.whyoleg.cryptography.providers.openssl3.materials.*\n+import kotlinx.cinterop.*\n+import platform.posix.*\n+import kotlin.experimental.*\n+\n+internal object Openssl3XDH : XDH {\n+ private fun algorithmName(curve: XDH.Curve): String = when (curve) {\n+ XDH.Curve.X25519 -\u003e \"X25519\"\n+ XDH.Curve.X448 -\u003e \"X448\"\n+ }\n+\n+ override fun publicKeyDecoder(curve: XDH.Curve): KeyDecoder\u003cXDH.PublicKey.Format, XDH.PublicKey\u003e =\n+ object : Openssl3PublicKeyDecoder\u003cXDH.PublicKey.Format, XDH.PublicKey\u003e(algorithmName(curve)) {\n+ override fun inputType(format: XDH.PublicKey.Format): String = when (format) {\n+ XDH.PublicKey.Format.DER -\u003e \"DER\"\n+ XDH.PublicKey.Format.PEM -\u003e \"PEM\"\n+ XDH.PublicKey.Format.JWK,\n+ XDH.PublicKey.Format.RAW -\u003e error(\"$format format is not supported\")\n+ }\n+\n+ override fun wrapKey(key: CPointer\u003cEVP_PKEY\u003e): XDH.PublicKey = XdhPublicKey(key)\n+ }\n+\n+ override fun privateKeyDecoder(curve: XDH.Curve): KeyDecoder\u003cXDH.PrivateKey.Format, XDH.PrivateKey\u003e =\n+ object : Openssl3PrivateKeyDecoder\u003cXDH.PrivateKey.Format, XDH.PrivateKey\u003e(algorithmName(curve)) {\n+ override fun inputType(format: XDH.PrivateKey.Format): String = when (format) {\n+ XDH.PrivateKey.Format.DER -\u003e \"DER\"\n+ XDH.PrivateKey.Format.PEM -\u003e \"PEM\"\n+ XDH.PrivateKey.Format.JWK,\n+ XDH.PrivateKey.Format.RAW -\u003e error(\"$format format is not supported\")","id":2323027616,"in_reply_to_id":null,"line":42,"original_commit_id":"c8488c9a2b9a24362bf1b5b7018a29e8e2286937","original_line":42,"path":"cryptography-providers/openssl3/api/src/commonMain/kotlin/algorithms/Openssl3XDH.kt","side":"RIGHT","start_line":null} +{"author":"whyoleg","body":"let's extract the function from `ecdh` implementation to `operations` package and use it here, instead of just copying ","diff_hunk":"@@ -0,0 +1,114 @@\n+/*\n+ * Copyright (c) 2025 Oleg Yukhnevich. Use of this source code is governed by the Apache 2.0 license.\n+ */\n+\n+package dev.whyoleg.cryptography.providers.openssl3.algorithms\n+\n+import dev.whyoleg.cryptography.algorithms.*\n+import dev.whyoleg.cryptography.materials.key.*\n+import dev.whyoleg.cryptography.operations.*\n+import dev.whyoleg.cryptography.providers.base.*\n+import dev.whyoleg.cryptography.providers.openssl3.internal.*\n+import dev.whyoleg.cryptography.providers.openssl3.internal.cinterop.*\n+import dev.whyoleg.cryptography.providers.openssl3.materials.*\n+import kotlinx.cinterop.*\n+import platform.posix.*\n+import kotlin.experimental.*\n+\n+internal object Openssl3XDH : XDH {\n+ private fun algorithmName(curve: XDH.Curve): String = when (curve) {\n+ XDH.Curve.X25519 -\u003e \"X25519\"\n+ XDH.Curve.X448 -\u003e \"X448\"\n+ }\n+\n+ override fun publicKeyDecoder(curve: XDH.Curve): KeyDecoder\u003cXDH.PublicKey.Format, XDH.PublicKey\u003e =\n+ object : Openssl3PublicKeyDecoder\u003cXDH.PublicKey.Format, XDH.PublicKey\u003e(algorithmName(curve)) {\n+ override fun inputType(format: XDH.PublicKey.Format): String = when (format) {\n+ XDH.PublicKey.Format.DER -\u003e \"DER\"\n+ XDH.PublicKey.Format.PEM -\u003e \"PEM\"\n+ XDH.PublicKey.Format.JWK,\n+ XDH.PublicKey.Format.RAW -\u003e error(\"$format format is not supported\")\n+ }\n+\n+ override fun wrapKey(key: CPointer\u003cEVP_PKEY\u003e): XDH.PublicKey = XdhPublicKey(key)\n+ }\n+\n+ override fun privateKeyDecoder(curve: XDH.Curve): KeyDecoder\u003cXDH.PrivateKey.Format, XDH.PrivateKey\u003e =\n+ object : Openssl3PrivateKeyDecoder\u003cXDH.PrivateKey.Format, XDH.PrivateKey\u003e(algorithmName(curve)) {\n+ override fun inputType(format: XDH.PrivateKey.Format): String = when (format) {\n+ XDH.PrivateKey.Format.DER -\u003e \"DER\"\n+ XDH.PrivateKey.Format.PEM -\u003e \"PEM\"\n+ XDH.PrivateKey.Format.JWK,\n+ XDH.PrivateKey.Format.RAW -\u003e error(\"$format format is not supported\")\n+ }\n+\n+ override fun wrapKey(key: CPointer\u003cEVP_PKEY\u003e): XDH.PrivateKey = XdhPrivateKey(key)\n+ }\n+\n+ override fun keyPairGenerator(curve: XDH.Curve): KeyGenerator\u003cXDH.KeyPair\u003e =\n+ object : Openssl3KeyPairGenerator\u003cXDH.KeyPair\u003e(algorithmName(curve)) {\n+ override fun MemScope.createParams(): CValuesRef\u003cOSSL_PARAM\u003e? = null\n+ override fun wrapKeyPair(keyPair: CPointer\u003cEVP_PKEY\u003e): XDH.KeyPair = XdhKeyPair(\n+ publicKey = XdhPublicKey(keyPair),\n+ privateKey = XdhPrivateKey(keyPair)\n+ )\n+ }\n+\n+ private class XdhKeyPair(\n+ override val publicKey: XDH.PublicKey,\n+ override val privateKey: XDH.PrivateKey,\n+ ) : XDH.KeyPair\n+\n+ private class XdhPublicKey(\n+ key: CPointer\u003cEVP_PKEY\u003e,\n+ ) : XDH.PublicKey, Openssl3PublicKeyEncodable\u003cXDH.PublicKey.Format\u003e(key), SharedSecretGenerator\u003cXDH.PrivateKey\u003e {\n+ override fun outputType(format: XDH.PublicKey.Format): String = when (format) {\n+ XDH.PublicKey.Format.DER -\u003e \"DER\"\n+ XDH.PublicKey.Format.PEM -\u003e \"PEM\"\n+ XDH.PublicKey.Format.JWK,\n+ XDH.PublicKey.Format.RAW -\u003e error(\"$format format is not supported\")\n+ }\n+\n+ override fun sharedSecretGenerator(): SharedSecretGenerator\u003cXDH.PrivateKey\u003e = this\n+ override fun generateSharedSecretToByteArrayBlocking(other: XDH.PrivateKey): ByteArray {\n+ check(other is XdhPrivateKey)\n+ return deriveSharedSecret(publicKey = key, privateKey = other.key)\n+ }\n+ }\n+\n+ private class XdhPrivateKey(\n+ key: CPointer\u003cEVP_PKEY\u003e,\n+ ) : XDH.PrivateKey, Openssl3PrivateKeyEncodable\u003cXDH.PrivateKey.Format\u003e(key), SharedSecretGenerator\u003cXDH.PublicKey\u003e {\n+ override fun outputType(format: XDH.PrivateKey.Format): String = when (format) {\n+ XDH.PrivateKey.Format.DER -\u003e \"DER\"\n+ XDH.PrivateKey.Format.PEM -\u003e \"PEM\"\n+ XDH.PrivateKey.Format.JWK,\n+ XDH.PrivateKey.Format.RAW -\u003e error(\"$format format is not supported\")\n+ }\n+\n+ override fun sharedSecretGenerator(): SharedSecretGenerator\u003cXDH.PublicKey\u003e = this\n+ override fun generateSharedSecretToByteArrayBlocking(other: XDH.PublicKey): ByteArray {\n+ check(other is XdhPublicKey)\n+ return deriveSharedSecret(publicKey = other.key, privateKey = key)\n+ }\n+ }\n+}\n+\n+@OptIn(UnsafeNumber::class)\n+private fun deriveSharedSecret(","id":2323033174,"in_reply_to_id":null,"line":98,"original_commit_id":"c8488c9a2b9a24362bf1b5b7018a29e8e2286937","original_line":98,"path":"cryptography-providers/openssl3/api/src/commonMain/kotlin/algorithms/Openssl3XDH.kt","side":"RIGHT","start_line":null} +{"author":"whyoleg","body":"we should support RAW format (openSSL should support it)\nsame in other places","diff_hunk":"@@ -0,0 +1,265 @@\n+/*\n+ * Copyright (c) 2025 Oleg Yukhnevich. Use of this source code is governed by the Apache 2.0 license.\n+ */\n+\n+package dev.whyoleg.cryptography.providers.openssl3.algorithms\n+\n+import dev.whyoleg.cryptography.algorithms.*\n+import dev.whyoleg.cryptography.materials.key.*\n+import dev.whyoleg.cryptography.operations.*\n+import dev.whyoleg.cryptography.providers.base.*\n+import dev.whyoleg.cryptography.providers.openssl3.internal.*\n+import dev.whyoleg.cryptography.providers.openssl3.internal.cinterop.*\n+import dev.whyoleg.cryptography.providers.openssl3.materials.*\n+import kotlinx.cinterop.*\n+import platform.posix.*\n+import kotlin.experimental.*\n+\n+internal object Openssl3EdDSA : EdDSA {\n+ private fun algorithmName(curve: EdDSA.Curve): String = when (curve) {\n+ EdDSA.Curve.Ed25519 -\u003e \"ED25519\"\n+ EdDSA.Curve.Ed448 -\u003e \"ED448\"\n+ }\n+\n+ override fun publicKeyDecoder(curve: EdDSA.Curve): KeyDecoder\u003cEdDSA.PublicKey.Format, EdDSA.PublicKey\u003e =\n+ object : Openssl3PublicKeyDecoder\u003cEdDSA.PublicKey.Format, EdDSA.PublicKey\u003e(algorithmName(curve)) {\n+ override fun inputType(format: EdDSA.PublicKey.Format): String = when (format) {\n+ EdDSA.PublicKey.Format.DER -\u003e \"DER\"\n+ EdDSA.PublicKey.Format.PEM -\u003e \"PEM\"\n+ EdDSA.PublicKey.Format.JWK,\n+ EdDSA.PublicKey.Format.RAW -\u003e error(\"$format format is not supported\")","id":2323039843,"in_reply_to_id":null,"line":30,"original_commit_id":"c8488c9a2b9a24362bf1b5b7018a29e8e2286937","original_line":30,"path":"cryptography-providers/openssl3/api/src/commonMain/kotlin/algorithms/Openssl3EdDSA.kt","side":"RIGHT","start_line":null} +{"author":"whyoleg","body":"we should support RAW format (openSSL should support it)","diff_hunk":"@@ -0,0 +1,114 @@\n+/*\n+ * Copyright (c) 2025 Oleg Yukhnevich. Use of this source code is governed by the Apache 2.0 license.\n+ */\n+\n+package dev.whyoleg.cryptography.providers.openssl3.algorithms\n+\n+import dev.whyoleg.cryptography.algorithms.*\n+import dev.whyoleg.cryptography.materials.key.*\n+import dev.whyoleg.cryptography.operations.*\n+import dev.whyoleg.cryptography.providers.base.*\n+import dev.whyoleg.cryptography.providers.openssl3.internal.*\n+import dev.whyoleg.cryptography.providers.openssl3.internal.cinterop.*\n+import dev.whyoleg.cryptography.providers.openssl3.materials.*\n+import kotlinx.cinterop.*\n+import platform.posix.*\n+import kotlin.experimental.*\n+\n+internal object Openssl3XDH : XDH {\n+ private fun algorithmName(curve: XDH.Curve): String = when (curve) {\n+ XDH.Curve.X25519 -\u003e \"X25519\"\n+ XDH.Curve.X448 -\u003e \"X448\"\n+ }\n+\n+ override fun publicKeyDecoder(curve: XDH.Curve): KeyDecoder\u003cXDH.PublicKey.Format, XDH.PublicKey\u003e =\n+ object : Openssl3PublicKeyDecoder\u003cXDH.PublicKey.Format, XDH.PublicKey\u003e(algorithmName(curve)) {\n+ override fun inputType(format: XDH.PublicKey.Format): String = when (format) {\n+ XDH.PublicKey.Format.DER -\u003e \"DER\"\n+ XDH.PublicKey.Format.PEM -\u003e \"PEM\"\n+ XDH.PublicKey.Format.JWK,\n+ XDH.PublicKey.Format.RAW -\u003e error(\"$format format is not supported\")\n+ }\n+\n+ override fun wrapKey(key: CPointer\u003cEVP_PKEY\u003e): XDH.PublicKey = XdhPublicKey(key)\n+ }\n+\n+ override fun privateKeyDecoder(curve: XDH.Curve): KeyDecoder\u003cXDH.PrivateKey.Format, XDH.PrivateKey\u003e =\n+ object : Openssl3PrivateKeyDecoder\u003cXDH.PrivateKey.Format, XDH.PrivateKey\u003e(algorithmName(curve)) {\n+ override fun inputType(format: XDH.PrivateKey.Format): String = when (format) {\n+ XDH.PrivateKey.Format.DER -\u003e \"DER\"\n+ XDH.PrivateKey.Format.PEM -\u003e \"PEM\"\n+ XDH.PrivateKey.Format.JWK,\n+ XDH.PrivateKey.Format.RAW -\u003e error(\"$format format is not supported\")\n+ }\n+\n+ override fun wrapKey(key: CPointer\u003cEVP_PKEY\u003e): XDH.PrivateKey = XdhPrivateKey(key)\n+ }\n+\n+ override fun keyPairGenerator(curve: XDH.Curve): KeyGenerator\u003cXDH.KeyPair\u003e =\n+ object : Openssl3KeyPairGenerator\u003cXDH.KeyPair\u003e(algorithmName(curve)) {\n+ override fun MemScope.createParams(): CValuesRef\u003cOSSL_PARAM\u003e? = null\n+ override fun wrapKeyPair(keyPair: CPointer\u003cEVP_PKEY\u003e): XDH.KeyPair = XdhKeyPair(\n+ publicKey = XdhPublicKey(keyPair),\n+ privateKey = XdhPrivateKey(keyPair)\n+ )\n+ }\n+\n+ private class XdhKeyPair(\n+ override val publicKey: XDH.PublicKey,\n+ override val privateKey: XDH.PrivateKey,\n+ ) : XDH.KeyPair\n+\n+ private class XdhPublicKey(\n+ key: CPointer\u003cEVP_PKEY\u003e,\n+ ) : XDH.PublicKey, Openssl3PublicKeyEncodable\u003cXDH.PublicKey.Format\u003e(key), SharedSecretGenerator\u003cXDH.PrivateKey\u003e {\n+ override fun outputType(format: XDH.PublicKey.Format): String = when (format) {\n+ XDH.PublicKey.Format.DER -\u003e \"DER\"\n+ XDH.PublicKey.Format.PEM -\u003e \"PEM\"\n+ XDH.PublicKey.Format.JWK,\n+ XDH.PublicKey.Format.RAW -\u003e error(\"$format format is not supported\")","id":2323041111,"in_reply_to_id":null,"line":69,"original_commit_id":"c8488c9a2b9a24362bf1b5b7018a29e8e2286937","original_line":69,"path":"cryptography-providers/openssl3/api/src/commonMain/kotlin/algorithms/Openssl3XDH.kt","side":"RIGHT","start_line":null} +{"author":"whyoleg","body":"we should support RAW format (openSSL should support it)","diff_hunk":"@@ -0,0 +1,114 @@\n+/*\n+ * Copyright (c) 2025 Oleg Yukhnevich. Use of this source code is governed by the Apache 2.0 license.\n+ */\n+\n+package dev.whyoleg.cryptography.providers.openssl3.algorithms\n+\n+import dev.whyoleg.cryptography.algorithms.*\n+import dev.whyoleg.cryptography.materials.key.*\n+import dev.whyoleg.cryptography.operations.*\n+import dev.whyoleg.cryptography.providers.base.*\n+import dev.whyoleg.cryptography.providers.openssl3.internal.*\n+import dev.whyoleg.cryptography.providers.openssl3.internal.cinterop.*\n+import dev.whyoleg.cryptography.providers.openssl3.materials.*\n+import kotlinx.cinterop.*\n+import platform.posix.*\n+import kotlin.experimental.*\n+\n+internal object Openssl3XDH : XDH {\n+ private fun algorithmName(curve: XDH.Curve): String = when (curve) {\n+ XDH.Curve.X25519 -\u003e \"X25519\"\n+ XDH.Curve.X448 -\u003e \"X448\"\n+ }\n+\n+ override fun publicKeyDecoder(curve: XDH.Curve): KeyDecoder\u003cXDH.PublicKey.Format, XDH.PublicKey\u003e =\n+ object : Openssl3PublicKeyDecoder\u003cXDH.PublicKey.Format, XDH.PublicKey\u003e(algorithmName(curve)) {\n+ override fun inputType(format: XDH.PublicKey.Format): String = when (format) {\n+ XDH.PublicKey.Format.DER -\u003e \"DER\"\n+ XDH.PublicKey.Format.PEM -\u003e \"PEM\"\n+ XDH.PublicKey.Format.JWK,\n+ XDH.PublicKey.Format.RAW -\u003e error(\"$format format is not supported\")\n+ }\n+\n+ override fun wrapKey(key: CPointer\u003cEVP_PKEY\u003e): XDH.PublicKey = XdhPublicKey(key)\n+ }\n+\n+ override fun privateKeyDecoder(curve: XDH.Curve): KeyDecoder\u003cXDH.PrivateKey.Format, XDH.PrivateKey\u003e =\n+ object : Openssl3PrivateKeyDecoder\u003cXDH.PrivateKey.Format, XDH.PrivateKey\u003e(algorithmName(curve)) {\n+ override fun inputType(format: XDH.PrivateKey.Format): String = when (format) {\n+ XDH.PrivateKey.Format.DER -\u003e \"DER\"\n+ XDH.PrivateKey.Format.PEM -\u003e \"PEM\"\n+ XDH.PrivateKey.Format.JWK,\n+ XDH.PrivateKey.Format.RAW -\u003e error(\"$format format is not supported\")\n+ }\n+\n+ override fun wrapKey(key: CPointer\u003cEVP_PKEY\u003e): XDH.PrivateKey = XdhPrivateKey(key)\n+ }\n+\n+ override fun keyPairGenerator(curve: XDH.Curve): KeyGenerator\u003cXDH.KeyPair\u003e =\n+ object : Openssl3KeyPairGenerator\u003cXDH.KeyPair\u003e(algorithmName(curve)) {\n+ override fun MemScope.createParams(): CValuesRef\u003cOSSL_PARAM\u003e? = null\n+ override fun wrapKeyPair(keyPair: CPointer\u003cEVP_PKEY\u003e): XDH.KeyPair = XdhKeyPair(\n+ publicKey = XdhPublicKey(keyPair),\n+ privateKey = XdhPrivateKey(keyPair)\n+ )\n+ }\n+\n+ private class XdhKeyPair(\n+ override val publicKey: XDH.PublicKey,\n+ override val privateKey: XDH.PrivateKey,\n+ ) : XDH.KeyPair\n+\n+ private class XdhPublicKey(\n+ key: CPointer\u003cEVP_PKEY\u003e,\n+ ) : XDH.PublicKey, Openssl3PublicKeyEncodable\u003cXDH.PublicKey.Format\u003e(key), SharedSecretGenerator\u003cXDH.PrivateKey\u003e {\n+ override fun outputType(format: XDH.PublicKey.Format): String = when (format) {\n+ XDH.PublicKey.Format.DER -\u003e \"DER\"\n+ XDH.PublicKey.Format.PEM -\u003e \"PEM\"\n+ XDH.PublicKey.Format.JWK,\n+ XDH.PublicKey.Format.RAW -\u003e error(\"$format format is not supported\")\n+ }\n+\n+ override fun sharedSecretGenerator(): SharedSecretGenerator\u003cXDH.PrivateKey\u003e = this\n+ override fun generateSharedSecretToByteArrayBlocking(other: XDH.PrivateKey): ByteArray {\n+ check(other is XdhPrivateKey)\n+ return deriveSharedSecret(publicKey = key, privateKey = other.key)\n+ }\n+ }\n+\n+ private class XdhPrivateKey(\n+ key: CPointer\u003cEVP_PKEY\u003e,\n+ ) : XDH.PrivateKey, Openssl3PrivateKeyEncodable\u003cXDH.PrivateKey.Format\u003e(key), SharedSecretGenerator\u003cXDH.PublicKey\u003e {\n+ override fun outputType(format: XDH.PrivateKey.Format): String = when (format) {\n+ XDH.PrivateKey.Format.DER -\u003e \"DER\"\n+ XDH.PrivateKey.Format.PEM -\u003e \"PEM\"\n+ XDH.PrivateKey.Format.JWK,\n+ XDH.PrivateKey.Format.RAW -\u003e error(\"$format format is not supported\")","id":2323041303,"in_reply_to_id":null,"line":86,"original_commit_id":"c8488c9a2b9a24362bf1b5b7018a29e8e2286937","original_line":86,"path":"cryptography-providers/openssl3/api/src/commonMain/kotlin/algorithms/Openssl3XDH.kt","side":"RIGHT","start_line":null} +{"author":"whyoleg","body":"what is the reason for reimplementing this from scratch? It looks like we only need to make `hashAlgorithm` nullable in `Openssl3DigestSignatureVerifier` and `Openssl3DigestSignatureGenerator` no?","diff_hunk":"@@ -0,0 +1,265 @@\n+/*\n+ * Copyright (c) 2025 Oleg Yukhnevich. Use of this source code is governed by the Apache 2.0 license.\n+ */\n+\n+package dev.whyoleg.cryptography.providers.openssl3.algorithms\n+\n+import dev.whyoleg.cryptography.algorithms.*\n+import dev.whyoleg.cryptography.materials.key.*\n+import dev.whyoleg.cryptography.operations.*\n+import dev.whyoleg.cryptography.providers.base.*\n+import dev.whyoleg.cryptography.providers.openssl3.internal.*\n+import dev.whyoleg.cryptography.providers.openssl3.internal.cinterop.*\n+import dev.whyoleg.cryptography.providers.openssl3.materials.*\n+import kotlinx.cinterop.*\n+import platform.posix.*\n+import kotlin.experimental.*\n+\n+internal object Openssl3EdDSA : EdDSA {\n+ private fun algorithmName(curve: EdDSA.Curve): String = when (curve) {\n+ EdDSA.Curve.Ed25519 -\u003e \"ED25519\"\n+ EdDSA.Curve.Ed448 -\u003e \"ED448\"\n+ }\n+\n+ override fun publicKeyDecoder(curve: EdDSA.Curve): KeyDecoder\u003cEdDSA.PublicKey.Format, EdDSA.PublicKey\u003e =\n+ object : Openssl3PublicKeyDecoder\u003cEdDSA.PublicKey.Format, EdDSA.PublicKey\u003e(algorithmName(curve)) {\n+ override fun inputType(format: EdDSA.PublicKey.Format): String = when (format) {\n+ EdDSA.PublicKey.Format.DER -\u003e \"DER\"\n+ EdDSA.PublicKey.Format.PEM -\u003e \"PEM\"\n+ EdDSA.PublicKey.Format.JWK,\n+ EdDSA.PublicKey.Format.RAW -\u003e error(\"$format format is not supported\")\n+ }\n+\n+ override fun wrapKey(key: CPointer\u003cEVP_PKEY\u003e): EdDSA.PublicKey = EdDsaPublicKey(key)\n+ }\n+\n+ override fun privateKeyDecoder(curve: EdDSA.Curve): KeyDecoder\u003cEdDSA.PrivateKey.Format, EdDSA.PrivateKey\u003e =\n+ object : Openssl3PrivateKeyDecoder\u003cEdDSA.PrivateKey.Format, EdDSA.PrivateKey\u003e(algorithmName(curve)) {\n+ override fun inputType(format: EdDSA.PrivateKey.Format): String = when (format) {\n+ EdDSA.PrivateKey.Format.DER -\u003e \"DER\"\n+ EdDSA.PrivateKey.Format.PEM -\u003e \"PEM\"\n+ EdDSA.PrivateKey.Format.JWK,\n+ EdDSA.PrivateKey.Format.RAW -\u003e error(\"$format format is not supported\")\n+ }\n+\n+ override fun wrapKey(key: CPointer\u003cEVP_PKEY\u003e): EdDSA.PrivateKey = EdDsaPrivateKey(key)\n+ }\n+\n+ override fun keyPairGenerator(curve: EdDSA.Curve): KeyGenerator\u003cEdDSA.KeyPair\u003e =\n+ object : Openssl3KeyPairGenerator\u003cEdDSA.KeyPair\u003e(algorithmName(curve)) {\n+ override fun MemScope.createParams(): CValuesRef\u003cOSSL_PARAM\u003e? = null\n+ override fun wrapKeyPair(keyPair: CPointer\u003cEVP_PKEY\u003e): EdDSA.KeyPair = EdDsaKeyPair(\n+ publicKey = EdDsaPublicKey(keyPair),\n+ privateKey = EdDsaPrivateKey(keyPair)\n+ )\n+ }\n+\n+ private class EdDsaKeyPair(\n+ override val publicKey: EdDSA.PublicKey,\n+ override val privateKey: EdDSA.PrivateKey,\n+ ) : EdDSA.KeyPair\n+\n+ private class EdDsaPublicKey(\n+ key: CPointer\u003cEVP_PKEY\u003e,\n+ ) : EdDSA.PublicKey, Openssl3PublicKeyEncodable\u003cEdDSA.PublicKey.Format\u003e(key) {\n+ override fun outputType(format: EdDSA.PublicKey.Format): String = when (format) {\n+ EdDSA.PublicKey.Format.DER -\u003e \"DER\"\n+ EdDSA.PublicKey.Format.PEM -\u003e \"PEM\"\n+ EdDSA.PublicKey.Format.JWK,\n+ EdDSA.PublicKey.Format.RAW -\u003e error(\"$format format is not supported\")\n+ }\n+\n+ override fun signatureVerifier(): SignatureVerifier = EdDsaSignatureVerifier(key)\n+ }\n+\n+ private class EdDsaPrivateKey(\n+ key: CPointer\u003cEVP_PKEY\u003e,\n+ ) : EdDSA.PrivateKey, Openssl3PrivateKeyEncodable\u003cEdDSA.PrivateKey.Format\u003e(key) {\n+ override fun outputType(format: EdDSA.PrivateKey.Format): String = when (format) {\n+ EdDSA.PrivateKey.Format.DER -\u003e \"DER\"\n+ EdDSA.PrivateKey.Format.PEM -\u003e \"PEM\"\n+ EdDSA.PrivateKey.Format.JWK,\n+ EdDSA.PrivateKey.Format.RAW -\u003e error(\"$format format is not supported\")\n+ }\n+\n+ override fun signatureGenerator(): SignatureGenerator = EdDsaSignatureGenerator(key)\n+ }\n+}\n+\n+@OptIn(ExperimentalNativeApi::class, UnsafeNumber::class)\n+private class EdDsaSignatureGenerator(","id":2323048552,"in_reply_to_id":null,"line":90,"original_commit_id":"c8488c9a2b9a24362bf1b5b7018a29e8e2286937","original_line":90,"path":"cryptography-providers/openssl3/api/src/commonMain/kotlin/algorithms/Openssl3EdDSA.kt","side":"RIGHT","start_line":null} +{"author":"whyoleg","body":"why can't we support it? (same in other places in JDK provider)","diff_hunk":"@@ -0,0 +1,89 @@\n+package dev.whyoleg.cryptography.providers.jdk.algorithms\n+\n+import dev.whyoleg.cryptography.algorithms.*\n+import dev.whyoleg.cryptography.materials.key.*\n+import dev.whyoleg.cryptography.operations.*\n+import dev.whyoleg.cryptography.providers.jdk.*\n+import dev.whyoleg.cryptography.providers.jdk.materials.*\n+import dev.whyoleg.cryptography.providers.base.materials.*\n+import dev.whyoleg.cryptography.providers.jdk.operations.*\n+import dev.whyoleg.cryptography.serialization.pem.*\n+\n+internal class JdkEdDSA(private val state: JdkCryptographyState) : EdDSA {\n+ private fun curveName(curve: EdDSA.Curve): String = when (curve) {\n+ EdDSA.Curve.Ed25519 -\u003e \"Ed25519\"\n+ EdDSA.Curve.Ed448 -\u003e \"Ed448\"\n+ }\n+\n+ override fun publicKeyDecoder(curve: EdDSA.Curve): KeyDecoder\u003cEdDSA.PublicKey.Format, EdDSA.PublicKey\u003e =\n+ object : JdkPublicKeyDecoder\u003cEdDSA.PublicKey.Format, EdDSA.PublicKey\u003e(state, curveName(curve)) {\n+ override fun JPublicKey.convert(): EdDSA.PublicKey = EdDsaPublicKey(state, this)\n+\n+ override fun decodeFromByteArrayBlocking(format: EdDSA.PublicKey.Format, bytes: ByteArray): EdDSA.PublicKey = when (format) {\n+ EdDSA.PublicKey.Format.JWK -\u003e error(\"JWK is not supported\")\n+ EdDSA.PublicKey.Format.RAW -\u003e TODO(\"RAW encoding is not supported yet\")","id":2323050460,"in_reply_to_id":null,"line":24,"original_commit_id":"c8488c9a2b9a24362bf1b5b7018a29e8e2286937","original_line":24,"path":"cryptography-providers/jdk/src/jvmMain/kotlin/algorithms/JdkEdDSA.kt","side":"RIGHT","start_line":null} +{"author":"whyoleg","body":"OIDs should be extracted and added into `asn1-modules` with correct names (same for xdh)","diff_hunk":"@@ -0,0 +1,182 @@\n+/*\n+ * Copyright (c) 2025 Oleg Yukhnevich. Use of this source code is governed by the Apache 2.0 license.\n+ */\n+\n+package dev.whyoleg.cryptography.providers.cryptokit.algorithms\n+\n+import dev.whyoleg.cryptography.algorithms.*\n+import dev.whyoleg.cryptography.materials.key.*\n+import dev.whyoleg.cryptography.operations.*\n+import dev.whyoleg.cryptography.providers.cryptokit.internal.*\n+import dev.whyoleg.cryptography.providers.cryptokit.internal.swiftinterop.*\n+import dev.whyoleg.cryptography.providers.base.*\n+import dev.whyoleg.cryptography.providers.base.materials.*\n+import dev.whyoleg.cryptography.serialization.asn1.*\n+import dev.whyoleg.cryptography.serialization.asn1.modules.*\n+import dev.whyoleg.cryptography.serialization.pem.*\n+import kotlinx.cinterop.*\n+import platform.Foundation.*\n+\n+internal object CryptoKitEdDSA : EdDSA {\n+ override fun publicKeyDecoder(curve: EdDSA.Curve): KeyDecoder\u003cEdDSA.PublicKey.Format, EdDSA.PublicKey\u003e {\n+ check(curve == EdDSA.Curve.Ed25519) { \"CryptoKit supports Ed25519 only\" }\n+ return object : KeyDecoder\u003cEdDSA.PublicKey.Format, EdDSA.PublicKey\u003e {\n+ override fun decodeFromByteArrayBlocking(format: EdDSA.PublicKey.Format, bytes: ByteArray): EdDSA.PublicKey = when (format) {\n+ EdDSA.PublicKey.Format.RAW -\u003e EdPublic(\n+ swiftTry\u003cSwiftEdDsaPublicKey\u003e { error -\u003e bytes.useNSData { SwiftEdDsaPublicKey.decodeRawWithRawRepresentation(it, error) } }\n+ )\n+ EdDSA.PublicKey.Format.DER -\u003e {\n+ val raw = unwrapSubjectPublicKeyInfo(ObjectIdentifier(\"1.3.101.112\"), bytes)","id":2323056088,"in_reply_to_id":null,"line":29,"original_commit_id":"c8488c9a2b9a24362bf1b5b7018a29e8e2286937","original_line":29,"path":"cryptography-providers/cryptokit/src/commonMain/kotlin/algorithms/CryptoKitEdDSA.kt","side":"RIGHT","start_line":null} +{"author":"whyoleg","body":"it would be nice to add this into `cryptokit.md`: that only this curve is supported (same for xdh) ","diff_hunk":"@@ -0,0 +1,182 @@\n+/*\n+ * Copyright (c) 2025 Oleg Yukhnevich. Use of this source code is governed by the Apache 2.0 license.\n+ */\n+\n+package dev.whyoleg.cryptography.providers.cryptokit.algorithms\n+\n+import dev.whyoleg.cryptography.algorithms.*\n+import dev.whyoleg.cryptography.materials.key.*\n+import dev.whyoleg.cryptography.operations.*\n+import dev.whyoleg.cryptography.providers.cryptokit.internal.*\n+import dev.whyoleg.cryptography.providers.cryptokit.internal.swiftinterop.*\n+import dev.whyoleg.cryptography.providers.base.*\n+import dev.whyoleg.cryptography.providers.base.materials.*\n+import dev.whyoleg.cryptography.serialization.asn1.*\n+import dev.whyoleg.cryptography.serialization.asn1.modules.*\n+import dev.whyoleg.cryptography.serialization.pem.*\n+import kotlinx.cinterop.*\n+import platform.Foundation.*\n+\n+internal object CryptoKitEdDSA : EdDSA {\n+ override fun publicKeyDecoder(curve: EdDSA.Curve): KeyDecoder\u003cEdDSA.PublicKey.Format, EdDSA.PublicKey\u003e {\n+ check(curve == EdDSA.Curve.Ed25519) { \"CryptoKit supports Ed25519 only\" }","id":2323060834,"in_reply_to_id":null,"line":22,"original_commit_id":"c8488c9a2b9a24362bf1b5b7018a29e8e2286937","original_line":22,"path":"cryptography-providers/cryptokit/src/commonMain/kotlin/algorithms/CryptoKitEdDSA.kt","side":"RIGHT","start_line":null} +{"author":"whyoleg","body":"should be removed","diff_hunk":"@@ -0,0 +1,130 @@\n+/*\n+ * Copyright (c) 2025 Oleg Yukhnevich. Use of this source code is governed by the Apache 2.0 license.\n+ */\n+\n+package dev.whyoleg.cryptography.providers.cryptokit.algorithms\n+\n+import dev.whyoleg.cryptography.algorithms.*\n+import dev.whyoleg.cryptography.materials.key.*\n+import dev.whyoleg.cryptography.operations.*\n+import dev.whyoleg.cryptography.providers.cryptokit.internal.*\n+import dev.whyoleg.cryptography.providers.cryptokit.internal.swiftinterop.*\n+import dev.whyoleg.cryptography.providers.base.*\n+import dev.whyoleg.cryptography.providers.base.materials.*\n+import dev.whyoleg.cryptography.serialization.asn1.*\n+import dev.whyoleg.cryptography.serialization.asn1.modules.*\n+import dev.whyoleg.cryptography.serialization.pem.*\n+import kotlinx.cinterop.*\n+import platform.Foundation.*\n+\n+internal object CryptoKitXDH : XDH {\n+ override fun publicKeyDecoder(curve: XDH.Curve): KeyDecoder\u003cXDH.PublicKey.Format, XDH.PublicKey\u003e {\n+ check(curve == XDH.Curve.X25519) { \"CryptoKit supports X25519 only\" }\n+ return object : KeyDecoder\u003cXDH.PublicKey.Format, XDH.PublicKey\u003e {\n+ override fun decodeFromByteArrayBlocking(format: XDH.PublicKey.Format, bytes: ByteArray): XDH.PublicKey = when (format) {\n+ XDH.PublicKey.Format.RAW -\u003e XPublic(\n+ swiftTry\u003cSwiftXdhPublicKey\u003e { error -\u003e bytes.useNSData { SwiftXdhPublicKey.decodeRawWithRawRepresentation(it, error) } }\n+ )\n+ XDH.PublicKey.Format.DER -\u003e {\n+ val raw = unwrapSubjectPublicKeyInfo(ObjectIdentifier(\"1.3.101.110\"), bytes)\n+ XPublic(swiftTry { error -\u003e raw.useNSData { SwiftXdhPublicKey.decodeRawWithRawRepresentation(it, error) } })\n+ }\n+ XDH.PublicKey.Format.PEM -\u003e {\n+ val der = unwrapPem(PemLabel.PublicKey, bytes)\n+ val raw = unwrapSubjectPublicKeyInfo(ObjectIdentifier(\"1.3.101.110\"), der)\n+ XPublic(swiftTry { error -\u003e raw.useNSData { SwiftXdhPublicKey.decodeRawWithRawRepresentation(it, error) } })\n+ }\n+ else -\u003e error(\"$format is not supported by CryptoKit XDH\")\n+ }\n+ }\n+ }\n+\n+ override fun privateKeyDecoder(curve: XDH.Curve): KeyDecoder\u003cXDH.PrivateKey.Format, XDH.PrivateKey\u003e {\n+ check(curve == XDH.Curve.X25519) { \"CryptoKit supports X25519 only\" }\n+ return object : KeyDecoder\u003cXDH.PrivateKey.Format, XDH.PrivateKey\u003e {\n+ override fun decodeFromByteArrayBlocking(format: XDH.PrivateKey.Format, bytes: ByteArray): XDH.PrivateKey = when (format) {\n+ XDH.PrivateKey.Format.RAW -\u003e XPrivate(\n+ swiftTry\u003cSwiftXdhPrivateKey\u003e { error -\u003e bytes.useNSData { SwiftXdhPrivateKey.decodeRawWithRawRepresentation(it, error) } }\n+ )\n+ XDH.PrivateKey.Format.DER -\u003e {\n+ val raw = unwrapPrivateKeyInfo(ObjectIdentifier(\"1.3.101.110\"), bytes)\n+ XPrivate(swiftTry { error -\u003e raw.useNSData { SwiftXdhPrivateKey.decodeRawWithRawRepresentation(it, error) } })\n+ }\n+ XDH.PrivateKey.Format.PEM -\u003e {\n+ val der = unwrapPem(PemLabel.PrivateKey, bytes)\n+ val raw = unwrapPrivateKeyInfo(ObjectIdentifier(\"1.3.101.110\"), der)\n+ XPrivate(swiftTry { error -\u003e raw.useNSData { SwiftXdhPrivateKey.decodeRawWithRawRepresentation(it, error) } })\n+ }\n+ else -\u003e error(\"$format is not supported by CryptoKit XDH\")\n+ }\n+ }\n+ }\n+\n+ override fun keyPairGenerator(curve: XDH.Curve): KeyGenerator\u003cXDH.KeyPair\u003e {\n+ check(curve == XDH.Curve.X25519) { \"CryptoKit supports X25519 only\" }\n+ return object : KeyGenerator\u003cXDH.KeyPair\u003e {\n+ override fun generateKeyBlocking(): XDH.KeyPair {\n+ val p = SwiftXdhPrivateKey.generate()\n+ return XKeyPair(XPublic(p.publicKey()), XPrivate(p))\n+ }\n+ }\n+ }\n+\n+ private class XKeyPair(\n+ override val publicKey: XDH.PublicKey,\n+ override val privateKey: XDH.PrivateKey,\n+ ) : XDH.KeyPair\n+\n+ private class XPublic(\n+ val key: SwiftXdhPublicKey,\n+ ) : XDH.PublicKey, SharedSecretGenerator\u003cXDH.PrivateKey\u003e {\n+ override fun encodeToByteArrayBlocking(format: XDH.PublicKey.Format): ByteArray = when (format) {\n+ XDH.PublicKey.Format.RAW -\u003e key.rawRepresentation().toByteArray()\n+ XDH.PublicKey.Format.DER -\u003e wrapSubjectPublicKeyInfo(\n+ UnknownKeyAlgorithmIdentifier(ObjectIdentifier(\"1.3.101.110\")),\n+ key.rawRepresentation().toByteArray()\n+ )\n+ XDH.PublicKey.Format.PEM -\u003e wrapPem(\n+ PemLabel.PublicKey,\n+ wrapSubjectPublicKeyInfo(\n+ UnknownKeyAlgorithmIdentifier(ObjectIdentifier(\"1.3.101.110\")),\n+ key.rawRepresentation().toByteArray()\n+ )\n+ )\n+ else -\u003e error(\"$format is not supported by CryptoKit XDH\")\n+ }\n+ override fun sharedSecretGenerator(): SharedSecretGenerator\u003cXDH.PrivateKey\u003e = this\n+ override fun generateSharedSecretToByteArrayBlocking(other: XDH.PrivateKey): ByteArray {\n+ require(other is XPrivate)\n+ return swiftTry { error -\u003e other.key.deriveSecretWith(key, error) }.toByteArray()\n+ }\n+ }\n+\n+ private class XPrivate(\n+ val key: SwiftXdhPrivateKey,\n+ ) : XDH.PrivateKey, SharedSecretGenerator\u003cXDH.PublicKey\u003e {\n+ override fun encodeToByteArrayBlocking(format: XDH.PrivateKey.Format): ByteArray = when (format) {\n+ XDH.PrivateKey.Format.RAW -\u003e key.rawRepresentation().toByteArray()\n+ XDH.PrivateKey.Format.DER -\u003e wrapPrivateKeyInfo(\n+ 0,\n+ UnknownKeyAlgorithmIdentifier(ObjectIdentifier(\"1.3.101.110\")),\n+ key.rawRepresentation().toByteArray()\n+ )\n+ XDH.PrivateKey.Format.PEM -\u003e wrapPem(\n+ PemLabel.PrivateKey,\n+ wrapPrivateKeyInfo(\n+ 0,\n+ UnknownKeyAlgorithmIdentifier(ObjectIdentifier(\"1.3.101.110\")),\n+ key.rawRepresentation().toByteArray()\n+ )\n+ )\n+ else -\u003e error(\"$format is not supported by CryptoKit XDH\")\n+ }\n+ override fun sharedSecretGenerator(): SharedSecretGenerator\u003cXDH.PublicKey\u003e = this\n+ override fun generateSharedSecretToByteArrayBlocking(other: XDH.PublicKey): ByteArray {\n+ require(other is XPublic)\n+ return swiftTry { error -\u003e key.deriveSecretWith(other.key, error) }.toByteArray()\n+ }\n+ }\n+}\n+// NSData helpers provided by cryptography-providers-base","id":2323064442,"in_reply_to_id":null,"line":130,"original_commit_id":"c8488c9a2b9a24362bf1b5b7018a29e8e2286937","original_line":130,"path":"cryptography-providers/cryptokit/src/commonMain/kotlin/algorithms/CryptoKitXDH.kt","side":"RIGHT","start_line":null} +{"author":"whyoleg","body":"The implementation looks similar to `SecSignFunction` from apple provider. It would be nice to extract the base implementation into `AccumulatingSignFunction` and place it into `provider-base`, so that it will be shared in the same way `AccumulatingCipherFunction` is shared and used in this cryptokit provider\nSame for `verify`","diff_hunk":"@@ -0,0 +1,182 @@\n+/*\n+ * Copyright (c) 2025 Oleg Yukhnevich. Use of this source code is governed by the Apache 2.0 license.\n+ */\n+\n+package dev.whyoleg.cryptography.providers.cryptokit.algorithms\n+\n+import dev.whyoleg.cryptography.algorithms.*\n+import dev.whyoleg.cryptography.materials.key.*\n+import dev.whyoleg.cryptography.operations.*\n+import dev.whyoleg.cryptography.providers.cryptokit.internal.*\n+import dev.whyoleg.cryptography.providers.cryptokit.internal.swiftinterop.*\n+import dev.whyoleg.cryptography.providers.base.*\n+import dev.whyoleg.cryptography.providers.base.materials.*\n+import dev.whyoleg.cryptography.serialization.asn1.*\n+import dev.whyoleg.cryptography.serialization.asn1.modules.*\n+import dev.whyoleg.cryptography.serialization.pem.*\n+import kotlinx.cinterop.*\n+import platform.Foundation.*\n+\n+internal object CryptoKitEdDSA : EdDSA {\n+ override fun publicKeyDecoder(curve: EdDSA.Curve): KeyDecoder\u003cEdDSA.PublicKey.Format, EdDSA.PublicKey\u003e {\n+ check(curve == EdDSA.Curve.Ed25519) { \"CryptoKit supports Ed25519 only\" }\n+ return object : KeyDecoder\u003cEdDSA.PublicKey.Format, EdDSA.PublicKey\u003e {\n+ override fun decodeFromByteArrayBlocking(format: EdDSA.PublicKey.Format, bytes: ByteArray): EdDSA.PublicKey = when (format) {\n+ EdDSA.PublicKey.Format.RAW -\u003e EdPublic(\n+ swiftTry\u003cSwiftEdDsaPublicKey\u003e { error -\u003e bytes.useNSData { SwiftEdDsaPublicKey.decodeRawWithRawRepresentation(it, error) } }\n+ )\n+ EdDSA.PublicKey.Format.DER -\u003e {\n+ val raw = unwrapSubjectPublicKeyInfo(ObjectIdentifier(\"1.3.101.112\"), bytes)\n+ EdPublic(swiftTry { error -\u003e raw.useNSData { SwiftEdDsaPublicKey.decodeRawWithRawRepresentation(it, error) } })\n+ }\n+ EdDSA.PublicKey.Format.PEM -\u003e {\n+ val der = unwrapPem(PemLabel.PublicKey, bytes)\n+ val raw = unwrapSubjectPublicKeyInfo(ObjectIdentifier(\"1.3.101.112\"), der)\n+ EdPublic(swiftTry { error -\u003e raw.useNSData { SwiftEdDsaPublicKey.decodeRawWithRawRepresentation(it, error) } })\n+ }\n+ else -\u003e error(\"$format is not supported by CryptoKit EdDSA\")\n+ }\n+ }\n+ }\n+\n+ override fun privateKeyDecoder(curve: EdDSA.Curve): KeyDecoder\u003cEdDSA.PrivateKey.Format, EdDSA.PrivateKey\u003e {\n+ check(curve == EdDSA.Curve.Ed25519) { \"CryptoKit supports Ed25519 only\" }\n+ return object : KeyDecoder\u003cEdDSA.PrivateKey.Format, EdDSA.PrivateKey\u003e {\n+ override fun decodeFromByteArrayBlocking(format: EdDSA.PrivateKey.Format, bytes: ByteArray): EdDSA.PrivateKey = when (format) {\n+ EdDSA.PrivateKey.Format.RAW -\u003e EdPrivate(\n+ swiftTry\u003cSwiftEdDsaPrivateKey\u003e { error -\u003e bytes.useNSData { SwiftEdDsaPrivateKey.decodeRawWithRawRepresentation(it, error) } }\n+ )\n+ EdDSA.PrivateKey.Format.DER -\u003e {\n+ val raw = unwrapPrivateKeyInfo(ObjectIdentifier(\"1.3.101.112\"), bytes)\n+ EdPrivate(swiftTry { error -\u003e raw.useNSData { SwiftEdDsaPrivateKey.decodeRawWithRawRepresentation(it, error) } })\n+ }\n+ EdDSA.PrivateKey.Format.PEM -\u003e {\n+ val der = unwrapPem(PemLabel.PrivateKey, bytes)\n+ val raw = unwrapPrivateKeyInfo(ObjectIdentifier(\"1.3.101.112\"), der)\n+ EdPrivate(swiftTry { error -\u003e raw.useNSData { SwiftEdDsaPrivateKey.decodeRawWithRawRepresentation(it, error) } })\n+ }\n+ else -\u003e error(\"$format is not supported by CryptoKit EdDSA\")\n+ }\n+ }\n+ }\n+\n+ override fun keyPairGenerator(curve: EdDSA.Curve): KeyGenerator\u003cEdDSA.KeyPair\u003e {\n+ check(curve == EdDSA.Curve.Ed25519) { \"CryptoKit supports Ed25519 only\" }\n+ return object : KeyGenerator\u003cEdDSA.KeyPair\u003e {\n+ override fun generateKeyBlocking(): EdDSA.KeyPair {\n+ val p = SwiftEdDsaPrivateKey.generate()\n+ return EdKeyPair(EdPublic(p.publicKey()), EdPrivate(p))\n+ }\n+ }\n+ }\n+\n+ private class EdKeyPair(\n+ override val publicKey: EdDSA.PublicKey,\n+ override val privateKey: EdDSA.PrivateKey,\n+ ) : EdDSA.KeyPair\n+\n+ private class EdPublic(\n+ val key: SwiftEdDsaPublicKey,\n+ ) : EdDSA.PublicKey {\n+ override fun encodeToByteArrayBlocking(format: EdDSA.PublicKey.Format): ByteArray = when (format) {\n+ EdDSA.PublicKey.Format.RAW -\u003e key.rawRepresentation().toByteArray()\n+ EdDSA.PublicKey.Format.DER -\u003e wrapSubjectPublicKeyInfo(\n+ UnknownKeyAlgorithmIdentifier(ObjectIdentifier(\"1.3.101.112\")),\n+ key.rawRepresentation().toByteArray()\n+ )\n+ EdDSA.PublicKey.Format.PEM -\u003e wrapPem(\n+ PemLabel.PublicKey,\n+ wrapSubjectPublicKeyInfo(\n+ UnknownKeyAlgorithmIdentifier(ObjectIdentifier(\"1.3.101.112\")),\n+ key.rawRepresentation().toByteArray()\n+ )\n+ )\n+ else -\u003e error(\"$format is not supported by CryptoKit EdDSA\")\n+ }\n+\n+ override fun signatureVerifier(): SignatureVerifier = object : SignatureVerifier {\n+ override fun createVerifyFunction(): VerifyFunction = EdVerifyFunction(key)\n+ }\n+ }\n+\n+ private class EdPrivate(\n+ val key: SwiftEdDsaPrivateKey,\n+ ) : EdDSA.PrivateKey {\n+ override fun encodeToByteArrayBlocking(format: EdDSA.PrivateKey.Format): ByteArray = when (format) {\n+ EdDSA.PrivateKey.Format.RAW -\u003e key.rawRepresentation().toByteArray()\n+ EdDSA.PrivateKey.Format.DER -\u003e wrapPrivateKeyInfo(\n+ 0,\n+ UnknownKeyAlgorithmIdentifier(ObjectIdentifier(\"1.3.101.112\")),\n+ key.rawRepresentation().toByteArray()\n+ )\n+ EdDSA.PrivateKey.Format.PEM -\u003e wrapPem(\n+ PemLabel.PrivateKey,\n+ wrapPrivateKeyInfo(\n+ 0,\n+ UnknownKeyAlgorithmIdentifier(ObjectIdentifier(\"1.3.101.112\")),\n+ key.rawRepresentation().toByteArray()\n+ )\n+ )\n+ else -\u003e error(\"$format is not supported by CryptoKit EdDSA\")\n+ }\n+\n+ override fun signatureGenerator(): SignatureGenerator = object : SignatureGenerator {\n+ override fun createSignFunction(): SignFunction = EdSignFunction(key)\n+ override fun generateSignatureBlocking(data: ByteArray): ByteArray =\n+ swiftTry { error -\u003e data.useNSData { key.signWithMessage(it, error) } }.toByteArray()\n+ }\n+ }\n+}\n+\n+private class EdSignFunction(","id":2323074323,"in_reply_to_id":null,"line":131,"original_commit_id":"c8488c9a2b9a24362bf1b5b7018a29e8e2286937","original_line":131,"path":"cryptography-providers/cryptokit/src/commonMain/kotlin/algorithms/CryptoKitEdDSA.kt","side":"RIGHT","start_line":null} diff --git a/build-logic/src/main/kotlin/ckbuild/tests/GenerateProviderTestsTask.kt b/build-logic/src/main/kotlin/ckbuild/tests/GenerateProviderTestsTask.kt index c581f670..f8a75310 100644 --- a/build-logic/src/main/kotlin/ckbuild/tests/GenerateProviderTestsTask.kt +++ b/build-logic/src/main/kotlin/ckbuild/tests/GenerateProviderTestsTask.kt @@ -112,7 +112,9 @@ abstract class GenerateProviderTestsTask : DefaultTask() { // Edwards-family "EdDsaTest", + "EdDsaCompatibilityTest", "XdhTest", + "XdhCompatibilityTest", "RsaOaepTest", "RsaOaepCompatibilityTest", diff --git a/cryptography-providers/jdk/src/jvmMain/kotlin/algorithms/JdkEdDSA.kt b/cryptography-providers/jdk/src/jvmMain/kotlin/algorithms/JdkEdDSA.kt index 5ff2473b..7c8bf2b6 100644 --- a/cryptography-providers/jdk/src/jvmMain/kotlin/algorithms/JdkEdDSA.kt +++ b/cryptography-providers/jdk/src/jvmMain/kotlin/algorithms/JdkEdDSA.kt @@ -8,12 +8,18 @@ import dev.whyoleg.cryptography.providers.jdk.materials.* import dev.whyoleg.cryptography.providers.base.materials.* import dev.whyoleg.cryptography.providers.jdk.operations.* import dev.whyoleg.cryptography.serialization.pem.* +import dev.whyoleg.cryptography.serialization.asn1.* +import dev.whyoleg.cryptography.serialization.asn1.modules.* internal class JdkEdDSA(private val state: JdkCryptographyState) : EdDSA { private fun curveName(curve: EdDSA.Curve): String = when (curve) { EdDSA.Curve.Ed25519 -> "Ed25519" EdDSA.Curve.Ed448 -> "Ed448" } + private fun oid(curve: EdDSA.Curve): ObjectIdentifier = when (curve) { + EdDSA.Curve.Ed25519 -> ObjectIdentifier("1.3.101.112") + EdDSA.Curve.Ed448 -> ObjectIdentifier("1.3.101.113") + } override fun publicKeyDecoder(curve: EdDSA.Curve): KeyDecoder = object : JdkPublicKeyDecoder(state, curveName(curve)) { @@ -21,7 +27,9 @@ internal class JdkEdDSA(private val state: JdkCryptographyState) : EdDSA { override fun decodeFromByteArrayBlocking(format: EdDSA.PublicKey.Format, bytes: ByteArray): EdDSA.PublicKey = when (format) { EdDSA.PublicKey.Format.JWK -> error("JWK is not supported") - EdDSA.PublicKey.Format.RAW -> TODO("RAW encoding is not supported yet") + EdDSA.PublicKey.Format.RAW -> decodeFromDer( + wrapSubjectPublicKeyInfo(UnknownKeyAlgorithmIdentifier(oid(curve)), bytes) + ) EdDSA.PublicKey.Format.DER -> decodeFromDer(bytes) EdDSA.PublicKey.Format.PEM -> decodeFromDer(unwrapPem(PemLabel.PublicKey, bytes)) } @@ -33,7 +41,13 @@ internal class JdkEdDSA(private val state: JdkCryptographyState) : EdDSA { override fun decodeFromByteArrayBlocking(format: EdDSA.PrivateKey.Format, bytes: ByteArray): EdDSA.PrivateKey = when (format) { EdDSA.PrivateKey.Format.JWK -> error("JWK is not supported") - EdDSA.PrivateKey.Format.RAW -> TODO("RAW encoding is not supported yet") + EdDSA.PrivateKey.Format.RAW -> decodeFromDer( + wrapPrivateKeyInfo( + 0, + UnknownKeyAlgorithmIdentifier(oid(curve)), + bytes + ) + ) EdDSA.PrivateKey.Format.DER -> decodeFromDer(bytes) EdDSA.PrivateKey.Format.PEM -> decodeFromDer(unwrapPem(PemLabel.PrivateKey, bytes)) } @@ -65,7 +79,13 @@ internal class JdkEdDSA(private val state: JdkCryptographyState) : EdDSA { override fun encodeToByteArrayBlocking(format: EdDSA.PublicKey.Format): ByteArray = when (format) { EdDSA.PublicKey.Format.JWK -> error("JWK is not supported") - EdDSA.PublicKey.Format.RAW -> TODO("RAW encoding is not supported yet") + EdDSA.PublicKey.Format.RAW -> { + val der = encodeToDer() + // unwrap SPKI to raw for known OIDs + try { unwrapSubjectPublicKeyInfo(ObjectIdentifier("1.3.101.112"), der) } catch (_: Throwable) { + unwrapSubjectPublicKeyInfo(ObjectIdentifier("1.3.101.113"), der) + } + } EdDSA.PublicKey.Format.DER -> encodeToDer() EdDSA.PublicKey.Format.PEM -> wrapPem(PemLabel.PublicKey, encodeToDer()) } @@ -81,7 +101,13 @@ internal class JdkEdDSA(private val state: JdkCryptographyState) : EdDSA { override fun encodeToByteArrayBlocking(format: EdDSA.PrivateKey.Format): ByteArray = when (format) { EdDSA.PrivateKey.Format.JWK -> error("JWK is not supported") - EdDSA.PrivateKey.Format.RAW -> TODO("RAW encoding is not supported yet") + EdDSA.PrivateKey.Format.RAW -> { + val der = encodeToDer() + // unwrap PKCS#8 to raw for known OIDs + try { unwrapPrivateKeyInfo(ObjectIdentifier("1.3.101.112"), der) } catch (_: Throwable) { + unwrapPrivateKeyInfo(ObjectIdentifier("1.3.101.113"), der) + } + } EdDSA.PrivateKey.Format.DER -> encodeToDer() EdDSA.PrivateKey.Format.PEM -> wrapPem(PemLabel.PrivateKey, encodeToDer()) } diff --git a/cryptography-providers/openssl3/api/src/commonMain/kotlin/algorithms/Openssl3Ecdh.kt b/cryptography-providers/openssl3/api/src/commonMain/kotlin/algorithms/Openssl3Ecdh.kt index e86c41f6..f738d703 100644 --- a/cryptography-providers/openssl3/api/src/commonMain/kotlin/algorithms/Openssl3Ecdh.kt +++ b/cryptography-providers/openssl3/api/src/commonMain/kotlin/algorithms/Openssl3Ecdh.kt @@ -11,6 +11,7 @@ import dev.whyoleg.cryptography.providers.base.* import dev.whyoleg.cryptography.providers.openssl3.internal.* import dev.whyoleg.cryptography.providers.openssl3.internal.cinterop.* import dev.whyoleg.cryptography.providers.openssl3.materials.* +import dev.whyoleg.cryptography.providers.openssl3.operations.* import kotlinx.cinterop.* import platform.posix.* @@ -149,21 +150,4 @@ internal object Openssl3Ecdh : ECDH { } } -@OptIn(UnsafeNumber::class) -private fun deriveSharedSecret( - publicKey: CPointer, - privateKey: CPointer, -): ByteArray = memScoped { - val context = checkError(EVP_PKEY_CTX_new_from_pkey(null, privateKey, null)) - try { - checkError(EVP_PKEY_derive_init(context)) - checkError(EVP_PKEY_derive_set_peer(context, publicKey)) - val secretSize = alloc() - checkError(EVP_PKEY_derive(context, null, secretSize.ptr)) - val secret = ByteArray(secretSize.value.toInt()) - checkError(EVP_PKEY_derive(context, secret.refToU(0), secretSize.ptr)) - secret - } finally { - EVP_PKEY_CTX_free(context) - } -} +// shared implementation moved to operations/KeyAgreement.kt diff --git a/cryptography-providers/openssl3/api/src/commonMain/kotlin/algorithms/Openssl3EdDSA.kt b/cryptography-providers/openssl3/api/src/commonMain/kotlin/algorithms/Openssl3EdDSA.kt index fc13cd47..5288ab56 100644 --- a/cryptography-providers/openssl3/api/src/commonMain/kotlin/algorithms/Openssl3EdDSA.kt +++ b/cryptography-providers/openssl3/api/src/commonMain/kotlin/algorithms/Openssl3EdDSA.kt @@ -20,6 +20,10 @@ internal object Openssl3EdDSA : EdDSA { EdDSA.Curve.Ed25519 -> "ED25519" EdDSA.Curve.Ed448 -> "ED448" } + private fun oid(curve: EdDSA.Curve): String = when (curve) { + EdDSA.Curve.Ed25519 -> "1.3.101.112" + EdDSA.Curve.Ed448 -> "1.3.101.113" + } override fun publicKeyDecoder(curve: EdDSA.Curve): KeyDecoder = object : Openssl3PublicKeyDecoder(algorithmName(curve)) { @@ -27,10 +31,20 @@ internal object Openssl3EdDSA : EdDSA { EdDSA.PublicKey.Format.DER -> "DER" EdDSA.PublicKey.Format.PEM -> "PEM" EdDSA.PublicKey.Format.JWK, - EdDSA.PublicKey.Format.RAW -> error("$format format is not supported") + EdDSA.PublicKey.Format.RAW -> error("should not be called: handled explicitly in decodeFromBlocking") + } + + override fun decodeFromByteArrayBlocking(format: EdDSA.PublicKey.Format, bytes: ByteArray): EdDSA.PublicKey = when (format) { + EdDSA.PublicKey.Format.RAW -> super.decodeFromByteArrayBlocking(EdDSA.PublicKey.Format.DER, + wrapSubjectPublicKeyInfo( + UnknownKeyAlgorithmIdentifier(ObjectIdentifier(oid(curve))), + bytes + ) + ) + else -> super.decodeFromByteArrayBlocking(format, bytes) } - override fun wrapKey(key: CPointer): EdDSA.PublicKey = EdDsaPublicKey(key) + override fun wrapKey(key: CPointer): EdDSA.PublicKey = EdDsaPublicKey(key, curve) } override fun privateKeyDecoder(curve: EdDSA.Curve): KeyDecoder = @@ -39,18 +53,29 @@ internal object Openssl3EdDSA : EdDSA { EdDSA.PrivateKey.Format.DER -> "DER" EdDSA.PrivateKey.Format.PEM -> "PEM" EdDSA.PrivateKey.Format.JWK, - EdDSA.PrivateKey.Format.RAW -> error("$format format is not supported") + EdDSA.PrivateKey.Format.RAW -> error("should not be called: handled explicitly in decodeFromBlocking") } - override fun wrapKey(key: CPointer): EdDSA.PrivateKey = EdDsaPrivateKey(key) + override fun decodeFromByteArrayBlocking(format: EdDSA.PrivateKey.Format, bytes: ByteArray): EdDSA.PrivateKey = when (format) { + EdDSA.PrivateKey.Format.RAW -> super.decodeFromByteArrayBlocking(EdDSA.PrivateKey.Format.DER, + wrapPrivateKeyInfo( + 0, + UnknownKeyAlgorithmIdentifier(ObjectIdentifier(oid(curve))), + bytes + ) + ) + else -> super.decodeFromByteArrayBlocking(format, bytes) + } + + override fun wrapKey(key: CPointer): EdDSA.PrivateKey = EdDsaPrivateKey(key, curve) } override fun keyPairGenerator(curve: EdDSA.Curve): KeyGenerator = object : Openssl3KeyPairGenerator(algorithmName(curve)) { override fun MemScope.createParams(): CValuesRef? = null override fun wrapKeyPair(keyPair: CPointer): EdDSA.KeyPair = EdDsaKeyPair( - publicKey = EdDsaPublicKey(keyPair), - privateKey = EdDsaPrivateKey(keyPair) + publicKey = EdDsaPublicKey(keyPair, curve), + privateKey = EdDsaPrivateKey(keyPair, curve) ) } @@ -61,12 +86,21 @@ internal object Openssl3EdDSA : EdDSA { private class EdDsaPublicKey( key: CPointer, + private val curve: EdDSA.Curve, ) : EdDSA.PublicKey, Openssl3PublicKeyEncodable(key) { override fun outputType(format: EdDSA.PublicKey.Format): String = when (format) { EdDSA.PublicKey.Format.DER -> "DER" EdDSA.PublicKey.Format.PEM -> "PEM" EdDSA.PublicKey.Format.JWK, - EdDSA.PublicKey.Format.RAW -> error("$format format is not supported") + EdDSA.PublicKey.Format.RAW -> error("should not be called: handled explicitly in encodeToBlocking") + } + + override fun encodeToByteArrayBlocking(format: EdDSA.PublicKey.Format): ByteArray = when (format) { + EdDSA.PublicKey.Format.RAW -> unwrapSubjectPublicKeyInfo( + ObjectIdentifier(oid(curve)), + super.encodeToByteArrayBlocking(EdDSA.PublicKey.Format.DER) + ) + else -> super.encodeToByteArrayBlocking(format) } override fun signatureVerifier(): SignatureVerifier = EdDsaSignatureVerifier(key) @@ -74,12 +108,21 @@ internal object Openssl3EdDSA : EdDSA { private class EdDsaPrivateKey( key: CPointer, + private val curve: EdDSA.Curve, ) : EdDSA.PrivateKey, Openssl3PrivateKeyEncodable(key) { override fun outputType(format: EdDSA.PrivateKey.Format): String = when (format) { EdDSA.PrivateKey.Format.DER -> "DER" EdDSA.PrivateKey.Format.PEM -> "PEM" EdDSA.PrivateKey.Format.JWK, - EdDSA.PrivateKey.Format.RAW -> error("$format format is not supported") + EdDSA.PrivateKey.Format.RAW -> error("should not be called: handled explicitly in encodeToBlocking") + } + + override fun encodeToByteArrayBlocking(format: EdDSA.PrivateKey.Format): ByteArray = when (format) { + EdDSA.PrivateKey.Format.RAW -> unwrapPrivateKeyInfo( + ObjectIdentifier(oid(curve)), + super.encodeToByteArrayBlocking(EdDSA.PrivateKey.Format.DER) + ) + else -> super.encodeToByteArrayBlocking(format) } override fun signatureGenerator(): SignatureGenerator = EdDsaSignatureGenerator(key) diff --git a/cryptography-providers/openssl3/api/src/commonMain/kotlin/algorithms/Openssl3XDH.kt b/cryptography-providers/openssl3/api/src/commonMain/kotlin/algorithms/Openssl3XDH.kt index 4eedcf90..fad8101b 100644 --- a/cryptography-providers/openssl3/api/src/commonMain/kotlin/algorithms/Openssl3XDH.kt +++ b/cryptography-providers/openssl3/api/src/commonMain/kotlin/algorithms/Openssl3XDH.kt @@ -14,12 +14,17 @@ import dev.whyoleg.cryptography.providers.openssl3.materials.* import kotlinx.cinterop.* import platform.posix.* import kotlin.experimental.* +import dev.whyoleg.cryptography.providers.openssl3.operations.* internal object Openssl3XDH : XDH { private fun algorithmName(curve: XDH.Curve): String = when (curve) { XDH.Curve.X25519 -> "X25519" XDH.Curve.X448 -> "X448" } + private fun oid(curve: XDH.Curve): String = when (curve) { + XDH.Curve.X25519 -> "1.3.101.110" + XDH.Curve.X448 -> "1.3.101.111" + } override fun publicKeyDecoder(curve: XDH.Curve): KeyDecoder = object : Openssl3PublicKeyDecoder(algorithmName(curve)) { @@ -27,10 +32,21 @@ internal object Openssl3XDH : XDH { XDH.PublicKey.Format.DER -> "DER" XDH.PublicKey.Format.PEM -> "PEM" XDH.PublicKey.Format.JWK, - XDH.PublicKey.Format.RAW -> error("$format format is not supported") + XDH.PublicKey.Format.RAW -> error("should not be called: handled explicitly in decodeFromBlocking") + } + + override fun decodeFromByteArrayBlocking(format: XDH.PublicKey.Format, bytes: ByteArray): XDH.PublicKey = when (format) { + XDH.PublicKey.Format.RAW -> super.decodeFromByteArrayBlocking( + XDH.PublicKey.Format.DER, + wrapSubjectPublicKeyInfo( + UnknownKeyAlgorithmIdentifier(ObjectIdentifier(oid(curve))), + bytes + ) + ) + else -> super.decodeFromByteArrayBlocking(format, bytes) } - override fun wrapKey(key: CPointer): XDH.PublicKey = XdhPublicKey(key) + override fun wrapKey(key: CPointer): XDH.PublicKey = XdhPublicKey(key, curve) } override fun privateKeyDecoder(curve: XDH.Curve): KeyDecoder = @@ -39,18 +55,30 @@ internal object Openssl3XDH : XDH { XDH.PrivateKey.Format.DER -> "DER" XDH.PrivateKey.Format.PEM -> "PEM" XDH.PrivateKey.Format.JWK, - XDH.PrivateKey.Format.RAW -> error("$format format is not supported") + XDH.PrivateKey.Format.RAW -> error("should not be called: handled explicitly in decodeFromBlocking") } - override fun wrapKey(key: CPointer): XDH.PrivateKey = XdhPrivateKey(key) + override fun decodeFromByteArrayBlocking(format: XDH.PrivateKey.Format, bytes: ByteArray): XDH.PrivateKey = when (format) { + XDH.PrivateKey.Format.RAW -> super.decodeFromByteArrayBlocking( + XDH.PrivateKey.Format.DER, + wrapPrivateKeyInfo( + 0, + UnknownKeyAlgorithmIdentifier(ObjectIdentifier(oid(curve))), + bytes + ) + ) + else -> super.decodeFromByteArrayBlocking(format, bytes) + } + + override fun wrapKey(key: CPointer): XDH.PrivateKey = XdhPrivateKey(key, curve) } override fun keyPairGenerator(curve: XDH.Curve): KeyGenerator = object : Openssl3KeyPairGenerator(algorithmName(curve)) { override fun MemScope.createParams(): CValuesRef? = null override fun wrapKeyPair(keyPair: CPointer): XDH.KeyPair = XdhKeyPair( - publicKey = XdhPublicKey(keyPair), - privateKey = XdhPrivateKey(keyPair) + publicKey = XdhPublicKey(keyPair, curve), + privateKey = XdhPrivateKey(keyPair, curve) ) } @@ -61,12 +89,21 @@ internal object Openssl3XDH : XDH { private class XdhPublicKey( key: CPointer, + private val curve: XDH.Curve, ) : XDH.PublicKey, Openssl3PublicKeyEncodable(key), SharedSecretGenerator { override fun outputType(format: XDH.PublicKey.Format): String = when (format) { XDH.PublicKey.Format.DER -> "DER" XDH.PublicKey.Format.PEM -> "PEM" XDH.PublicKey.Format.JWK, - XDH.PublicKey.Format.RAW -> error("$format format is not supported") + XDH.PublicKey.Format.RAW -> error("should not be called: handled explicitly in encodeToBlocking") + } + + override fun encodeToByteArrayBlocking(format: XDH.PublicKey.Format): ByteArray = when (format) { + XDH.PublicKey.Format.RAW -> unwrapSubjectPublicKeyInfo( + ObjectIdentifier(oid(curve)), + super.encodeToByteArrayBlocking(XDH.PublicKey.Format.DER) + ) + else -> super.encodeToByteArrayBlocking(format) } override fun sharedSecretGenerator(): SharedSecretGenerator = this @@ -78,12 +115,21 @@ internal object Openssl3XDH : XDH { private class XdhPrivateKey( key: CPointer, + private val curve: XDH.Curve, ) : XDH.PrivateKey, Openssl3PrivateKeyEncodable(key), SharedSecretGenerator { override fun outputType(format: XDH.PrivateKey.Format): String = when (format) { XDH.PrivateKey.Format.DER -> "DER" XDH.PrivateKey.Format.PEM -> "PEM" XDH.PrivateKey.Format.JWK, - XDH.PrivateKey.Format.RAW -> error("$format format is not supported") + XDH.PrivateKey.Format.RAW -> error("should not be called: handled explicitly in encodeToBlocking") + } + + override fun encodeToByteArrayBlocking(format: XDH.PrivateKey.Format): ByteArray = when (format) { + XDH.PrivateKey.Format.RAW -> unwrapPrivateKeyInfo( + ObjectIdentifier(oid(curve)), + super.encodeToByteArrayBlocking(XDH.PrivateKey.Format.DER) + ) + else -> super.encodeToByteArrayBlocking(format) } override fun sharedSecretGenerator(): SharedSecretGenerator = this @@ -94,21 +140,4 @@ internal object Openssl3XDH : XDH { } } -@OptIn(UnsafeNumber::class) -private fun deriveSharedSecret( - publicKey: CPointer, - privateKey: CPointer, -): ByteArray = memScoped { - val context = checkError(EVP_PKEY_CTX_new_from_pkey(null, privateKey, null)) - try { - checkError(EVP_PKEY_derive_init(context)) - checkError(EVP_PKEY_derive_set_peer(context, publicKey)) - val secretSize = alloc() - checkError(EVP_PKEY_derive(context, null, secretSize.ptr)) - val secret = ByteArray(secretSize.value.toInt()) - checkError(EVP_PKEY_derive(context, secret.refToU(0), secretSize.ptr)) - secret - } finally { - EVP_PKEY_CTX_free(context) - } -} +// shared implementation moved to operations/KeyAgreement.kt diff --git a/cryptography-providers/openssl3/api/src/commonMain/kotlin/operations/KeyAgreement.kt b/cryptography-providers/openssl3/api/src/commonMain/kotlin/operations/KeyAgreement.kt new file mode 100644 index 00000000..2492676c --- /dev/null +++ b/cryptography-providers/openssl3/api/src/commonMain/kotlin/operations/KeyAgreement.kt @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2025 Oleg Yukhnevich. Use of this source code is governed by the Apache 2.0 license. + */ + +package dev.whyoleg.cryptography.providers.openssl3.operations + +import dev.whyoleg.cryptography.providers.openssl3.internal.* +import dev.whyoleg.cryptography.providers.openssl3.internal.cinterop.* +import kotlinx.cinterop.* +import platform.posix.* +import kotlin.experimental.* + +@OptIn(UnsafeNumber::class) +internal fun deriveSharedSecret( + publicKey: CPointer, + privateKey: CPointer, +): ByteArray = memScoped { + val context = checkError(EVP_PKEY_CTX_new_from_pkey(null, privateKey, null)) + try { + checkError(EVP_PKEY_derive_init(context)) + checkError(EVP_PKEY_derive_set_peer(context, publicKey)) + val secretSize = alloc() + checkError(EVP_PKEY_derive(context, null, secretSize.ptr)) + val secret = ByteArray(secretSize.value.toInt()) + checkError(EVP_PKEY_derive(context, secret.refToU(0), secretSize.ptr)) + secret + } finally { + EVP_PKEY_CTX_free(context) + } +} + diff --git a/cryptography-providers/tests/src/commonMain/kotlin/compatibility/EdDsaCompatibilityTest.kt b/cryptography-providers/tests/src/commonMain/kotlin/compatibility/EdDsaCompatibilityTest.kt new file mode 100644 index 00000000..0f11ab29 --- /dev/null +++ b/cryptography-providers/tests/src/commonMain/kotlin/compatibility/EdDsaCompatibilityTest.kt @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2025 Oleg Yukhnevich. Use of this source code is governed by the Apache 2.0 license. + */ + +package dev.whyoleg.cryptography.providers.tests.compatibility + +import dev.whyoleg.cryptography.* +import dev.whyoleg.cryptography.algorithms.* +import dev.whyoleg.cryptography.providers.tests.* +import dev.whyoleg.cryptography.providers.tests.compatibility.api.* +import dev.whyoleg.cryptography.random.* +import dev.whyoleg.cryptography.serialization.pem.* +import kotlinx.io.bytestring.* +import kotlinx.serialization.* +import kotlin.test.* + +private val edPublicKeyFormats = listOf( + EdDSA.PublicKey.Format.RAW, + EdDSA.PublicKey.Format.DER, + EdDSA.PublicKey.Format.PEM, +).associateBy { it.name } + +private val edPrivateKeyFormats = listOf( + EdDSA.PrivateKey.Format.RAW, + EdDSA.PrivateKey.Format.DER, + EdDSA.PrivateKey.Format.PEM, +).associateBy { it.name } + +abstract class EdDsaCompatibilityTest( + provider: CryptographyProvider, +) : CompatibilityTest(EdDSA, provider) { + + @Serializable + private data class KeyParameters(val curveName: String) : TestParameters { + val curve get() = EdDSA.Curve(curveName) + } + + override suspend fun CompatibilityTestScope.generate(isStressTest: Boolean) { + val signatureIterations = if (isStressTest) 5 else 2 + + listOf(EdDSA.Curve.Ed25519, EdDSA.Curve.Ed448).forEach { curve -> + if (!supportsAlgorithmOnCurve(curve)) return@forEach + + val keyParametersId = api.keyPairs.saveParameters(KeyParameters(curve.name)) + + val keyIterations = if (isStressTest) 5 else 2 + algorithm.keyPairGenerator(curve).generateKeys(keyIterations) { keyPair -> + val keyReference = api.keyPairs.saveData( + keyParametersId, + KeyPairData( + public = KeyData(keyPair.publicKey.encodeTo(edPublicKeyFormats.values, ::supportsKeyFormat)), + private = KeyData(keyPair.privateKey.encodeTo(edPrivateKeyFormats.values, ::supportsKeyFormat)), + ) + ) + + repeat(signatureIterations) { + val dataSize = CryptographyRandom.nextInt(0, 8192) + val data = ByteString(CryptographyRandom.nextBytes(dataSize)) + val signature = keyPair.privateKey.signatureGenerator().generateSignature(data) + + api.signatures.saveData( + parametersId = api.signatures.saveParameters(TestParameters.Empty), + data = SignatureData(keyReference, data, signature) + ) + } + } + } + } + + private fun ProviderTestScope.supportsAlgorithmOnCurve(curve: EdDSA.Curve): Boolean { + // no per-curve gating at the moment; provider-level SupportedAlgorithmsTest already checks availability + return true + } + + override suspend fun CompatibilityTestScope.validate() { + // Decode saved keys + val keyPairs = buildMap { + api.keyPairs.getParameters { params, parametersId, _ -> + val publicKeyDecoder = algorithm.publicKeyDecoder(params.curve) + val privateKeyDecoder = algorithm.privateKeyDecoder(params.curve) + api.keyPairs.getData(parametersId) { (public, private), keyReference, _ -> + val publicKeys = publicKeyDecoder.decodeFrom( + formats = public.formats, + formatOf = edPublicKeyFormats::getValue, + supports = ::supportsKeyFormat + ) { key, format, bytes -> + when (format) { + EdDSA.PublicKey.Format.PEM -> { + val expected = PemDocument.decode(bytes) + val actual = PemDocument.decode(key.encodeToByteString(format)) + assertEquals(expected.label, actual.label) + assertEquals(PemLabel.PublicKey, actual.label) + assertContentEquals(expected.content, actual.content, "Public Key $format content encoding") + } + else -> assertContentEquals(bytes, key.encodeToByteString(format), "Public Key $format encoding") + } + } + val privateKeys = privateKeyDecoder.decodeFrom( + formats = private.formats, + formatOf = edPrivateKeyFormats::getValue, + supports = ::supportsKeyFormat + ) { key, format, bytes -> + when (format) { + EdDSA.PrivateKey.Format.PEM -> { + val expected = PemDocument.decode(bytes) + val actual = PemDocument.decode(key.encodeToByteString(format)) + assertEquals(expected.label, actual.label) + assertEquals(PemLabel.PrivateKey, actual.label) + assertContentEquals(expected.content, actual.content, "Private Key $format content encoding") + } + else -> assertContentEquals(bytes, key.encodeToByteString(format), "Private Key $format encoding") + } + } + put(keyReference, publicKeys to privateKeys) + } + } + } + + // Validate signatures across providers + api.signatures.getParameters { _, parametersId, _ -> + api.signatures.getData(parametersId) { (keyReference, data, signature), _, _ -> + val (publicKeys, privateKeys) = keyPairs[keyReference] ?: return@getData + val verifiers = publicKeys.map { it.signatureVerifier() } + val generators = privateKeys.map { it.signatureGenerator() } + + verifiers.forEach { verifier -> + assertTrue(verifier.tryVerifySignature(data, signature), "Verify") + generators.forEach { generator -> + val s = generator.generateSignature(data) + assertTrue(verifier.tryVerifySignature(data, s), "Sign-Verify") + } + } + } + } + } +} + diff --git a/cryptography-providers/tests/src/commonMain/kotlin/compatibility/XdhCompatibilityTest.kt b/cryptography-providers/tests/src/commonMain/kotlin/compatibility/XdhCompatibilityTest.kt new file mode 100644 index 00000000..017a68dd --- /dev/null +++ b/cryptography-providers/tests/src/commonMain/kotlin/compatibility/XdhCompatibilityTest.kt @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2025 Oleg Yukhnevich. Use of this source code is governed by the Apache 2.0 license. + */ + +package dev.whyoleg.cryptography.providers.tests.compatibility + +import dev.whyoleg.cryptography.* +import dev.whyoleg.cryptography.algorithms.* +import dev.whyoleg.cryptography.providers.tests.* +import dev.whyoleg.cryptography.providers.tests.compatibility.api.* +import dev.whyoleg.cryptography.serialization.pem.* +import kotlinx.serialization.* +import kotlin.test.* + +private val xdhPublicKeyFormats = listOf( + XDH.PublicKey.Format.RAW, + XDH.PublicKey.Format.DER, + XDH.PublicKey.Format.PEM, +).associateBy { it.name } + +private val xdhPrivateKeyFormats = listOf( + XDH.PrivateKey.Format.RAW, + XDH.PrivateKey.Format.DER, + XDH.PrivateKey.Format.PEM, +).associateBy { it.name } + +abstract class XdhCompatibilityTest( + provider: CryptographyProvider, +) : CompatibilityTest(XDH, provider) { + + @Serializable + private data class KeyParameters(val curveName: String) : TestParameters { + val curve get() = XDH.Curve(curveName) + } + + override suspend fun CompatibilityTestScope.generate(isStressTest: Boolean) { + val parametersId = api.sharedSecrets.saveParameters(TestParameters.Empty) + + listOf(XDH.Curve.X25519, XDH.Curve.X448).forEach { curve -> + val keyParametersId = api.keyPairs.saveParameters(KeyParameters(curve.name)) + + val keyIterations = if (isStressTest) 5 else 2 + // Generate two key pairs for shared secret validation + algorithm.keyPairGenerator(curve).generateKeys(keyIterations) { keyPair -> + val keyReference = api.keyPairs.saveData( + keyParametersId, + KeyPairData( + public = KeyData(keyPair.publicKey.encodeTo(xdhPublicKeyFormats.values, ::supportsKeyFormat)), + private = KeyData(keyPair.privateKey.encodeTo(xdhPrivateKeyFormats.values, ::supportsKeyFormat)) + ) + ) + + algorithm.keyPairGenerator(curve).generateKeys(1) { otherKeyPair -> + val otherKeyReference = api.keyPairs.saveData( + keyParametersId, + KeyPairData( + public = KeyData(otherKeyPair.publicKey.encodeTo(xdhPublicKeyFormats.values, ::supportsKeyFormat)), + private = KeyData(otherKeyPair.privateKey.encodeTo(xdhPrivateKeyFormats.values, ::supportsKeyFormat)) + ) + ) + + val shared = keyPair.privateKey.sharedSecretGenerator().generateSharedSecret(otherKeyPair.publicKey) + api.sharedSecrets.saveData(parametersId, SharedSecretData(keyReference, otherKeyReference, shared)) + } + } + } + } + + override suspend fun CompatibilityTestScope.validate() { + val keyPairs = buildMap { + api.keyPairs.getParameters { params, parametersId, _ -> + val publicKeyDecoder = algorithm.publicKeyDecoder(params.curve) + val privateKeyDecoder = algorithm.privateKeyDecoder(params.curve) + api.keyPairs.getData(parametersId) { (public, private), keyReference, _ -> + val publicKeys = publicKeyDecoder.decodeFrom( + formats = public.formats, + formatOf = xdhPublicKeyFormats::getValue, + supports = ::supportsKeyFormat + ) { key, format, bytes -> + when (format) { + XDH.PublicKey.Format.PEM -> { + val expected = PemDocument.decode(bytes) + val actual = PemDocument.decode(key.encodeToByteString(format)) + assertEquals(expected.label, actual.label) + assertEquals(PemLabel.PublicKey, actual.label) + assertContentEquals(expected.content, actual.content, "Public Key $format content encoding") + } + else -> assertContentEquals(bytes, key.encodeToByteString(format), "Public Key $format encoding") + } + } + val privateKeys = privateKeyDecoder.decodeFrom( + formats = private.formats, + formatOf = xdhPrivateKeyFormats::getValue, + supports = ::supportsKeyFormat + ) { key, format, bytes -> + when (format) { + XDH.PrivateKey.Format.PEM -> { + val expected = PemDocument.decode(bytes) + val actual = PemDocument.decode(key.encodeToByteString(format)) + assertEquals(expected.label, actual.label) + assertEquals(PemLabel.PrivateKey, actual.label) + assertContentEquals(expected.content, actual.content, "Private Key $format content encoding") + } + else -> assertContentEquals(bytes, key.encodeToByteString(format), "Private Key $format encoding") + } + } + put(keyReference, publicKeys to privateKeys) + } + } + } + + api.sharedSecrets.getParameters { _, parametersId, _ -> + api.sharedSecrets.getData(parametersId) { (keyReference, otherKeyReference, sharedSecret), _, _ -> + val (publicKeys, privateKeys) = keyPairs[keyReference] ?: return@getData + val (otherPublicKeys, otherPrivateKeys) = keyPairs[otherKeyReference] ?: return@getData + + // Verify both combinations generate the same secret + publicKeys.forEach { publicKey -> + otherPrivateKeys.forEach { otherPrivateKey -> + assertContentEquals(sharedSecret, publicKey.sharedSecretGenerator().generateSharedSecret(otherPrivateKey)) + assertContentEquals(sharedSecret, otherPrivateKey.sharedSecretGenerator().generateSharedSecret(publicKey)) + } + } + privateKeys.forEach { privateKey -> + otherPublicKeys.forEach { otherPublicKey -> + assertContentEquals(sharedSecret, privateKey.sharedSecretGenerator().generateSharedSecret(otherPublicKey)) + assertContentEquals(sharedSecret, otherPublicKey.sharedSecretGenerator().generateSharedSecret(privateKey)) + } + } + } + } + } +} + diff --git a/cryptography-providers/tests/src/commonMain/kotlin/default/SupportedAlgorithmsTest.kt b/cryptography-providers/tests/src/commonMain/kotlin/default/SupportedAlgorithmsTest.kt index edf9b64d..e364dc2d 100644 --- a/cryptography-providers/tests/src/commonMain/kotlin/default/SupportedAlgorithmsTest.kt +++ b/cryptography-providers/tests/src/commonMain/kotlin/default/SupportedAlgorithmsTest.kt @@ -52,9 +52,8 @@ abstract class SupportedAlgorithmsTest(provider: CryptographyProvider) : Provide assertSupports(ECDH, !context.provider.isApple) // Edwards-family - // WebCrypto availability depends on engine; skip expecting it there - assertSupports(EdDSA, !context.provider.isApple && !context.provider.isWebCrypto) - assertSupports(XDH, !context.provider.isApple && !context.provider.isWebCrypto) + assertSupports(EdDSA, !context.provider.isApple) + assertSupports(XDH, !context.provider.isApple) assertSupports(RSA.PSS, !context.provider.isCryptoKit) assertSupports(RSA.OAEP, !context.provider.isCryptoKit) diff --git a/cryptography-providers/tests/src/commonMain/kotlin/support.kt b/cryptography-providers/tests/src/commonMain/kotlin/support.kt index e5f54ac7..011c3f39 100644 --- a/cryptography-providers/tests/src/commonMain/kotlin/support.kt +++ b/cryptography-providers/tests/src/commonMain/kotlin/support.kt @@ -146,10 +146,8 @@ fun ProviderTestScope.supports(algorithmId: CryptographyAlgorithmId<*>): Boolean when (algorithmId) { AES.CMAC if provider.isJdkDefault -> "Default JDK provider doesn't support AES-CMAC, only supported with BouncyCastle" RSA.PSS if provider.isJdkDefault && platform.isAndroid -> "JDK provider on Android doesn't support RSASSA-PSS" - EdDSA if provider.isWebCrypto -> "WebCrypto EdDSA not yet supported in this engine" - XDH if provider.isWebCrypto -> "WebCrypto X25519/X448 not yet supported in this engine" - // Some JDKs used in CI (jvm / jvm11) lack these algorithms - EdDSA if provider.isJdkDefault -> "Default JDK may not support EdDSA on this JDK version" + // Some JDKs used in CI (jvm / jvm11) may lack these algorithms + EdDSA if provider.isJdkDefault && platform.isJdk { major < 11 } -> "Default JDK doesn't support EdDSA before JDK 11" XDH if provider.isJdkDefault && platform.isJdk { major < 11 } -> "Default JDK doesn't support XDH before JDK 11" else -> null } diff --git a/cryptography-providers/webcrypto/src/commonMain/kotlin/WebCryptoCryptographyProvider.kt b/cryptography-providers/webcrypto/src/commonMain/kotlin/WebCryptoCryptographyProvider.kt index f927a450..c43f7340 100644 --- a/cryptography-providers/webcrypto/src/commonMain/kotlin/WebCryptoCryptographyProvider.kt +++ b/cryptography-providers/webcrypto/src/commonMain/kotlin/WebCryptoCryptographyProvider.kt @@ -7,8 +7,6 @@ package dev.whyoleg.cryptography.providers.webcrypto import dev.whyoleg.cryptography.* import dev.whyoleg.cryptography.algorithms.* import dev.whyoleg.cryptography.providers.webcrypto.algorithms.* -import dev.whyoleg.cryptography.providers.webcrypto.internal.Engine -import dev.whyoleg.cryptography.providers.webcrypto.internal.detectEngine internal val defaultProvider = lazy { WebCryptoCryptographyProvider } @@ -17,18 +15,6 @@ public val CryptographyProvider.Companion.WebCrypto: CryptographyProvider by def internal object WebCryptoCryptographyProvider : CryptographyProvider() { override val name: String get() = "WebCrypto" - private val engine: Engine = detectEngine() - private var experimentalEdwardsEnabled: Boolean = false - - internal fun setExperimentalEdwards(value: Boolean) { - experimentalEdwardsEnabled = value - } - - private fun supportsEdwards(): Boolean = when (engine) { - Engine.Node, Engine.Firefox, Engine.Safari -> true - Engine.Chromium, Engine.Unknown -> experimentalEdwardsEnabled - } - @Suppress("UNCHECKED_CAST") override fun getOrNull(identifier: CryptographyAlgorithmId): A? = when (identifier) { SHA1 -> WebCryptoDigest.sha1 @@ -43,19 +29,11 @@ internal object WebCryptoCryptographyProvider : CryptographyProvider() { RSA.PSS -> WebCryptoRsaPss RSA.PKCS1 -> WebCryptoRsaPkcs1 ECDSA -> WebCryptoEcdsa - EdDSA -> if (supportsEdwards()) WebCryptoEdDSA else null + EdDSA -> WebCryptoEdDSA ECDH -> WebCryptoEcdh - XDH -> if (supportsEdwards()) WebCryptoXDH else null + XDH -> WebCryptoXDH PBKDF2 -> WebCryptoPbkdf2 HKDF -> WebCryptoHkdf else -> null } as A? } - -@Suppress("FunctionName") -public fun CryptographyProvider.Companion.WebCrypto( - enableExperimentalEdwards: Boolean, -): CryptographyProvider { - WebCryptoCryptographyProvider.setExperimentalEdwards(enableExperimentalEdwards) - return WebCryptoCryptographyProvider -} diff --git a/cryptography-providers/webcrypto/src/commonMain/kotlin/algorithms/WebCryptoEcdh.kt b/cryptography-providers/webcrypto/src/commonMain/kotlin/algorithms/WebCryptoEcdh.kt index eaf625ff..8c67ad49 100644 --- a/cryptography-providers/webcrypto/src/commonMain/kotlin/algorithms/WebCryptoEcdh.kt +++ b/cryptography-providers/webcrypto/src/commonMain/kotlin/algorithms/WebCryptoEcdh.kt @@ -28,7 +28,7 @@ internal object WebCryptoEcdh : WebCryptoEc(publicKey, EdPublicKeyProcessor), EdDSA.PublicKey { override fun signatureVerifier(): SignatureVerifier { - return WebCryptoSignatureVerifier(Algorithm(publicKey.algorithm.algorithmName), publicKey) + return WebCryptoSignatureVerifier(publicKey.algorithm, publicKey) } } @@ -54,7 +54,7 @@ internal object WebCryptoEdDSA : EdDSA { val privateKey: CryptoKey, ) : WebCryptoEncodableKey(privateKey, EdPrivateKeyProcessor), EdDSA.PrivateKey { override fun signatureGenerator(): SignatureGenerator { - return WebCryptoSignatureGenerator(Algorithm(privateKey.algorithm.algorithmName), privateKey) + return WebCryptoSignatureGenerator(privateKey.algorithm, privateKey) } } } @@ -84,16 +84,15 @@ private object EdPublicKeyProcessor : WebCryptoKeyProcessor() { override fun stringFormat(format: EdDSA.PrivateKey.Format): String = when (format) { - EdDSA.PrivateKey.Format.JWK, - EdDSA.PrivateKey.Format.RAW, - EdDSA.PrivateKey.Format.DER, - EdDSA.PrivateKey.Format.PEM, - -> "pkcs8" + EdDSA.PrivateKey.Format.JWK -> "jwk" + EdDSA.PrivateKey.Format.RAW -> "raw" + EdDSA.PrivateKey.Format.DER -> "pkcs8" + EdDSA.PrivateKey.Format.PEM -> "pkcs8" } override fun beforeDecoding(algorithm: Algorithm, format: EdDSA.PrivateKey.Format, key: ByteArray): ByteArray = when (format) { EdDSA.PrivateKey.Format.JWK -> key - EdDSA.PrivateKey.Format.RAW -> key // treat as already PKCS8 if user passes raw bytes; no wrap + EdDSA.PrivateKey.Format.RAW -> key EdDSA.PrivateKey.Format.DER -> key EdDSA.PrivateKey.Format.PEM -> unwrapPem(PemLabel.PrivateKey, key) } diff --git a/cryptography-providers/webcrypto/src/commonMain/kotlin/algorithms/WebCryptoXDH.kt b/cryptography-providers/webcrypto/src/commonMain/kotlin/algorithms/WebCryptoXDH.kt index 6201aed3..a6b3e768 100644 --- a/cryptography-providers/webcrypto/src/commonMain/kotlin/algorithms/WebCryptoXDH.kt +++ b/cryptography-providers/webcrypto/src/commonMain/kotlin/algorithms/WebCryptoXDH.kt @@ -113,11 +113,10 @@ private object XdhPublicKeyProcessor : WebCryptoKeyProcessor() { override fun stringFormat(format: XDH.PrivateKey.Format): String = when (format) { - XDH.PrivateKey.Format.JWK, - XDH.PrivateKey.Format.RAW, - XDH.PrivateKey.Format.DER, - XDH.PrivateKey.Format.PEM, - -> "pkcs8" + XDH.PrivateKey.Format.JWK -> "jwk" + XDH.PrivateKey.Format.RAW -> "raw" + XDH.PrivateKey.Format.DER -> "pkcs8" + XDH.PrivateKey.Format.PEM -> "pkcs8" } override fun beforeDecoding(algorithm: Algorithm, format: XDH.PrivateKey.Format, key: ByteArray): ByteArray = when (format) { diff --git a/cryptography-providers/webcrypto/src/commonMain/kotlin/internal/Algorithms.kt b/cryptography-providers/webcrypto/src/commonMain/kotlin/internal/Algorithms.kt index d2d03401..2007d2b2 100644 --- a/cryptography-providers/webcrypto/src/commonMain/kotlin/internal/Algorithms.kt +++ b/cryptography-providers/webcrypto/src/commonMain/kotlin/internal/Algorithms.kt @@ -30,7 +30,6 @@ internal expect val Algorithm.ecKeyAlgorithmNamedCurve: String internal expect fun EcdsaSignatureAlgorithm(hash: String): Algorithm -internal expect fun EcdhKeyDeriveAlgorithm(publicKey: CryptoKey): Algorithm internal expect fun KeyDeriveAlgorithm(name: String, publicKey: CryptoKey): Algorithm internal expect fun Pbkdf2DeriveAlgorithm(hash: String, iterations: Int, salt: ByteArray): Algorithm diff --git a/cryptography-providers/webcrypto/src/jsMain/kotlin/internal/Algorithms.js.kt b/cryptography-providers/webcrypto/src/jsMain/kotlin/internal/Algorithms.js.kt index 9cde7011..8e831be8 100644 --- a/cryptography-providers/webcrypto/src/jsMain/kotlin/internal/Algorithms.js.kt +++ b/cryptography-providers/webcrypto/src/jsMain/kotlin/internal/Algorithms.js.kt @@ -48,9 +48,6 @@ private fun ecKeyAlgorithmNamedCurve(algorithm: Algorithm): String = js("algorit internal actual fun EcdsaSignatureAlgorithm(hash: String): Algorithm = js("{ name: 'ECDSA', hash: hash }").unsafeCast() -internal actual fun EcdhKeyDeriveAlgorithm(publicKey: CryptoKey): Algorithm = - js("{ name: 'ECDH', public: publicKey }").unsafeCast() - internal actual fun KeyDeriveAlgorithm(name: String, publicKey: CryptoKey): Algorithm = js("{ name: name, public: publicKey }").unsafeCast() diff --git a/cryptography-providers/webcrypto/src/wasmJsMain/kotlin/internal/Algorithms.wasmJs.kt b/cryptography-providers/webcrypto/src/wasmJsMain/kotlin/internal/Algorithms.wasmJs.kt index eaad1e0f..bb7a2f7f 100644 --- a/cryptography-providers/webcrypto/src/wasmJsMain/kotlin/internal/Algorithms.wasmJs.kt +++ b/cryptography-providers/webcrypto/src/wasmJsMain/kotlin/internal/Algorithms.wasmJs.kt @@ -61,9 +61,6 @@ private fun ecKeyAlgorithmNamedCurve(algorithm: Algorithm): String = js("algorit internal actual fun EcdsaSignatureAlgorithm(hash: String): Algorithm = js("({ name: 'ECDSA', hash: hash })") -internal actual fun EcdhKeyDeriveAlgorithm(publicKey: CryptoKey): Algorithm = - js("({ name: 'ECDH', public: publicKey })") - internal actual fun KeyDeriveAlgorithm(name: String, publicKey: CryptoKey): Algorithm = js("({ name: name, public: publicKey })") diff --git a/docs/providers/cryptokit.md b/docs/providers/cryptokit.md index 44ccb6ad..80efba9d 100644 --- a/docs/providers/cryptokit.md +++ b/docs/providers/cryptokit.md @@ -8,7 +8,7 @@ For supported targets and algorithms, please consult [Supported primitives secti * KeyFormat: doesn't support `JWK` key format yet * AES.GCM supports only a default tag size of 96 bits -* EdDSA/XDH: Ed25519 and X25519 are supported via RAW key formats. DER/PEM for these curves are not exposed in CryptoKit and are not supported here. +* EdDSA/XDH: CryptoKit supports Ed25519 and X25519. DER/PEM and RAW key encodings are available via the provider APIs. ## Example diff --git a/docs/providers/jdk.md b/docs/providers/jdk.md index 93b2ddac..3486aaa3 100644 --- a/docs/providers/jdk.md +++ b/docs/providers/jdk.md @@ -8,11 +8,6 @@ For supported targets and algorithms, please consult [Supported primitives secti * KeyFormat: doesn't support `JWK` key format yet -### EdDSA and XDH - -EdDSA (Ed25519/Ed448) and XDH (X25519/X448) are exposed when supported by the underlying JDK. -Availability depends on JDK version and provider configuration. If your default JDK provider lacks them, -consider supplying a custom provider (e.g., BouncyCastle) as described below. ## Example ```kotlin diff --git a/docs/providers/openssl3.md b/docs/providers/openssl3.md index 7bc5829c..db54e406 100644 --- a/docs/providers/openssl3.md +++ b/docs/providers/openssl3.md @@ -21,10 +21,6 @@ For supported targets and algorithms, please consult [Supported primitives secti * KeyFormat: doesn't support `JWK` key format yet -### EdDSA and XDH - -OpenSSL 3.x supports EdDSA (Ed25519/Ed448) and XDH (X25519/X448). These algorithms are available via the OpenSSL3 provider modules -(`api`, `shared`, or `prebuilt`). ## Example diff --git a/docs/providers/webcrypto.md b/docs/providers/webcrypto.md index 3d2f4b4d..84e0a33d 100644 --- a/docs/providers/webcrypto.md +++ b/docs/providers/webcrypto.md @@ -9,9 +9,7 @@ For supported targets and algorithms, please consult [Supported primitives secti * only `suspend` functions are supported, because `WebCrypto` API is async by default * AES.* (browser only): may not support `192 bit` keys * AES.CBC: only `padding=true` is supported -* EdDSA/XDH availability depends on the engine: - - Node.js, Firefox, Safari: supported (Ed25519/Ed448, X25519/X448) - - Chromium-based (Chrome/Edge/Opera): requires enabling experimental web platform features; otherwise not exposed by the provider +* EdDSA/XDH: These algorithms were added later to WebCrypto and might not be available in all engines/browsers yet. If unsupported, operations will fail at runtime with an error from the underlying engine. ## Example @@ -23,13 +21,6 @@ val provider = CryptographyProvider.WebCrypto // or CryptographyProvider.Default provider.get(SHA512) ``` -To opt-in to EdDSA/XDH on Chromium-based engines (with experimental web platform features enabled), -explicitly enable experimental Edwards algorithms: - -```kotlin -val provider = CryptographyProvider.WebCrypto(enableExperimentalEdwards = true) -``` - ## Using in your projects ```kotlin From 059212f7ac8513edc60d0528df598e28849ff9d0 Mon Sep 17 00:00:00 2001 From: Aria Wisp Date: Thu, 4 Sep 2025 15:15:02 -0600 Subject: [PATCH 11/22] Cleanup: remove temp JSONL and stray internal comments --- .gh-pr-105-comments.jsonl | 26 -------------------------- 1 file changed, 26 deletions(-) delete mode 100644 .gh-pr-105-comments.jsonl diff --git a/.gh-pr-105-comments.jsonl b/.gh-pr-105-comments.jsonl deleted file mode 100644 index 9f62dbc0..00000000 --- a/.gh-pr-105-comments.jsonl +++ /dev/null @@ -1,26 +0,0 @@ -{"author":"whyoleg","body":"this could be removed in favor of `Custom Java providers` section above ","diff_hunk":"@@ -8,6 +8,11 @@ For supported targets and algorithms, please consult [Supported primitives secti\n \n * KeyFormat: doesn't support `JWK` key format yet\n \n+### EdDSA and XDH","id":2322958544,"in_reply_to_id":null,"line":11,"original_commit_id":"c8488c9a2b9a24362bf1b5b7018a29e8e2286937","original_line":11,"path":"docs/providers/jdk.md","side":"RIGHT","start_line":null} -{"author":"whyoleg","body":"this should be removed","diff_hunk":"@@ -21,6 +21,11 @@ For supported targets and algorithms, please consult [Supported primitives secti\n \n * KeyFormat: doesn't support `JWK` key format yet\n \n+### EdDSA and XDH","id":2322959257,"in_reply_to_id":null,"line":24,"original_commit_id":"c8488c9a2b9a24362bf1b5b7018a29e8e2286937","original_line":24,"path":"docs/providers/openssl3.md","side":"RIGHT","start_line":null} -{"author":"whyoleg","body":"it's not true: we could, and should support those, as we have both DER and PEM machinery implemented and used there.","diff_hunk":"@@ -8,6 +8,7 @@ For supported targets and algorithms, please consult [Supported primitives secti\n \n * KeyFormat: doesn't support `JWK` key format yet\n * AES.GCM supports only a default tag size of 96 bits\n+* EdDSA/XDH: Ed25519 and X25519 are supported via RAW key formats. DER/PEM for these curves are not exposed in CryptoKit and are not supported here.","id":2322963075,"in_reply_to_id":null,"line":11,"original_commit_id":"c8488c9a2b9a24362bf1b5b7018a29e8e2286937","original_line":11,"path":"docs/providers/cryptokit.md","side":"RIGHT","start_line":null} -{"author":"whyoleg","body":"it might be better, to mention instead, that those algorithms were added later (maybe add link to RFC?) and so might not be supported by all browsers","diff_hunk":"@@ -9,16 +9,27 @@ For supported targets and algorithms, please consult [Supported primitives secti\n * only `suspend` functions are supported, because `WebCrypto` API is async by default\n * AES.* (browser only): may not support `192 bit` keys\n * AES.CBC: only `padding=true` is supported\n+* EdDSA/XDH availability depends on the engine:","id":2322965396,"in_reply_to_id":null,"line":12,"original_commit_id":"c8488c9a2b9a24362bf1b5b7018a29e8e2286937","original_line":12,"path":"docs/providers/webcrypto.md","side":"RIGHT","start_line":null} -{"author":"whyoleg","body":"no need to have this, as we should not have this flag","diff_hunk":"@@ -9,16 +9,27 @@ For supported targets and algorithms, please consult [Supported primitives secti\n * only `suspend` functions are supported, because `WebCrypto` API is async by default\n * AES.* (browser only): may not support `192 bit` keys\n * AES.CBC: only `padding=true` is supported\n+* EdDSA/XDH availability depends on the engine:\n+ - Node.js, Firefox, Safari: supported (Ed25519/Ed448, X25519/X448)\n+ - Chromium-based (Chrome/Edge/Opera): requires enabling experimental web platform features; otherwise not exposed by the provider\n \n ## Example\n \n ```kotlin\n+// default provider\n val provider = CryptographyProvider.WebCrypto // or CryptographyProvider.Default\n \n // get some algorithm\n provider.get(SHA512)\n ```\n \n+To opt-in to EdDSA/XDH on Chromium-based engines (with experimental web platform features enabled),","id":2322966182,"in_reply_to_id":null,"line":26,"original_commit_id":"c8488c9a2b9a24362bf1b5b7018a29e8e2286937","original_line":26,"path":"docs/providers/webcrypto.md","side":"RIGHT","start_line":null} -{"author":"whyoleg","body":"I don't think, that it's correct to do it like this.\nIf Chromium will at some point enable this support, or some other environment - we will need to release a new version.\nInstead, it's fine to fail at runtime, that algorithm is not supported with an exception coming from WebCrypto.\nIt's the same to how JDK provider fails, if algorithm is not supported by the security provider\n\nLet's remove this machinery (including `detectEngine`)","diff_hunk":"@@ -15,6 +17,18 @@ public val CryptographyProvider.Companion.WebCrypto: CryptographyProvider by def\n internal object WebCryptoCryptographyProvider : CryptographyProvider() {\n override val name: String get() = \"WebCrypto\"\n \n+ private val engine: Engine = detectEngine()","id":2322973682,"in_reply_to_id":null,"line":20,"original_commit_id":"c8488c9a2b9a24362bf1b5b7018a29e8e2286937","original_line":20,"path":"cryptography-providers/webcrypto/src/commonMain/kotlin/WebCryptoCryptographyProvider.kt","side":"RIGHT","start_line":null} -{"author":"whyoleg","body":"can't we just pass `publicKey.algorithm` there?\nsame for `privateKey.algorithm` below","diff_hunk":"@@ -0,0 +1,107 @@\n+/*\n+ * Copyright (c) 2025 Oleg Yukhnevich. Use of this source code is governed by the Apache 2.0 license.\n+ */\n+\n+package dev.whyoleg.cryptography.providers.webcrypto.algorithms\n+\n+import dev.whyoleg.cryptography.algorithms.*\n+import dev.whyoleg.cryptography.materials.key.*\n+import dev.whyoleg.cryptography.operations.*\n+import dev.whyoleg.cryptography.providers.webcrypto.internal.*\n+import dev.whyoleg.cryptography.providers.webcrypto.materials.*\n+import dev.whyoleg.cryptography.providers.webcrypto.operations.*\n+import dev.whyoleg.cryptography.providers.base.materials.*\n+import dev.whyoleg.cryptography.serialization.pem.*\n+\n+internal object WebCryptoEdDSA : EdDSA {\n+ private fun curveName(curve: EdDSA.Curve): String = when (curve) {\n+ EdDSA.Curve.Ed25519 -\u003e \"Ed25519\"\n+ EdDSA.Curve.Ed448 -\u003e \"Ed448\"\n+ }\n+\n+ override fun publicKeyDecoder(curve: EdDSA.Curve): KeyDecoder\u003cEdDSA.PublicKey.Format, EdDSA.PublicKey\u003e = WebCryptoKeyDecoder(\n+ algorithm = Algorithm(curveName(curve)),\n+ keyProcessor = EdPublicKeyProcessor,\n+ keyWrapper = WebCryptoKeyWrapper(arrayOf(\"verify\")) { EdDsaPublicKey(it) },\n+ )\n+\n+ override fun privateKeyDecoder(curve: EdDSA.Curve): KeyDecoder\u003cEdDSA.PrivateKey.Format, EdDSA.PrivateKey\u003e = WebCryptoKeyDecoder(\n+ algorithm = Algorithm(curveName(curve)),\n+ keyProcessor = EdPrivateKeyProcessor,\n+ keyWrapper = WebCryptoKeyWrapper(arrayOf(\"sign\")) { EdDsaPrivateKey(it) },\n+ )\n+\n+ override fun keyPairGenerator(curve: EdDSA.Curve): KeyGenerator\u003cEdDSA.KeyPair\u003e = WebCryptoAsymmetricKeyGenerator(\n+ algorithm = Algorithm(curveName(curve)),\n+ keyUsages = arrayOf(\"verify\", \"sign\"),\n+ keyPairWrapper = { EdDsaKeyPair(EdDsaPublicKey(it.publicKey), EdDsaPrivateKey(it.privateKey)) },\n+ )\n+\n+ private class EdDsaKeyPair(\n+ override val publicKey: EdDSA.PublicKey,\n+ override val privateKey: EdDSA.PrivateKey,\n+ ) : EdDSA.KeyPair\n+\n+ private class EdDsaPublicKey(\n+ val publicKey: CryptoKey,\n+ ) : WebCryptoEncodableKey\u003cEdDSA.PublicKey.Format\u003e(publicKey, EdPublicKeyProcessor), EdDSA.PublicKey {\n+ override fun signatureVerifier(): SignatureVerifier {\n+ return WebCryptoSignatureVerifier(Algorithm(publicKey.algorithm.algorithmName), publicKey)","id":2322986311,"in_reply_to_id":null,"line":49,"original_commit_id":"c8488c9a2b9a24362bf1b5b7018a29e8e2286937","original_line":49,"path":"cryptography-providers/webcrypto/src/commonMain/kotlin/algorithms/WebCryptoEdDSA.kt","side":"RIGHT","start_line":null} -{"author":"whyoleg","body":"Something is wrong here: we should have both `jwk` and `raw` here too. Also it should be tested","diff_hunk":"@@ -0,0 +1,107 @@\n+/*\n+ * Copyright (c) 2025 Oleg Yukhnevich. Use of this source code is governed by the Apache 2.0 license.\n+ */\n+\n+package dev.whyoleg.cryptography.providers.webcrypto.algorithms\n+\n+import dev.whyoleg.cryptography.algorithms.*\n+import dev.whyoleg.cryptography.materials.key.*\n+import dev.whyoleg.cryptography.operations.*\n+import dev.whyoleg.cryptography.providers.webcrypto.internal.*\n+import dev.whyoleg.cryptography.providers.webcrypto.materials.*\n+import dev.whyoleg.cryptography.providers.webcrypto.operations.*\n+import dev.whyoleg.cryptography.providers.base.materials.*\n+import dev.whyoleg.cryptography.serialization.pem.*\n+\n+internal object WebCryptoEdDSA : EdDSA {\n+ private fun curveName(curve: EdDSA.Curve): String = when (curve) {\n+ EdDSA.Curve.Ed25519 -\u003e \"Ed25519\"\n+ EdDSA.Curve.Ed448 -\u003e \"Ed448\"\n+ }\n+\n+ override fun publicKeyDecoder(curve: EdDSA.Curve): KeyDecoder\u003cEdDSA.PublicKey.Format, EdDSA.PublicKey\u003e = WebCryptoKeyDecoder(\n+ algorithm = Algorithm(curveName(curve)),\n+ keyProcessor = EdPublicKeyProcessor,\n+ keyWrapper = WebCryptoKeyWrapper(arrayOf(\"verify\")) { EdDsaPublicKey(it) },\n+ )\n+\n+ override fun privateKeyDecoder(curve: EdDSA.Curve): KeyDecoder\u003cEdDSA.PrivateKey.Format, EdDSA.PrivateKey\u003e = WebCryptoKeyDecoder(\n+ algorithm = Algorithm(curveName(curve)),\n+ keyProcessor = EdPrivateKeyProcessor,\n+ keyWrapper = WebCryptoKeyWrapper(arrayOf(\"sign\")) { EdDsaPrivateKey(it) },\n+ )\n+\n+ override fun keyPairGenerator(curve: EdDSA.Curve): KeyGenerator\u003cEdDSA.KeyPair\u003e = WebCryptoAsymmetricKeyGenerator(\n+ algorithm = Algorithm(curveName(curve)),\n+ keyUsages = arrayOf(\"verify\", \"sign\"),\n+ keyPairWrapper = { EdDsaKeyPair(EdDsaPublicKey(it.publicKey), EdDsaPrivateKey(it.privateKey)) },\n+ )\n+\n+ private class EdDsaKeyPair(\n+ override val publicKey: EdDSA.PublicKey,\n+ override val privateKey: EdDSA.PrivateKey,\n+ ) : EdDSA.KeyPair\n+\n+ private class EdDsaPublicKey(\n+ val publicKey: CryptoKey,\n+ ) : WebCryptoEncodableKey\u003cEdDSA.PublicKey.Format\u003e(publicKey, EdPublicKeyProcessor), EdDSA.PublicKey {\n+ override fun signatureVerifier(): SignatureVerifier {\n+ return WebCryptoSignatureVerifier(Algorithm(publicKey.algorithm.algorithmName), publicKey)\n+ }\n+ }\n+\n+ private class EdDsaPrivateKey(\n+ val privateKey: CryptoKey,\n+ ) : WebCryptoEncodableKey\u003cEdDSA.PrivateKey.Format\u003e(privateKey, EdPrivateKeyProcessor), EdDSA.PrivateKey {\n+ override fun signatureGenerator(): SignatureGenerator {\n+ return WebCryptoSignatureGenerator(Algorithm(privateKey.algorithm.algorithmName), privateKey)\n+ }\n+ }\n+}\n+\n+private object EdPublicKeyProcessor : WebCryptoKeyProcessor\u003cEdDSA.PublicKey.Format\u003e() {\n+ override fun stringFormat(format: EdDSA.PublicKey.Format): String = when (format) {\n+ EdDSA.PublicKey.Format.JWK -\u003e \"jwk\"\n+ EdDSA.PublicKey.Format.RAW -\u003e \"raw\"\n+ EdDSA.PublicKey.Format.DER,\n+ EdDSA.PublicKey.Format.PEM -\u003e \"spki\"\n+ }\n+\n+ override fun beforeDecoding(algorithm: Algorithm, format: EdDSA.PublicKey.Format, key: ByteArray): ByteArray = when (format) {\n+ EdDSA.PublicKey.Format.JWK -\u003e key\n+ EdDSA.PublicKey.Format.RAW -\u003e key\n+ EdDSA.PublicKey.Format.DER -\u003e key\n+ EdDSA.PublicKey.Format.PEM -\u003e unwrapPem(PemLabel.PublicKey, key)\n+ }\n+\n+ override fun afterEncoding(format: EdDSA.PublicKey.Format, key: ByteArray): ByteArray = when (format) {\n+ EdDSA.PublicKey.Format.JWK -\u003e key\n+ EdDSA.PublicKey.Format.RAW -\u003e key\n+ EdDSA.PublicKey.Format.DER -\u003e key\n+ EdDSA.PublicKey.Format.PEM -\u003e wrapPem(PemLabel.PublicKey, key)\n+ }\n+}\n+\n+private object EdPrivateKeyProcessor : WebCryptoKeyProcessor\u003cEdDSA.PrivateKey.Format\u003e() {\n+ override fun stringFormat(format: EdDSA.PrivateKey.Format): String = when (format) {\n+ EdDSA.PrivateKey.Format.JWK,","id":2322988363,"in_reply_to_id":null,"line":87,"original_commit_id":"c8488c9a2b9a24362bf1b5b7018a29e8e2286937","original_line":87,"path":"cryptography-providers/webcrypto/src/commonMain/kotlin/algorithms/WebCryptoEdDSA.kt","side":"RIGHT","start_line":null} -{"author":"whyoleg","body":"all the same comments from `WebCryptoEdDSA` are applied here","diff_hunk":"@@ -0,0 +1,136 @@\n+/*\n+ * Copyright (c) 2025 Oleg Yukhnevich. Use of this source code is governed by the Apache 2.0 license.\n+ */\n+\n+package dev.whyoleg.cryptography.providers.webcrypto.algorithms\n+\n+import dev.whyoleg.cryptography.algorithms.*\n+import dev.whyoleg.cryptography.materials.key.*\n+import dev.whyoleg.cryptography.operations.*\n+import dev.whyoleg.cryptography.providers.webcrypto.internal.*\n+import dev.whyoleg.cryptography.providers.webcrypto.materials.*\n+import dev.whyoleg.cryptography.providers.base.materials.*\n+import dev.whyoleg.cryptography.serialization.pem.*\n+\n+internal object WebCryptoXDH : XDH {","id":2322993298,"in_reply_to_id":null,"line":15,"original_commit_id":"c8488c9a2b9a24362bf1b5b7018a29e8e2286937","original_line":15,"path":"cryptography-providers/webcrypto/src/commonMain/kotlin/algorithms/WebCryptoXDH.kt","side":"RIGHT","start_line":null} -{"author":"whyoleg","body":"it looks like we can just remove `EcdhKeyDeriveAlgorithm` and use `KeyDeriveAlgorithm` everywhere?","diff_hunk":"@@ -31,6 +31,7 @@ internal expect val Algorithm.ecKeyAlgorithmNamedCurve: String\n internal expect fun EcdsaSignatureAlgorithm(hash: String): Algorithm\n \n internal expect fun EcdhKeyDeriveAlgorithm(publicKey: CryptoKey): Algorithm","id":2322999331,"in_reply_to_id":null,"line":33,"original_commit_id":"c8488c9a2b9a24362bf1b5b7018a29e8e2286937","original_line":33,"path":"cryptography-providers/webcrypto/src/commonMain/kotlin/internal/Algorithms.kt","side":"RIGHT","start_line":null} -{"author":"whyoleg","body":"Those should be definitely dropped. If there is some problem with this on CI and locally, I could take a look. probably we can just pass experimental argument in karma somewhere.","diff_hunk":"@@ -141,6 +146,11 @@ fun ProviderTestScope.supports(algorithmId: CryptographyAlgorithmId\u003c*\u003e): Boolean\n when (algorithmId) {\n AES.CMAC if provider.isJdkDefault -\u003e \"Default JDK provider doesn't support AES-CMAC, only supported with BouncyCastle\"\n RSA.PSS if provider.isJdkDefault \u0026\u0026 platform.isAndroid -\u003e \"JDK provider on Android doesn't support RSASSA-PSS\"\n+ EdDSA if provider.isWebCrypto -\u003e \"WebCrypto EdDSA not yet supported in this engine\"\n+ XDH if provider.isWebCrypto -\u003e \"WebCrypto X25519/X448 not yet supported in this engine\"","id":2323007111,"in_reply_to_id":null,"line":150,"original_commit_id":"c8488c9a2b9a24362bf1b5b7018a29e8e2286937","original_line":150,"path":"cryptography-providers/tests/src/commonMain/kotlin/support.kt","side":"RIGHT","start_line":149} -{"author":"whyoleg","body":"I think that it should be the same, as for `XDH` - those are supported from JDK 11","diff_hunk":"@@ -141,6 +146,11 @@ fun ProviderTestScope.supports(algorithmId: CryptographyAlgorithmId\u003c*\u003e): Boolean\n when (algorithmId) {\n AES.CMAC if provider.isJdkDefault -\u003e \"Default JDK provider doesn't support AES-CMAC, only supported with BouncyCastle\"\n RSA.PSS if provider.isJdkDefault \u0026\u0026 platform.isAndroid -\u003e \"JDK provider on Android doesn't support RSASSA-PSS\"\n+ EdDSA if provider.isWebCrypto -\u003e \"WebCrypto EdDSA not yet supported in this engine\"\n+ XDH if provider.isWebCrypto -\u003e \"WebCrypto X25519/X448 not yet supported in this engine\"\n+ // Some JDKs used in CI (jvm / jvm11) lack these algorithms\n+ EdDSA if provider.isJdkDefault -\u003e \"Default JDK may not support EdDSA on this JDK version\"","id":2323016503,"in_reply_to_id":null,"line":152,"original_commit_id":"c8488c9a2b9a24362bf1b5b7018a29e8e2286937","original_line":152,"path":"cryptography-providers/tests/src/commonMain/kotlin/support.kt","side":"RIGHT","start_line":null} -{"author":"whyoleg","body":"should be just `!context.provider.isApple` after the engine machinery is removed","diff_hunk":"@@ -51,6 +51,11 @@ abstract class SupportedAlgorithmsTest(provider: CryptographyProvider) : Provide\n assertSupports(ECDSA)\n assertSupports(ECDH, !context.provider.isApple)\n \n+ // Edwards-family\n+ // WebCrypto availability depends on engine; skip expecting it there\n+ assertSupports(EdDSA, !context.provider.isApple \u0026\u0026 !context.provider.isWebCrypto)","id":2323019510,"in_reply_to_id":null,"line":56,"original_commit_id":"c8488c9a2b9a24362bf1b5b7018a29e8e2286937","original_line":56,"path":"cryptography-providers/tests/src/commonMain/kotlin/default/SupportedAlgorithmsTest.kt","side":"RIGHT","start_line":null} -{"author":"whyoleg","body":"We should also add compatibility tests for both EdDSA and XDH, similar to the ECDSA and ECDH","diff_hunk":"@@ -0,0 +1,49 @@\n+/*\n+ * Copyright (c) 2025 Oleg Yukhnevich. Use of this source code is governed by the Apache 2.0 license.\n+ */\n+\n+package dev.whyoleg.cryptography.providers.tests.default\n+\n+import dev.whyoleg.cryptography.*\n+import dev.whyoleg.cryptography.algorithms.*\n+import dev.whyoleg.cryptography.providers.tests.*\n+import dev.whyoleg.cryptography.random.*\n+import kotlinx.io.bytestring.*\n+import kotlin.test.*\n+\n+abstract class EdDsaTest(provider: CryptographyProvider) : AlgorithmTest\u003cEdDSA\u003e(EdDSA, provider), SignatureTest {","id":2323024591,"in_reply_to_id":null,"line":14,"original_commit_id":"c8488c9a2b9a24362bf1b5b7018a29e8e2286937","original_line":14,"path":"cryptography-providers/tests/src/commonMain/kotlin/default/EdDsaTest.kt","side":"RIGHT","start_line":null} -{"author":"whyoleg","body":"we should support `RAW` format (openSSL should support it)","diff_hunk":"@@ -0,0 +1,114 @@\n+/*\n+ * Copyright (c) 2025 Oleg Yukhnevich. Use of this source code is governed by the Apache 2.0 license.\n+ */\n+\n+package dev.whyoleg.cryptography.providers.openssl3.algorithms\n+\n+import dev.whyoleg.cryptography.algorithms.*\n+import dev.whyoleg.cryptography.materials.key.*\n+import dev.whyoleg.cryptography.operations.*\n+import dev.whyoleg.cryptography.providers.base.*\n+import dev.whyoleg.cryptography.providers.openssl3.internal.*\n+import dev.whyoleg.cryptography.providers.openssl3.internal.cinterop.*\n+import dev.whyoleg.cryptography.providers.openssl3.materials.*\n+import kotlinx.cinterop.*\n+import platform.posix.*\n+import kotlin.experimental.*\n+\n+internal object Openssl3XDH : XDH {\n+ private fun algorithmName(curve: XDH.Curve): String = when (curve) {\n+ XDH.Curve.X25519 -\u003e \"X25519\"\n+ XDH.Curve.X448 -\u003e \"X448\"\n+ }\n+\n+ override fun publicKeyDecoder(curve: XDH.Curve): KeyDecoder\u003cXDH.PublicKey.Format, XDH.PublicKey\u003e =\n+ object : Openssl3PublicKeyDecoder\u003cXDH.PublicKey.Format, XDH.PublicKey\u003e(algorithmName(curve)) {\n+ override fun inputType(format: XDH.PublicKey.Format): String = when (format) {\n+ XDH.PublicKey.Format.DER -\u003e \"DER\"\n+ XDH.PublicKey.Format.PEM -\u003e \"PEM\"\n+ XDH.PublicKey.Format.JWK,\n+ XDH.PublicKey.Format.RAW -\u003e error(\"$format format is not supported\")","id":2323027291,"in_reply_to_id":null,"line":30,"original_commit_id":"c8488c9a2b9a24362bf1b5b7018a29e8e2286937","original_line":30,"path":"cryptography-providers/openssl3/api/src/commonMain/kotlin/algorithms/Openssl3XDH.kt","side":"RIGHT","start_line":null} -{"author":"whyoleg","body":"we should support `RAW` format (openSSL should support it)","diff_hunk":"@@ -0,0 +1,114 @@\n+/*\n+ * Copyright (c) 2025 Oleg Yukhnevich. Use of this source code is governed by the Apache 2.0 license.\n+ */\n+\n+package dev.whyoleg.cryptography.providers.openssl3.algorithms\n+\n+import dev.whyoleg.cryptography.algorithms.*\n+import dev.whyoleg.cryptography.materials.key.*\n+import dev.whyoleg.cryptography.operations.*\n+import dev.whyoleg.cryptography.providers.base.*\n+import dev.whyoleg.cryptography.providers.openssl3.internal.*\n+import dev.whyoleg.cryptography.providers.openssl3.internal.cinterop.*\n+import dev.whyoleg.cryptography.providers.openssl3.materials.*\n+import kotlinx.cinterop.*\n+import platform.posix.*\n+import kotlin.experimental.*\n+\n+internal object Openssl3XDH : XDH {\n+ private fun algorithmName(curve: XDH.Curve): String = when (curve) {\n+ XDH.Curve.X25519 -\u003e \"X25519\"\n+ XDH.Curve.X448 -\u003e \"X448\"\n+ }\n+\n+ override fun publicKeyDecoder(curve: XDH.Curve): KeyDecoder\u003cXDH.PublicKey.Format, XDH.PublicKey\u003e =\n+ object : Openssl3PublicKeyDecoder\u003cXDH.PublicKey.Format, XDH.PublicKey\u003e(algorithmName(curve)) {\n+ override fun inputType(format: XDH.PublicKey.Format): String = when (format) {\n+ XDH.PublicKey.Format.DER -\u003e \"DER\"\n+ XDH.PublicKey.Format.PEM -\u003e \"PEM\"\n+ XDH.PublicKey.Format.JWK,\n+ XDH.PublicKey.Format.RAW -\u003e error(\"$format format is not supported\")\n+ }\n+\n+ override fun wrapKey(key: CPointer\u003cEVP_PKEY\u003e): XDH.PublicKey = XdhPublicKey(key)\n+ }\n+\n+ override fun privateKeyDecoder(curve: XDH.Curve): KeyDecoder\u003cXDH.PrivateKey.Format, XDH.PrivateKey\u003e =\n+ object : Openssl3PrivateKeyDecoder\u003cXDH.PrivateKey.Format, XDH.PrivateKey\u003e(algorithmName(curve)) {\n+ override fun inputType(format: XDH.PrivateKey.Format): String = when (format) {\n+ XDH.PrivateKey.Format.DER -\u003e \"DER\"\n+ XDH.PrivateKey.Format.PEM -\u003e \"PEM\"\n+ XDH.PrivateKey.Format.JWK,\n+ XDH.PrivateKey.Format.RAW -\u003e error(\"$format format is not supported\")","id":2323027616,"in_reply_to_id":null,"line":42,"original_commit_id":"c8488c9a2b9a24362bf1b5b7018a29e8e2286937","original_line":42,"path":"cryptography-providers/openssl3/api/src/commonMain/kotlin/algorithms/Openssl3XDH.kt","side":"RIGHT","start_line":null} -{"author":"whyoleg","body":"let's extract the function from `ecdh` implementation to `operations` package and use it here, instead of just copying ","diff_hunk":"@@ -0,0 +1,114 @@\n+/*\n+ * Copyright (c) 2025 Oleg Yukhnevich. Use of this source code is governed by the Apache 2.0 license.\n+ */\n+\n+package dev.whyoleg.cryptography.providers.openssl3.algorithms\n+\n+import dev.whyoleg.cryptography.algorithms.*\n+import dev.whyoleg.cryptography.materials.key.*\n+import dev.whyoleg.cryptography.operations.*\n+import dev.whyoleg.cryptography.providers.base.*\n+import dev.whyoleg.cryptography.providers.openssl3.internal.*\n+import dev.whyoleg.cryptography.providers.openssl3.internal.cinterop.*\n+import dev.whyoleg.cryptography.providers.openssl3.materials.*\n+import kotlinx.cinterop.*\n+import platform.posix.*\n+import kotlin.experimental.*\n+\n+internal object Openssl3XDH : XDH {\n+ private fun algorithmName(curve: XDH.Curve): String = when (curve) {\n+ XDH.Curve.X25519 -\u003e \"X25519\"\n+ XDH.Curve.X448 -\u003e \"X448\"\n+ }\n+\n+ override fun publicKeyDecoder(curve: XDH.Curve): KeyDecoder\u003cXDH.PublicKey.Format, XDH.PublicKey\u003e =\n+ object : Openssl3PublicKeyDecoder\u003cXDH.PublicKey.Format, XDH.PublicKey\u003e(algorithmName(curve)) {\n+ override fun inputType(format: XDH.PublicKey.Format): String = when (format) {\n+ XDH.PublicKey.Format.DER -\u003e \"DER\"\n+ XDH.PublicKey.Format.PEM -\u003e \"PEM\"\n+ XDH.PublicKey.Format.JWK,\n+ XDH.PublicKey.Format.RAW -\u003e error(\"$format format is not supported\")\n+ }\n+\n+ override fun wrapKey(key: CPointer\u003cEVP_PKEY\u003e): XDH.PublicKey = XdhPublicKey(key)\n+ }\n+\n+ override fun privateKeyDecoder(curve: XDH.Curve): KeyDecoder\u003cXDH.PrivateKey.Format, XDH.PrivateKey\u003e =\n+ object : Openssl3PrivateKeyDecoder\u003cXDH.PrivateKey.Format, XDH.PrivateKey\u003e(algorithmName(curve)) {\n+ override fun inputType(format: XDH.PrivateKey.Format): String = when (format) {\n+ XDH.PrivateKey.Format.DER -\u003e \"DER\"\n+ XDH.PrivateKey.Format.PEM -\u003e \"PEM\"\n+ XDH.PrivateKey.Format.JWK,\n+ XDH.PrivateKey.Format.RAW -\u003e error(\"$format format is not supported\")\n+ }\n+\n+ override fun wrapKey(key: CPointer\u003cEVP_PKEY\u003e): XDH.PrivateKey = XdhPrivateKey(key)\n+ }\n+\n+ override fun keyPairGenerator(curve: XDH.Curve): KeyGenerator\u003cXDH.KeyPair\u003e =\n+ object : Openssl3KeyPairGenerator\u003cXDH.KeyPair\u003e(algorithmName(curve)) {\n+ override fun MemScope.createParams(): CValuesRef\u003cOSSL_PARAM\u003e? = null\n+ override fun wrapKeyPair(keyPair: CPointer\u003cEVP_PKEY\u003e): XDH.KeyPair = XdhKeyPair(\n+ publicKey = XdhPublicKey(keyPair),\n+ privateKey = XdhPrivateKey(keyPair)\n+ )\n+ }\n+\n+ private class XdhKeyPair(\n+ override val publicKey: XDH.PublicKey,\n+ override val privateKey: XDH.PrivateKey,\n+ ) : XDH.KeyPair\n+\n+ private class XdhPublicKey(\n+ key: CPointer\u003cEVP_PKEY\u003e,\n+ ) : XDH.PublicKey, Openssl3PublicKeyEncodable\u003cXDH.PublicKey.Format\u003e(key), SharedSecretGenerator\u003cXDH.PrivateKey\u003e {\n+ override fun outputType(format: XDH.PublicKey.Format): String = when (format) {\n+ XDH.PublicKey.Format.DER -\u003e \"DER\"\n+ XDH.PublicKey.Format.PEM -\u003e \"PEM\"\n+ XDH.PublicKey.Format.JWK,\n+ XDH.PublicKey.Format.RAW -\u003e error(\"$format format is not supported\")\n+ }\n+\n+ override fun sharedSecretGenerator(): SharedSecretGenerator\u003cXDH.PrivateKey\u003e = this\n+ override fun generateSharedSecretToByteArrayBlocking(other: XDH.PrivateKey): ByteArray {\n+ check(other is XdhPrivateKey)\n+ return deriveSharedSecret(publicKey = key, privateKey = other.key)\n+ }\n+ }\n+\n+ private class XdhPrivateKey(\n+ key: CPointer\u003cEVP_PKEY\u003e,\n+ ) : XDH.PrivateKey, Openssl3PrivateKeyEncodable\u003cXDH.PrivateKey.Format\u003e(key), SharedSecretGenerator\u003cXDH.PublicKey\u003e {\n+ override fun outputType(format: XDH.PrivateKey.Format): String = when (format) {\n+ XDH.PrivateKey.Format.DER -\u003e \"DER\"\n+ XDH.PrivateKey.Format.PEM -\u003e \"PEM\"\n+ XDH.PrivateKey.Format.JWK,\n+ XDH.PrivateKey.Format.RAW -\u003e error(\"$format format is not supported\")\n+ }\n+\n+ override fun sharedSecretGenerator(): SharedSecretGenerator\u003cXDH.PublicKey\u003e = this\n+ override fun generateSharedSecretToByteArrayBlocking(other: XDH.PublicKey): ByteArray {\n+ check(other is XdhPublicKey)\n+ return deriveSharedSecret(publicKey = other.key, privateKey = key)\n+ }\n+ }\n+}\n+\n+@OptIn(UnsafeNumber::class)\n+private fun deriveSharedSecret(","id":2323033174,"in_reply_to_id":null,"line":98,"original_commit_id":"c8488c9a2b9a24362bf1b5b7018a29e8e2286937","original_line":98,"path":"cryptography-providers/openssl3/api/src/commonMain/kotlin/algorithms/Openssl3XDH.kt","side":"RIGHT","start_line":null} -{"author":"whyoleg","body":"we should support RAW format (openSSL should support it)\nsame in other places","diff_hunk":"@@ -0,0 +1,265 @@\n+/*\n+ * Copyright (c) 2025 Oleg Yukhnevich. Use of this source code is governed by the Apache 2.0 license.\n+ */\n+\n+package dev.whyoleg.cryptography.providers.openssl3.algorithms\n+\n+import dev.whyoleg.cryptography.algorithms.*\n+import dev.whyoleg.cryptography.materials.key.*\n+import dev.whyoleg.cryptography.operations.*\n+import dev.whyoleg.cryptography.providers.base.*\n+import dev.whyoleg.cryptography.providers.openssl3.internal.*\n+import dev.whyoleg.cryptography.providers.openssl3.internal.cinterop.*\n+import dev.whyoleg.cryptography.providers.openssl3.materials.*\n+import kotlinx.cinterop.*\n+import platform.posix.*\n+import kotlin.experimental.*\n+\n+internal object Openssl3EdDSA : EdDSA {\n+ private fun algorithmName(curve: EdDSA.Curve): String = when (curve) {\n+ EdDSA.Curve.Ed25519 -\u003e \"ED25519\"\n+ EdDSA.Curve.Ed448 -\u003e \"ED448\"\n+ }\n+\n+ override fun publicKeyDecoder(curve: EdDSA.Curve): KeyDecoder\u003cEdDSA.PublicKey.Format, EdDSA.PublicKey\u003e =\n+ object : Openssl3PublicKeyDecoder\u003cEdDSA.PublicKey.Format, EdDSA.PublicKey\u003e(algorithmName(curve)) {\n+ override fun inputType(format: EdDSA.PublicKey.Format): String = when (format) {\n+ EdDSA.PublicKey.Format.DER -\u003e \"DER\"\n+ EdDSA.PublicKey.Format.PEM -\u003e \"PEM\"\n+ EdDSA.PublicKey.Format.JWK,\n+ EdDSA.PublicKey.Format.RAW -\u003e error(\"$format format is not supported\")","id":2323039843,"in_reply_to_id":null,"line":30,"original_commit_id":"c8488c9a2b9a24362bf1b5b7018a29e8e2286937","original_line":30,"path":"cryptography-providers/openssl3/api/src/commonMain/kotlin/algorithms/Openssl3EdDSA.kt","side":"RIGHT","start_line":null} -{"author":"whyoleg","body":"we should support RAW format (openSSL should support it)","diff_hunk":"@@ -0,0 +1,114 @@\n+/*\n+ * Copyright (c) 2025 Oleg Yukhnevich. Use of this source code is governed by the Apache 2.0 license.\n+ */\n+\n+package dev.whyoleg.cryptography.providers.openssl3.algorithms\n+\n+import dev.whyoleg.cryptography.algorithms.*\n+import dev.whyoleg.cryptography.materials.key.*\n+import dev.whyoleg.cryptography.operations.*\n+import dev.whyoleg.cryptography.providers.base.*\n+import dev.whyoleg.cryptography.providers.openssl3.internal.*\n+import dev.whyoleg.cryptography.providers.openssl3.internal.cinterop.*\n+import dev.whyoleg.cryptography.providers.openssl3.materials.*\n+import kotlinx.cinterop.*\n+import platform.posix.*\n+import kotlin.experimental.*\n+\n+internal object Openssl3XDH : XDH {\n+ private fun algorithmName(curve: XDH.Curve): String = when (curve) {\n+ XDH.Curve.X25519 -\u003e \"X25519\"\n+ XDH.Curve.X448 -\u003e \"X448\"\n+ }\n+\n+ override fun publicKeyDecoder(curve: XDH.Curve): KeyDecoder\u003cXDH.PublicKey.Format, XDH.PublicKey\u003e =\n+ object : Openssl3PublicKeyDecoder\u003cXDH.PublicKey.Format, XDH.PublicKey\u003e(algorithmName(curve)) {\n+ override fun inputType(format: XDH.PublicKey.Format): String = when (format) {\n+ XDH.PublicKey.Format.DER -\u003e \"DER\"\n+ XDH.PublicKey.Format.PEM -\u003e \"PEM\"\n+ XDH.PublicKey.Format.JWK,\n+ XDH.PublicKey.Format.RAW -\u003e error(\"$format format is not supported\")\n+ }\n+\n+ override fun wrapKey(key: CPointer\u003cEVP_PKEY\u003e): XDH.PublicKey = XdhPublicKey(key)\n+ }\n+\n+ override fun privateKeyDecoder(curve: XDH.Curve): KeyDecoder\u003cXDH.PrivateKey.Format, XDH.PrivateKey\u003e =\n+ object : Openssl3PrivateKeyDecoder\u003cXDH.PrivateKey.Format, XDH.PrivateKey\u003e(algorithmName(curve)) {\n+ override fun inputType(format: XDH.PrivateKey.Format): String = when (format) {\n+ XDH.PrivateKey.Format.DER -\u003e \"DER\"\n+ XDH.PrivateKey.Format.PEM -\u003e \"PEM\"\n+ XDH.PrivateKey.Format.JWK,\n+ XDH.PrivateKey.Format.RAW -\u003e error(\"$format format is not supported\")\n+ }\n+\n+ override fun wrapKey(key: CPointer\u003cEVP_PKEY\u003e): XDH.PrivateKey = XdhPrivateKey(key)\n+ }\n+\n+ override fun keyPairGenerator(curve: XDH.Curve): KeyGenerator\u003cXDH.KeyPair\u003e =\n+ object : Openssl3KeyPairGenerator\u003cXDH.KeyPair\u003e(algorithmName(curve)) {\n+ override fun MemScope.createParams(): CValuesRef\u003cOSSL_PARAM\u003e? = null\n+ override fun wrapKeyPair(keyPair: CPointer\u003cEVP_PKEY\u003e): XDH.KeyPair = XdhKeyPair(\n+ publicKey = XdhPublicKey(keyPair),\n+ privateKey = XdhPrivateKey(keyPair)\n+ )\n+ }\n+\n+ private class XdhKeyPair(\n+ override val publicKey: XDH.PublicKey,\n+ override val privateKey: XDH.PrivateKey,\n+ ) : XDH.KeyPair\n+\n+ private class XdhPublicKey(\n+ key: CPointer\u003cEVP_PKEY\u003e,\n+ ) : XDH.PublicKey, Openssl3PublicKeyEncodable\u003cXDH.PublicKey.Format\u003e(key), SharedSecretGenerator\u003cXDH.PrivateKey\u003e {\n+ override fun outputType(format: XDH.PublicKey.Format): String = when (format) {\n+ XDH.PublicKey.Format.DER -\u003e \"DER\"\n+ XDH.PublicKey.Format.PEM -\u003e \"PEM\"\n+ XDH.PublicKey.Format.JWK,\n+ XDH.PublicKey.Format.RAW -\u003e error(\"$format format is not supported\")","id":2323041111,"in_reply_to_id":null,"line":69,"original_commit_id":"c8488c9a2b9a24362bf1b5b7018a29e8e2286937","original_line":69,"path":"cryptography-providers/openssl3/api/src/commonMain/kotlin/algorithms/Openssl3XDH.kt","side":"RIGHT","start_line":null} -{"author":"whyoleg","body":"we should support RAW format (openSSL should support it)","diff_hunk":"@@ -0,0 +1,114 @@\n+/*\n+ * Copyright (c) 2025 Oleg Yukhnevich. Use of this source code is governed by the Apache 2.0 license.\n+ */\n+\n+package dev.whyoleg.cryptography.providers.openssl3.algorithms\n+\n+import dev.whyoleg.cryptography.algorithms.*\n+import dev.whyoleg.cryptography.materials.key.*\n+import dev.whyoleg.cryptography.operations.*\n+import dev.whyoleg.cryptography.providers.base.*\n+import dev.whyoleg.cryptography.providers.openssl3.internal.*\n+import dev.whyoleg.cryptography.providers.openssl3.internal.cinterop.*\n+import dev.whyoleg.cryptography.providers.openssl3.materials.*\n+import kotlinx.cinterop.*\n+import platform.posix.*\n+import kotlin.experimental.*\n+\n+internal object Openssl3XDH : XDH {\n+ private fun algorithmName(curve: XDH.Curve): String = when (curve) {\n+ XDH.Curve.X25519 -\u003e \"X25519\"\n+ XDH.Curve.X448 -\u003e \"X448\"\n+ }\n+\n+ override fun publicKeyDecoder(curve: XDH.Curve): KeyDecoder\u003cXDH.PublicKey.Format, XDH.PublicKey\u003e =\n+ object : Openssl3PublicKeyDecoder\u003cXDH.PublicKey.Format, XDH.PublicKey\u003e(algorithmName(curve)) {\n+ override fun inputType(format: XDH.PublicKey.Format): String = when (format) {\n+ XDH.PublicKey.Format.DER -\u003e \"DER\"\n+ XDH.PublicKey.Format.PEM -\u003e \"PEM\"\n+ XDH.PublicKey.Format.JWK,\n+ XDH.PublicKey.Format.RAW -\u003e error(\"$format format is not supported\")\n+ }\n+\n+ override fun wrapKey(key: CPointer\u003cEVP_PKEY\u003e): XDH.PublicKey = XdhPublicKey(key)\n+ }\n+\n+ override fun privateKeyDecoder(curve: XDH.Curve): KeyDecoder\u003cXDH.PrivateKey.Format, XDH.PrivateKey\u003e =\n+ object : Openssl3PrivateKeyDecoder\u003cXDH.PrivateKey.Format, XDH.PrivateKey\u003e(algorithmName(curve)) {\n+ override fun inputType(format: XDH.PrivateKey.Format): String = when (format) {\n+ XDH.PrivateKey.Format.DER -\u003e \"DER\"\n+ XDH.PrivateKey.Format.PEM -\u003e \"PEM\"\n+ XDH.PrivateKey.Format.JWK,\n+ XDH.PrivateKey.Format.RAW -\u003e error(\"$format format is not supported\")\n+ }\n+\n+ override fun wrapKey(key: CPointer\u003cEVP_PKEY\u003e): XDH.PrivateKey = XdhPrivateKey(key)\n+ }\n+\n+ override fun keyPairGenerator(curve: XDH.Curve): KeyGenerator\u003cXDH.KeyPair\u003e =\n+ object : Openssl3KeyPairGenerator\u003cXDH.KeyPair\u003e(algorithmName(curve)) {\n+ override fun MemScope.createParams(): CValuesRef\u003cOSSL_PARAM\u003e? = null\n+ override fun wrapKeyPair(keyPair: CPointer\u003cEVP_PKEY\u003e): XDH.KeyPair = XdhKeyPair(\n+ publicKey = XdhPublicKey(keyPair),\n+ privateKey = XdhPrivateKey(keyPair)\n+ )\n+ }\n+\n+ private class XdhKeyPair(\n+ override val publicKey: XDH.PublicKey,\n+ override val privateKey: XDH.PrivateKey,\n+ ) : XDH.KeyPair\n+\n+ private class XdhPublicKey(\n+ key: CPointer\u003cEVP_PKEY\u003e,\n+ ) : XDH.PublicKey, Openssl3PublicKeyEncodable\u003cXDH.PublicKey.Format\u003e(key), SharedSecretGenerator\u003cXDH.PrivateKey\u003e {\n+ override fun outputType(format: XDH.PublicKey.Format): String = when (format) {\n+ XDH.PublicKey.Format.DER -\u003e \"DER\"\n+ XDH.PublicKey.Format.PEM -\u003e \"PEM\"\n+ XDH.PublicKey.Format.JWK,\n+ XDH.PublicKey.Format.RAW -\u003e error(\"$format format is not supported\")\n+ }\n+\n+ override fun sharedSecretGenerator(): SharedSecretGenerator\u003cXDH.PrivateKey\u003e = this\n+ override fun generateSharedSecretToByteArrayBlocking(other: XDH.PrivateKey): ByteArray {\n+ check(other is XdhPrivateKey)\n+ return deriveSharedSecret(publicKey = key, privateKey = other.key)\n+ }\n+ }\n+\n+ private class XdhPrivateKey(\n+ key: CPointer\u003cEVP_PKEY\u003e,\n+ ) : XDH.PrivateKey, Openssl3PrivateKeyEncodable\u003cXDH.PrivateKey.Format\u003e(key), SharedSecretGenerator\u003cXDH.PublicKey\u003e {\n+ override fun outputType(format: XDH.PrivateKey.Format): String = when (format) {\n+ XDH.PrivateKey.Format.DER -\u003e \"DER\"\n+ XDH.PrivateKey.Format.PEM -\u003e \"PEM\"\n+ XDH.PrivateKey.Format.JWK,\n+ XDH.PrivateKey.Format.RAW -\u003e error(\"$format format is not supported\")","id":2323041303,"in_reply_to_id":null,"line":86,"original_commit_id":"c8488c9a2b9a24362bf1b5b7018a29e8e2286937","original_line":86,"path":"cryptography-providers/openssl3/api/src/commonMain/kotlin/algorithms/Openssl3XDH.kt","side":"RIGHT","start_line":null} -{"author":"whyoleg","body":"what is the reason for reimplementing this from scratch? It looks like we only need to make `hashAlgorithm` nullable in `Openssl3DigestSignatureVerifier` and `Openssl3DigestSignatureGenerator` no?","diff_hunk":"@@ -0,0 +1,265 @@\n+/*\n+ * Copyright (c) 2025 Oleg Yukhnevich. Use of this source code is governed by the Apache 2.0 license.\n+ */\n+\n+package dev.whyoleg.cryptography.providers.openssl3.algorithms\n+\n+import dev.whyoleg.cryptography.algorithms.*\n+import dev.whyoleg.cryptography.materials.key.*\n+import dev.whyoleg.cryptography.operations.*\n+import dev.whyoleg.cryptography.providers.base.*\n+import dev.whyoleg.cryptography.providers.openssl3.internal.*\n+import dev.whyoleg.cryptography.providers.openssl3.internal.cinterop.*\n+import dev.whyoleg.cryptography.providers.openssl3.materials.*\n+import kotlinx.cinterop.*\n+import platform.posix.*\n+import kotlin.experimental.*\n+\n+internal object Openssl3EdDSA : EdDSA {\n+ private fun algorithmName(curve: EdDSA.Curve): String = when (curve) {\n+ EdDSA.Curve.Ed25519 -\u003e \"ED25519\"\n+ EdDSA.Curve.Ed448 -\u003e \"ED448\"\n+ }\n+\n+ override fun publicKeyDecoder(curve: EdDSA.Curve): KeyDecoder\u003cEdDSA.PublicKey.Format, EdDSA.PublicKey\u003e =\n+ object : Openssl3PublicKeyDecoder\u003cEdDSA.PublicKey.Format, EdDSA.PublicKey\u003e(algorithmName(curve)) {\n+ override fun inputType(format: EdDSA.PublicKey.Format): String = when (format) {\n+ EdDSA.PublicKey.Format.DER -\u003e \"DER\"\n+ EdDSA.PublicKey.Format.PEM -\u003e \"PEM\"\n+ EdDSA.PublicKey.Format.JWK,\n+ EdDSA.PublicKey.Format.RAW -\u003e error(\"$format format is not supported\")\n+ }\n+\n+ override fun wrapKey(key: CPointer\u003cEVP_PKEY\u003e): EdDSA.PublicKey = EdDsaPublicKey(key)\n+ }\n+\n+ override fun privateKeyDecoder(curve: EdDSA.Curve): KeyDecoder\u003cEdDSA.PrivateKey.Format, EdDSA.PrivateKey\u003e =\n+ object : Openssl3PrivateKeyDecoder\u003cEdDSA.PrivateKey.Format, EdDSA.PrivateKey\u003e(algorithmName(curve)) {\n+ override fun inputType(format: EdDSA.PrivateKey.Format): String = when (format) {\n+ EdDSA.PrivateKey.Format.DER -\u003e \"DER\"\n+ EdDSA.PrivateKey.Format.PEM -\u003e \"PEM\"\n+ EdDSA.PrivateKey.Format.JWK,\n+ EdDSA.PrivateKey.Format.RAW -\u003e error(\"$format format is not supported\")\n+ }\n+\n+ override fun wrapKey(key: CPointer\u003cEVP_PKEY\u003e): EdDSA.PrivateKey = EdDsaPrivateKey(key)\n+ }\n+\n+ override fun keyPairGenerator(curve: EdDSA.Curve): KeyGenerator\u003cEdDSA.KeyPair\u003e =\n+ object : Openssl3KeyPairGenerator\u003cEdDSA.KeyPair\u003e(algorithmName(curve)) {\n+ override fun MemScope.createParams(): CValuesRef\u003cOSSL_PARAM\u003e? = null\n+ override fun wrapKeyPair(keyPair: CPointer\u003cEVP_PKEY\u003e): EdDSA.KeyPair = EdDsaKeyPair(\n+ publicKey = EdDsaPublicKey(keyPair),\n+ privateKey = EdDsaPrivateKey(keyPair)\n+ )\n+ }\n+\n+ private class EdDsaKeyPair(\n+ override val publicKey: EdDSA.PublicKey,\n+ override val privateKey: EdDSA.PrivateKey,\n+ ) : EdDSA.KeyPair\n+\n+ private class EdDsaPublicKey(\n+ key: CPointer\u003cEVP_PKEY\u003e,\n+ ) : EdDSA.PublicKey, Openssl3PublicKeyEncodable\u003cEdDSA.PublicKey.Format\u003e(key) {\n+ override fun outputType(format: EdDSA.PublicKey.Format): String = when (format) {\n+ EdDSA.PublicKey.Format.DER -\u003e \"DER\"\n+ EdDSA.PublicKey.Format.PEM -\u003e \"PEM\"\n+ EdDSA.PublicKey.Format.JWK,\n+ EdDSA.PublicKey.Format.RAW -\u003e error(\"$format format is not supported\")\n+ }\n+\n+ override fun signatureVerifier(): SignatureVerifier = EdDsaSignatureVerifier(key)\n+ }\n+\n+ private class EdDsaPrivateKey(\n+ key: CPointer\u003cEVP_PKEY\u003e,\n+ ) : EdDSA.PrivateKey, Openssl3PrivateKeyEncodable\u003cEdDSA.PrivateKey.Format\u003e(key) {\n+ override fun outputType(format: EdDSA.PrivateKey.Format): String = when (format) {\n+ EdDSA.PrivateKey.Format.DER -\u003e \"DER\"\n+ EdDSA.PrivateKey.Format.PEM -\u003e \"PEM\"\n+ EdDSA.PrivateKey.Format.JWK,\n+ EdDSA.PrivateKey.Format.RAW -\u003e error(\"$format format is not supported\")\n+ }\n+\n+ override fun signatureGenerator(): SignatureGenerator = EdDsaSignatureGenerator(key)\n+ }\n+}\n+\n+@OptIn(ExperimentalNativeApi::class, UnsafeNumber::class)\n+private class EdDsaSignatureGenerator(","id":2323048552,"in_reply_to_id":null,"line":90,"original_commit_id":"c8488c9a2b9a24362bf1b5b7018a29e8e2286937","original_line":90,"path":"cryptography-providers/openssl3/api/src/commonMain/kotlin/algorithms/Openssl3EdDSA.kt","side":"RIGHT","start_line":null} -{"author":"whyoleg","body":"why can't we support it? (same in other places in JDK provider)","diff_hunk":"@@ -0,0 +1,89 @@\n+package dev.whyoleg.cryptography.providers.jdk.algorithms\n+\n+import dev.whyoleg.cryptography.algorithms.*\n+import dev.whyoleg.cryptography.materials.key.*\n+import dev.whyoleg.cryptography.operations.*\n+import dev.whyoleg.cryptography.providers.jdk.*\n+import dev.whyoleg.cryptography.providers.jdk.materials.*\n+import dev.whyoleg.cryptography.providers.base.materials.*\n+import dev.whyoleg.cryptography.providers.jdk.operations.*\n+import dev.whyoleg.cryptography.serialization.pem.*\n+\n+internal class JdkEdDSA(private val state: JdkCryptographyState) : EdDSA {\n+ private fun curveName(curve: EdDSA.Curve): String = when (curve) {\n+ EdDSA.Curve.Ed25519 -\u003e \"Ed25519\"\n+ EdDSA.Curve.Ed448 -\u003e \"Ed448\"\n+ }\n+\n+ override fun publicKeyDecoder(curve: EdDSA.Curve): KeyDecoder\u003cEdDSA.PublicKey.Format, EdDSA.PublicKey\u003e =\n+ object : JdkPublicKeyDecoder\u003cEdDSA.PublicKey.Format, EdDSA.PublicKey\u003e(state, curveName(curve)) {\n+ override fun JPublicKey.convert(): EdDSA.PublicKey = EdDsaPublicKey(state, this)\n+\n+ override fun decodeFromByteArrayBlocking(format: EdDSA.PublicKey.Format, bytes: ByteArray): EdDSA.PublicKey = when (format) {\n+ EdDSA.PublicKey.Format.JWK -\u003e error(\"JWK is not supported\")\n+ EdDSA.PublicKey.Format.RAW -\u003e TODO(\"RAW encoding is not supported yet\")","id":2323050460,"in_reply_to_id":null,"line":24,"original_commit_id":"c8488c9a2b9a24362bf1b5b7018a29e8e2286937","original_line":24,"path":"cryptography-providers/jdk/src/jvmMain/kotlin/algorithms/JdkEdDSA.kt","side":"RIGHT","start_line":null} -{"author":"whyoleg","body":"OIDs should be extracted and added into `asn1-modules` with correct names (same for xdh)","diff_hunk":"@@ -0,0 +1,182 @@\n+/*\n+ * Copyright (c) 2025 Oleg Yukhnevich. Use of this source code is governed by the Apache 2.0 license.\n+ */\n+\n+package dev.whyoleg.cryptography.providers.cryptokit.algorithms\n+\n+import dev.whyoleg.cryptography.algorithms.*\n+import dev.whyoleg.cryptography.materials.key.*\n+import dev.whyoleg.cryptography.operations.*\n+import dev.whyoleg.cryptography.providers.cryptokit.internal.*\n+import dev.whyoleg.cryptography.providers.cryptokit.internal.swiftinterop.*\n+import dev.whyoleg.cryptography.providers.base.*\n+import dev.whyoleg.cryptography.providers.base.materials.*\n+import dev.whyoleg.cryptography.serialization.asn1.*\n+import dev.whyoleg.cryptography.serialization.asn1.modules.*\n+import dev.whyoleg.cryptography.serialization.pem.*\n+import kotlinx.cinterop.*\n+import platform.Foundation.*\n+\n+internal object CryptoKitEdDSA : EdDSA {\n+ override fun publicKeyDecoder(curve: EdDSA.Curve): KeyDecoder\u003cEdDSA.PublicKey.Format, EdDSA.PublicKey\u003e {\n+ check(curve == EdDSA.Curve.Ed25519) { \"CryptoKit supports Ed25519 only\" }\n+ return object : KeyDecoder\u003cEdDSA.PublicKey.Format, EdDSA.PublicKey\u003e {\n+ override fun decodeFromByteArrayBlocking(format: EdDSA.PublicKey.Format, bytes: ByteArray): EdDSA.PublicKey = when (format) {\n+ EdDSA.PublicKey.Format.RAW -\u003e EdPublic(\n+ swiftTry\u003cSwiftEdDsaPublicKey\u003e { error -\u003e bytes.useNSData { SwiftEdDsaPublicKey.decodeRawWithRawRepresentation(it, error) } }\n+ )\n+ EdDSA.PublicKey.Format.DER -\u003e {\n+ val raw = unwrapSubjectPublicKeyInfo(ObjectIdentifier(\"1.3.101.112\"), bytes)","id":2323056088,"in_reply_to_id":null,"line":29,"original_commit_id":"c8488c9a2b9a24362bf1b5b7018a29e8e2286937","original_line":29,"path":"cryptography-providers/cryptokit/src/commonMain/kotlin/algorithms/CryptoKitEdDSA.kt","side":"RIGHT","start_line":null} -{"author":"whyoleg","body":"it would be nice to add this into `cryptokit.md`: that only this curve is supported (same for xdh) ","diff_hunk":"@@ -0,0 +1,182 @@\n+/*\n+ * Copyright (c) 2025 Oleg Yukhnevich. Use of this source code is governed by the Apache 2.0 license.\n+ */\n+\n+package dev.whyoleg.cryptography.providers.cryptokit.algorithms\n+\n+import dev.whyoleg.cryptography.algorithms.*\n+import dev.whyoleg.cryptography.materials.key.*\n+import dev.whyoleg.cryptography.operations.*\n+import dev.whyoleg.cryptography.providers.cryptokit.internal.*\n+import dev.whyoleg.cryptography.providers.cryptokit.internal.swiftinterop.*\n+import dev.whyoleg.cryptography.providers.base.*\n+import dev.whyoleg.cryptography.providers.base.materials.*\n+import dev.whyoleg.cryptography.serialization.asn1.*\n+import dev.whyoleg.cryptography.serialization.asn1.modules.*\n+import dev.whyoleg.cryptography.serialization.pem.*\n+import kotlinx.cinterop.*\n+import platform.Foundation.*\n+\n+internal object CryptoKitEdDSA : EdDSA {\n+ override fun publicKeyDecoder(curve: EdDSA.Curve): KeyDecoder\u003cEdDSA.PublicKey.Format, EdDSA.PublicKey\u003e {\n+ check(curve == EdDSA.Curve.Ed25519) { \"CryptoKit supports Ed25519 only\" }","id":2323060834,"in_reply_to_id":null,"line":22,"original_commit_id":"c8488c9a2b9a24362bf1b5b7018a29e8e2286937","original_line":22,"path":"cryptography-providers/cryptokit/src/commonMain/kotlin/algorithms/CryptoKitEdDSA.kt","side":"RIGHT","start_line":null} -{"author":"whyoleg","body":"should be removed","diff_hunk":"@@ -0,0 +1,130 @@\n+/*\n+ * Copyright (c) 2025 Oleg Yukhnevich. Use of this source code is governed by the Apache 2.0 license.\n+ */\n+\n+package dev.whyoleg.cryptography.providers.cryptokit.algorithms\n+\n+import dev.whyoleg.cryptography.algorithms.*\n+import dev.whyoleg.cryptography.materials.key.*\n+import dev.whyoleg.cryptography.operations.*\n+import dev.whyoleg.cryptography.providers.cryptokit.internal.*\n+import dev.whyoleg.cryptography.providers.cryptokit.internal.swiftinterop.*\n+import dev.whyoleg.cryptography.providers.base.*\n+import dev.whyoleg.cryptography.providers.base.materials.*\n+import dev.whyoleg.cryptography.serialization.asn1.*\n+import dev.whyoleg.cryptography.serialization.asn1.modules.*\n+import dev.whyoleg.cryptography.serialization.pem.*\n+import kotlinx.cinterop.*\n+import platform.Foundation.*\n+\n+internal object CryptoKitXDH : XDH {\n+ override fun publicKeyDecoder(curve: XDH.Curve): KeyDecoder\u003cXDH.PublicKey.Format, XDH.PublicKey\u003e {\n+ check(curve == XDH.Curve.X25519) { \"CryptoKit supports X25519 only\" }\n+ return object : KeyDecoder\u003cXDH.PublicKey.Format, XDH.PublicKey\u003e {\n+ override fun decodeFromByteArrayBlocking(format: XDH.PublicKey.Format, bytes: ByteArray): XDH.PublicKey = when (format) {\n+ XDH.PublicKey.Format.RAW -\u003e XPublic(\n+ swiftTry\u003cSwiftXdhPublicKey\u003e { error -\u003e bytes.useNSData { SwiftXdhPublicKey.decodeRawWithRawRepresentation(it, error) } }\n+ )\n+ XDH.PublicKey.Format.DER -\u003e {\n+ val raw = unwrapSubjectPublicKeyInfo(ObjectIdentifier(\"1.3.101.110\"), bytes)\n+ XPublic(swiftTry { error -\u003e raw.useNSData { SwiftXdhPublicKey.decodeRawWithRawRepresentation(it, error) } })\n+ }\n+ XDH.PublicKey.Format.PEM -\u003e {\n+ val der = unwrapPem(PemLabel.PublicKey, bytes)\n+ val raw = unwrapSubjectPublicKeyInfo(ObjectIdentifier(\"1.3.101.110\"), der)\n+ XPublic(swiftTry { error -\u003e raw.useNSData { SwiftXdhPublicKey.decodeRawWithRawRepresentation(it, error) } })\n+ }\n+ else -\u003e error(\"$format is not supported by CryptoKit XDH\")\n+ }\n+ }\n+ }\n+\n+ override fun privateKeyDecoder(curve: XDH.Curve): KeyDecoder\u003cXDH.PrivateKey.Format, XDH.PrivateKey\u003e {\n+ check(curve == XDH.Curve.X25519) { \"CryptoKit supports X25519 only\" }\n+ return object : KeyDecoder\u003cXDH.PrivateKey.Format, XDH.PrivateKey\u003e {\n+ override fun decodeFromByteArrayBlocking(format: XDH.PrivateKey.Format, bytes: ByteArray): XDH.PrivateKey = when (format) {\n+ XDH.PrivateKey.Format.RAW -\u003e XPrivate(\n+ swiftTry\u003cSwiftXdhPrivateKey\u003e { error -\u003e bytes.useNSData { SwiftXdhPrivateKey.decodeRawWithRawRepresentation(it, error) } }\n+ )\n+ XDH.PrivateKey.Format.DER -\u003e {\n+ val raw = unwrapPrivateKeyInfo(ObjectIdentifier(\"1.3.101.110\"), bytes)\n+ XPrivate(swiftTry { error -\u003e raw.useNSData { SwiftXdhPrivateKey.decodeRawWithRawRepresentation(it, error) } })\n+ }\n+ XDH.PrivateKey.Format.PEM -\u003e {\n+ val der = unwrapPem(PemLabel.PrivateKey, bytes)\n+ val raw = unwrapPrivateKeyInfo(ObjectIdentifier(\"1.3.101.110\"), der)\n+ XPrivate(swiftTry { error -\u003e raw.useNSData { SwiftXdhPrivateKey.decodeRawWithRawRepresentation(it, error) } })\n+ }\n+ else -\u003e error(\"$format is not supported by CryptoKit XDH\")\n+ }\n+ }\n+ }\n+\n+ override fun keyPairGenerator(curve: XDH.Curve): KeyGenerator\u003cXDH.KeyPair\u003e {\n+ check(curve == XDH.Curve.X25519) { \"CryptoKit supports X25519 only\" }\n+ return object : KeyGenerator\u003cXDH.KeyPair\u003e {\n+ override fun generateKeyBlocking(): XDH.KeyPair {\n+ val p = SwiftXdhPrivateKey.generate()\n+ return XKeyPair(XPublic(p.publicKey()), XPrivate(p))\n+ }\n+ }\n+ }\n+\n+ private class XKeyPair(\n+ override val publicKey: XDH.PublicKey,\n+ override val privateKey: XDH.PrivateKey,\n+ ) : XDH.KeyPair\n+\n+ private class XPublic(\n+ val key: SwiftXdhPublicKey,\n+ ) : XDH.PublicKey, SharedSecretGenerator\u003cXDH.PrivateKey\u003e {\n+ override fun encodeToByteArrayBlocking(format: XDH.PublicKey.Format): ByteArray = when (format) {\n+ XDH.PublicKey.Format.RAW -\u003e key.rawRepresentation().toByteArray()\n+ XDH.PublicKey.Format.DER -\u003e wrapSubjectPublicKeyInfo(\n+ UnknownKeyAlgorithmIdentifier(ObjectIdentifier(\"1.3.101.110\")),\n+ key.rawRepresentation().toByteArray()\n+ )\n+ XDH.PublicKey.Format.PEM -\u003e wrapPem(\n+ PemLabel.PublicKey,\n+ wrapSubjectPublicKeyInfo(\n+ UnknownKeyAlgorithmIdentifier(ObjectIdentifier(\"1.3.101.110\")),\n+ key.rawRepresentation().toByteArray()\n+ )\n+ )\n+ else -\u003e error(\"$format is not supported by CryptoKit XDH\")\n+ }\n+ override fun sharedSecretGenerator(): SharedSecretGenerator\u003cXDH.PrivateKey\u003e = this\n+ override fun generateSharedSecretToByteArrayBlocking(other: XDH.PrivateKey): ByteArray {\n+ require(other is XPrivate)\n+ return swiftTry { error -\u003e other.key.deriveSecretWith(key, error) }.toByteArray()\n+ }\n+ }\n+\n+ private class XPrivate(\n+ val key: SwiftXdhPrivateKey,\n+ ) : XDH.PrivateKey, SharedSecretGenerator\u003cXDH.PublicKey\u003e {\n+ override fun encodeToByteArrayBlocking(format: XDH.PrivateKey.Format): ByteArray = when (format) {\n+ XDH.PrivateKey.Format.RAW -\u003e key.rawRepresentation().toByteArray()\n+ XDH.PrivateKey.Format.DER -\u003e wrapPrivateKeyInfo(\n+ 0,\n+ UnknownKeyAlgorithmIdentifier(ObjectIdentifier(\"1.3.101.110\")),\n+ key.rawRepresentation().toByteArray()\n+ )\n+ XDH.PrivateKey.Format.PEM -\u003e wrapPem(\n+ PemLabel.PrivateKey,\n+ wrapPrivateKeyInfo(\n+ 0,\n+ UnknownKeyAlgorithmIdentifier(ObjectIdentifier(\"1.3.101.110\")),\n+ key.rawRepresentation().toByteArray()\n+ )\n+ )\n+ else -\u003e error(\"$format is not supported by CryptoKit XDH\")\n+ }\n+ override fun sharedSecretGenerator(): SharedSecretGenerator\u003cXDH.PublicKey\u003e = this\n+ override fun generateSharedSecretToByteArrayBlocking(other: XDH.PublicKey): ByteArray {\n+ require(other is XPublic)\n+ return swiftTry { error -\u003e key.deriveSecretWith(other.key, error) }.toByteArray()\n+ }\n+ }\n+}\n+// NSData helpers provided by cryptography-providers-base","id":2323064442,"in_reply_to_id":null,"line":130,"original_commit_id":"c8488c9a2b9a24362bf1b5b7018a29e8e2286937","original_line":130,"path":"cryptography-providers/cryptokit/src/commonMain/kotlin/algorithms/CryptoKitXDH.kt","side":"RIGHT","start_line":null} -{"author":"whyoleg","body":"The implementation looks similar to `SecSignFunction` from apple provider. It would be nice to extract the base implementation into `AccumulatingSignFunction` and place it into `provider-base`, so that it will be shared in the same way `AccumulatingCipherFunction` is shared and used in this cryptokit provider\nSame for `verify`","diff_hunk":"@@ -0,0 +1,182 @@\n+/*\n+ * Copyright (c) 2025 Oleg Yukhnevich. Use of this source code is governed by the Apache 2.0 license.\n+ */\n+\n+package dev.whyoleg.cryptography.providers.cryptokit.algorithms\n+\n+import dev.whyoleg.cryptography.algorithms.*\n+import dev.whyoleg.cryptography.materials.key.*\n+import dev.whyoleg.cryptography.operations.*\n+import dev.whyoleg.cryptography.providers.cryptokit.internal.*\n+import dev.whyoleg.cryptography.providers.cryptokit.internal.swiftinterop.*\n+import dev.whyoleg.cryptography.providers.base.*\n+import dev.whyoleg.cryptography.providers.base.materials.*\n+import dev.whyoleg.cryptography.serialization.asn1.*\n+import dev.whyoleg.cryptography.serialization.asn1.modules.*\n+import dev.whyoleg.cryptography.serialization.pem.*\n+import kotlinx.cinterop.*\n+import platform.Foundation.*\n+\n+internal object CryptoKitEdDSA : EdDSA {\n+ override fun publicKeyDecoder(curve: EdDSA.Curve): KeyDecoder\u003cEdDSA.PublicKey.Format, EdDSA.PublicKey\u003e {\n+ check(curve == EdDSA.Curve.Ed25519) { \"CryptoKit supports Ed25519 only\" }\n+ return object : KeyDecoder\u003cEdDSA.PublicKey.Format, EdDSA.PublicKey\u003e {\n+ override fun decodeFromByteArrayBlocking(format: EdDSA.PublicKey.Format, bytes: ByteArray): EdDSA.PublicKey = when (format) {\n+ EdDSA.PublicKey.Format.RAW -\u003e EdPublic(\n+ swiftTry\u003cSwiftEdDsaPublicKey\u003e { error -\u003e bytes.useNSData { SwiftEdDsaPublicKey.decodeRawWithRawRepresentation(it, error) } }\n+ )\n+ EdDSA.PublicKey.Format.DER -\u003e {\n+ val raw = unwrapSubjectPublicKeyInfo(ObjectIdentifier(\"1.3.101.112\"), bytes)\n+ EdPublic(swiftTry { error -\u003e raw.useNSData { SwiftEdDsaPublicKey.decodeRawWithRawRepresentation(it, error) } })\n+ }\n+ EdDSA.PublicKey.Format.PEM -\u003e {\n+ val der = unwrapPem(PemLabel.PublicKey, bytes)\n+ val raw = unwrapSubjectPublicKeyInfo(ObjectIdentifier(\"1.3.101.112\"), der)\n+ EdPublic(swiftTry { error -\u003e raw.useNSData { SwiftEdDsaPublicKey.decodeRawWithRawRepresentation(it, error) } })\n+ }\n+ else -\u003e error(\"$format is not supported by CryptoKit EdDSA\")\n+ }\n+ }\n+ }\n+\n+ override fun privateKeyDecoder(curve: EdDSA.Curve): KeyDecoder\u003cEdDSA.PrivateKey.Format, EdDSA.PrivateKey\u003e {\n+ check(curve == EdDSA.Curve.Ed25519) { \"CryptoKit supports Ed25519 only\" }\n+ return object : KeyDecoder\u003cEdDSA.PrivateKey.Format, EdDSA.PrivateKey\u003e {\n+ override fun decodeFromByteArrayBlocking(format: EdDSA.PrivateKey.Format, bytes: ByteArray): EdDSA.PrivateKey = when (format) {\n+ EdDSA.PrivateKey.Format.RAW -\u003e EdPrivate(\n+ swiftTry\u003cSwiftEdDsaPrivateKey\u003e { error -\u003e bytes.useNSData { SwiftEdDsaPrivateKey.decodeRawWithRawRepresentation(it, error) } }\n+ )\n+ EdDSA.PrivateKey.Format.DER -\u003e {\n+ val raw = unwrapPrivateKeyInfo(ObjectIdentifier(\"1.3.101.112\"), bytes)\n+ EdPrivate(swiftTry { error -\u003e raw.useNSData { SwiftEdDsaPrivateKey.decodeRawWithRawRepresentation(it, error) } })\n+ }\n+ EdDSA.PrivateKey.Format.PEM -\u003e {\n+ val der = unwrapPem(PemLabel.PrivateKey, bytes)\n+ val raw = unwrapPrivateKeyInfo(ObjectIdentifier(\"1.3.101.112\"), der)\n+ EdPrivate(swiftTry { error -\u003e raw.useNSData { SwiftEdDsaPrivateKey.decodeRawWithRawRepresentation(it, error) } })\n+ }\n+ else -\u003e error(\"$format is not supported by CryptoKit EdDSA\")\n+ }\n+ }\n+ }\n+\n+ override fun keyPairGenerator(curve: EdDSA.Curve): KeyGenerator\u003cEdDSA.KeyPair\u003e {\n+ check(curve == EdDSA.Curve.Ed25519) { \"CryptoKit supports Ed25519 only\" }\n+ return object : KeyGenerator\u003cEdDSA.KeyPair\u003e {\n+ override fun generateKeyBlocking(): EdDSA.KeyPair {\n+ val p = SwiftEdDsaPrivateKey.generate()\n+ return EdKeyPair(EdPublic(p.publicKey()), EdPrivate(p))\n+ }\n+ }\n+ }\n+\n+ private class EdKeyPair(\n+ override val publicKey: EdDSA.PublicKey,\n+ override val privateKey: EdDSA.PrivateKey,\n+ ) : EdDSA.KeyPair\n+\n+ private class EdPublic(\n+ val key: SwiftEdDsaPublicKey,\n+ ) : EdDSA.PublicKey {\n+ override fun encodeToByteArrayBlocking(format: EdDSA.PublicKey.Format): ByteArray = when (format) {\n+ EdDSA.PublicKey.Format.RAW -\u003e key.rawRepresentation().toByteArray()\n+ EdDSA.PublicKey.Format.DER -\u003e wrapSubjectPublicKeyInfo(\n+ UnknownKeyAlgorithmIdentifier(ObjectIdentifier(\"1.3.101.112\")),\n+ key.rawRepresentation().toByteArray()\n+ )\n+ EdDSA.PublicKey.Format.PEM -\u003e wrapPem(\n+ PemLabel.PublicKey,\n+ wrapSubjectPublicKeyInfo(\n+ UnknownKeyAlgorithmIdentifier(ObjectIdentifier(\"1.3.101.112\")),\n+ key.rawRepresentation().toByteArray()\n+ )\n+ )\n+ else -\u003e error(\"$format is not supported by CryptoKit EdDSA\")\n+ }\n+\n+ override fun signatureVerifier(): SignatureVerifier = object : SignatureVerifier {\n+ override fun createVerifyFunction(): VerifyFunction = EdVerifyFunction(key)\n+ }\n+ }\n+\n+ private class EdPrivate(\n+ val key: SwiftEdDsaPrivateKey,\n+ ) : EdDSA.PrivateKey {\n+ override fun encodeToByteArrayBlocking(format: EdDSA.PrivateKey.Format): ByteArray = when (format) {\n+ EdDSA.PrivateKey.Format.RAW -\u003e key.rawRepresentation().toByteArray()\n+ EdDSA.PrivateKey.Format.DER -\u003e wrapPrivateKeyInfo(\n+ 0,\n+ UnknownKeyAlgorithmIdentifier(ObjectIdentifier(\"1.3.101.112\")),\n+ key.rawRepresentation().toByteArray()\n+ )\n+ EdDSA.PrivateKey.Format.PEM -\u003e wrapPem(\n+ PemLabel.PrivateKey,\n+ wrapPrivateKeyInfo(\n+ 0,\n+ UnknownKeyAlgorithmIdentifier(ObjectIdentifier(\"1.3.101.112\")),\n+ key.rawRepresentation().toByteArray()\n+ )\n+ )\n+ else -\u003e error(\"$format is not supported by CryptoKit EdDSA\")\n+ }\n+\n+ override fun signatureGenerator(): SignatureGenerator = object : SignatureGenerator {\n+ override fun createSignFunction(): SignFunction = EdSignFunction(key)\n+ override fun generateSignatureBlocking(data: ByteArray): ByteArray =\n+ swiftTry { error -\u003e data.useNSData { key.signWithMessage(it, error) } }.toByteArray()\n+ }\n+ }\n+}\n+\n+private class EdSignFunction(","id":2323074323,"in_reply_to_id":null,"line":131,"original_commit_id":"c8488c9a2b9a24362bf1b5b7018a29e8e2286937","original_line":131,"path":"cryptography-providers/cryptokit/src/commonMain/kotlin/algorithms/CryptoKitEdDSA.kt","side":"RIGHT","start_line":null} From 3d7885ac593bd50b71029a2572215035396639ec Mon Sep 17 00:00:00 2001 From: Aria Wisp Date: Thu, 4 Sep 2025 15:15:56 -0600 Subject: [PATCH 12/22] Remove WebCrypto engine detection machinery (Env files) --- .../kotlin/algorithms/Openssl3Ecdh.kt | 1 - .../kotlin/algorithms/Openssl3XDH.kt | 1 - .../src/commonMain/kotlin/internal/Env.kt | 10 ------- .../src/jsMain/kotlin/internal/Env.js.kt | 28 ------------------- .../wasmJsMain/kotlin/internal/Env.wasmJs.kt | 7 ----- 5 files changed, 47 deletions(-) delete mode 100644 cryptography-providers/webcrypto/src/commonMain/kotlin/internal/Env.kt delete mode 100644 cryptography-providers/webcrypto/src/jsMain/kotlin/internal/Env.js.kt delete mode 100644 cryptography-providers/webcrypto/src/wasmJsMain/kotlin/internal/Env.wasmJs.kt diff --git a/cryptography-providers/openssl3/api/src/commonMain/kotlin/algorithms/Openssl3Ecdh.kt b/cryptography-providers/openssl3/api/src/commonMain/kotlin/algorithms/Openssl3Ecdh.kt index f738d703..55c13841 100644 --- a/cryptography-providers/openssl3/api/src/commonMain/kotlin/algorithms/Openssl3Ecdh.kt +++ b/cryptography-providers/openssl3/api/src/commonMain/kotlin/algorithms/Openssl3Ecdh.kt @@ -150,4 +150,3 @@ internal object Openssl3Ecdh : ECDH { } } -// shared implementation moved to operations/KeyAgreement.kt diff --git a/cryptography-providers/openssl3/api/src/commonMain/kotlin/algorithms/Openssl3XDH.kt b/cryptography-providers/openssl3/api/src/commonMain/kotlin/algorithms/Openssl3XDH.kt index fad8101b..b2368432 100644 --- a/cryptography-providers/openssl3/api/src/commonMain/kotlin/algorithms/Openssl3XDH.kt +++ b/cryptography-providers/openssl3/api/src/commonMain/kotlin/algorithms/Openssl3XDH.kt @@ -140,4 +140,3 @@ internal object Openssl3XDH : XDH { } } -// shared implementation moved to operations/KeyAgreement.kt diff --git a/cryptography-providers/webcrypto/src/commonMain/kotlin/internal/Env.kt b/cryptography-providers/webcrypto/src/commonMain/kotlin/internal/Env.kt deleted file mode 100644 index f03a103d..00000000 --- a/cryptography-providers/webcrypto/src/commonMain/kotlin/internal/Env.kt +++ /dev/null @@ -1,10 +0,0 @@ -/* - * Copyright (c) 2025 Oleg Yukhnevich. Use of this source code is governed by the Apache 2.0 license. - */ - -package dev.whyoleg.cryptography.providers.webcrypto.internal - -internal expect fun detectEngine(): Engine - -internal enum class Engine { Node, Chromium, Firefox, Safari, Unknown } - diff --git a/cryptography-providers/webcrypto/src/jsMain/kotlin/internal/Env.js.kt b/cryptography-providers/webcrypto/src/jsMain/kotlin/internal/Env.js.kt deleted file mode 100644 index 51392b16..00000000 --- a/cryptography-providers/webcrypto/src/jsMain/kotlin/internal/Env.js.kt +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (c) 2025 Oleg Yukhnevich. Use of this source code is governed by the Apache 2.0 license. - */ - -package dev.whyoleg.cryptography.providers.webcrypto.internal - -internal actual fun detectEngine(): Engine { - // Node.js detection - val isNode = js("typeof process !== 'undefined' && process.versions && process.versions.node") as Boolean? ?: false - if (isNode) return Engine.Node - - // Browser detection via userAgent - val ua = try { - js("navigator.userAgent") as String - } catch (_: dynamic) { - "" - } - - return when { - ua.contains("Firefox", ignoreCase = true) -> Engine.Firefox - // Chromium derivatives first - ua.contains("Edg", ignoreCase = true) || ua.contains("OPR", ignoreCase = true) || ua.contains("Chrome", ignoreCase = true) || ua.contains("Chromium", ignoreCase = true) -> Engine.Chromium - // Safari (exclude Chrome/Chromium matches above) - ua.contains("Safari", ignoreCase = true) && ua.contains("AppleWebKit", ignoreCase = true) -> Engine.Safari - else -> Engine.Unknown - } -} - diff --git a/cryptography-providers/webcrypto/src/wasmJsMain/kotlin/internal/Env.wasmJs.kt b/cryptography-providers/webcrypto/src/wasmJsMain/kotlin/internal/Env.wasmJs.kt deleted file mode 100644 index f4c8d660..00000000 --- a/cryptography-providers/webcrypto/src/wasmJsMain/kotlin/internal/Env.wasmJs.kt +++ /dev/null @@ -1,7 +0,0 @@ -/* - * Copyright (c) 2025 Oleg Yukhnevich. Use of this source code is governed by the Apache 2.0 license. - */ - -package dev.whyoleg.cryptography.providers.webcrypto.internal - -internal actual fun detectEngine(): Engine = Engine.Chromium From 0d2b4b136d00f100f17707f115edc4b6ed66a88a Mon Sep 17 00:00:00 2001 From: Aria Wisp Date: Thu, 4 Sep 2025 15:20:45 -0600 Subject: [PATCH 13/22] JDK XDH: add RAW key format support via PKCS8/SPKI wrap/unwrap (X25519/X448) --- .../src/jvmMain/kotlin/algorithms/JdkXDH.kt | 34 ++++++++++++++++--- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/cryptography-providers/jdk/src/jvmMain/kotlin/algorithms/JdkXDH.kt b/cryptography-providers/jdk/src/jvmMain/kotlin/algorithms/JdkXDH.kt index 63a90d01..024d07f2 100644 --- a/cryptography-providers/jdk/src/jvmMain/kotlin/algorithms/JdkXDH.kt +++ b/cryptography-providers/jdk/src/jvmMain/kotlin/algorithms/JdkXDH.kt @@ -8,12 +8,18 @@ import dev.whyoleg.cryptography.providers.jdk.materials.* import dev.whyoleg.cryptography.providers.base.materials.* import dev.whyoleg.cryptography.providers.jdk.operations.* import dev.whyoleg.cryptography.serialization.pem.* +import dev.whyoleg.cryptography.serialization.asn1.* +import dev.whyoleg.cryptography.serialization.asn1.modules.* internal class JdkXDH(private val state: JdkCryptographyState) : XDH { private fun curveName(curve: XDH.Curve): String = when (curve) { XDH.Curve.X25519 -> "X25519" XDH.Curve.X448 -> "X448" } + private fun oid(curve: XDH.Curve): ObjectIdentifier = when (curve) { + XDH.Curve.X25519 -> ObjectIdentifier("1.3.101.110") + XDH.Curve.X448 -> ObjectIdentifier("1.3.101.111") + } override fun publicKeyDecoder(curve: XDH.Curve): KeyDecoder = object : JdkPublicKeyDecoder(state, curveName(curve)) { @@ -21,7 +27,9 @@ internal class JdkXDH(private val state: JdkCryptographyState) : XDH { override fun decodeFromByteArrayBlocking(format: XDH.PublicKey.Format, bytes: ByteArray): XDH.PublicKey = when (format) { XDH.PublicKey.Format.JWK -> error("JWK is not supported") - XDH.PublicKey.Format.RAW -> TODO("RAW encoding is not supported yet") + XDH.PublicKey.Format.RAW -> decodeFromDer( + wrapSubjectPublicKeyInfo(UnknownKeyAlgorithmIdentifier(oid(curve)), bytes) + ) XDH.PublicKey.Format.DER -> decodeFromDer(bytes) XDH.PublicKey.Format.PEM -> decodeFromDer(unwrapPem(PemLabel.PublicKey, bytes)) } @@ -33,7 +41,13 @@ internal class JdkXDH(private val state: JdkCryptographyState) : XDH { override fun decodeFromByteArrayBlocking(format: XDH.PrivateKey.Format, bytes: ByteArray): XDH.PrivateKey = when (format) { XDH.PrivateKey.Format.JWK -> error("JWK is not supported") - XDH.PrivateKey.Format.RAW -> TODO("RAW encoding is not supported yet") + XDH.PrivateKey.Format.RAW -> decodeFromDer( + wrapPrivateKeyInfo( + 0, + UnknownKeyAlgorithmIdentifier(oid(curve)), + bytes + ) + ) XDH.PrivateKey.Format.DER -> decodeFromDer(bytes) XDH.PrivateKey.Format.PEM -> decodeFromDer(unwrapPem(PemLabel.PrivateKey, bytes)) } @@ -68,7 +82,12 @@ internal class JdkXDH(private val state: JdkCryptographyState) : XDH { override fun encodeToByteArrayBlocking(format: XDH.PublicKey.Format): ByteArray = when (format) { XDH.PublicKey.Format.JWK -> error("JWK is not supported") - XDH.PublicKey.Format.RAW -> TODO("RAW encoding is not supported yet") + XDH.PublicKey.Format.RAW -> { + val der = encodeToDer() + try { unwrapSubjectPublicKeyInfo(ObjectIdentifier("1.3.101.110"), der) } catch (_: Throwable) { + unwrapSubjectPublicKeyInfo(ObjectIdentifier("1.3.101.111"), der) + } + } XDH.PublicKey.Format.DER -> encodeToDer() XDH.PublicKey.Format.PEM -> wrapPem(PemLabel.PublicKey, encodeToDer()) } @@ -87,9 +106,16 @@ internal class JdkXDH(private val state: JdkCryptographyState) : XDH { override fun encodeToByteArrayBlocking(format: XDH.PrivateKey.Format): ByteArray = when (format) { XDH.PrivateKey.Format.JWK -> error("JWK is not supported") - XDH.PrivateKey.Format.RAW -> TODO("RAW encoding is not supported yet") + XDH.PrivateKey.Format.RAW -> { + val der = encodeToDer() + try { unwrapPrivateKeyInfo(ObjectIdentifier("1.3.101.110"), der) } catch (_: Throwable) { + unwrapPrivateKeyInfo(ObjectIdentifier("1.3.101.111"), der) + } + } XDH.PrivateKey.Format.DER -> encodeToDer() XDH.PrivateKey.Format.PEM -> wrapPem(PemLabel.PrivateKey, encodeToDer()) } } } + + From a79fb6468c7b22474027b2ac4fdb567cab74cf60 Mon Sep 17 00:00:00 2001 From: Aria Wisp Date: Thu, 4 Sep 2025 15:21:17 -0600 Subject: [PATCH 14/22] Remove leftover implementation notes in CryptoKit EdDSA/XDH files --- .../cryptokit/src/commonMain/kotlin/algorithms/CryptoKitEdDSA.kt | 1 - .../cryptokit/src/commonMain/kotlin/algorithms/CryptoKitXDH.kt | 1 - 2 files changed, 2 deletions(-) diff --git a/cryptography-providers/cryptokit/src/commonMain/kotlin/algorithms/CryptoKitEdDSA.kt b/cryptography-providers/cryptokit/src/commonMain/kotlin/algorithms/CryptoKitEdDSA.kt index 73ed9300..1125a69f 100644 --- a/cryptography-providers/cryptokit/src/commonMain/kotlin/algorithms/CryptoKitEdDSA.kt +++ b/cryptography-providers/cryptokit/src/commonMain/kotlin/algorithms/CryptoKitEdDSA.kt @@ -179,4 +179,3 @@ private class EdVerifyFunction( override fun close() { closed = true; buffer.clear() } } -// NSData helpers provided by cryptography-providers-base diff --git a/cryptography-providers/cryptokit/src/commonMain/kotlin/algorithms/CryptoKitXDH.kt b/cryptography-providers/cryptokit/src/commonMain/kotlin/algorithms/CryptoKitXDH.kt index 88dc417b..efa1b216 100644 --- a/cryptography-providers/cryptokit/src/commonMain/kotlin/algorithms/CryptoKitXDH.kt +++ b/cryptography-providers/cryptokit/src/commonMain/kotlin/algorithms/CryptoKitXDH.kt @@ -127,4 +127,3 @@ internal object CryptoKitXDH : XDH { } } } -// NSData helpers provided by cryptography-providers-base From 9bb1ac2c4db17576e3bceca041d34484b1dd4495 Mon Sep 17 00:00:00 2001 From: Aria Wisp Date: Thu, 4 Sep 2025 15:21:39 -0600 Subject: [PATCH 15/22] Docs(CryptoKit): clarify that only Ed25519/X25519 are supported --- docs/providers/cryptokit.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/providers/cryptokit.md b/docs/providers/cryptokit.md index 80efba9d..a8c2757e 100644 --- a/docs/providers/cryptokit.md +++ b/docs/providers/cryptokit.md @@ -8,7 +8,7 @@ For supported targets and algorithms, please consult [Supported primitives secti * KeyFormat: doesn't support `JWK` key format yet * AES.GCM supports only a default tag size of 96 bits -* EdDSA/XDH: CryptoKit supports Ed25519 and X25519. DER/PEM and RAW key encodings are available via the provider APIs. +* EdDSA/XDH: CryptoKit supports only Ed25519 and X25519. DER/PEM and RAW key encodings are available via the provider APIs. ## Example From cf445fb837529fc40eeff872a8d01ba5c0cd3ec2 Mon Sep 17 00:00:00 2001 From: Aria Wisp Date: Thu, 4 Sep 2025 15:26:24 -0600 Subject: [PATCH 16/22] ASN.1 modules: add Edwards/Montgomery OIDs and use them across providers; Compatibility tests: include JWK for WebCrypto in EdDSA/XDH --- .../kotlin/algorithms/CryptoKitEdDSA.kt | 17 +++++------ .../kotlin/algorithms/CryptoKitXDH.kt | 16 +++++----- .../src/jvmMain/kotlin/algorithms/JdkEdDSA.kt | 12 ++++---- .../src/jvmMain/kotlin/algorithms/JdkXDH.kt | 12 ++++---- .../kotlin/algorithms/Openssl3EdDSA.kt | 28 ++++++++---------- .../kotlin/algorithms/Openssl3XDH.kt | 29 +++++-------------- .../compatibility/EdDsaCompatibilityTest.kt | 5 +++- .../compatibility/XdhCompatibilityTest.kt | 3 +- .../modules/src/commonMain/kotlin/Oids.kt | 18 ++++++++++++ 9 files changed, 72 insertions(+), 68 deletions(-) create mode 100644 cryptography-serialization/asn1/modules/src/commonMain/kotlin/Oids.kt diff --git a/cryptography-providers/cryptokit/src/commonMain/kotlin/algorithms/CryptoKitEdDSA.kt b/cryptography-providers/cryptokit/src/commonMain/kotlin/algorithms/CryptoKitEdDSA.kt index 1125a69f..e8a003bc 100644 --- a/cryptography-providers/cryptokit/src/commonMain/kotlin/algorithms/CryptoKitEdDSA.kt +++ b/cryptography-providers/cryptokit/src/commonMain/kotlin/algorithms/CryptoKitEdDSA.kt @@ -26,12 +26,12 @@ internal object CryptoKitEdDSA : EdDSA { swiftTry { error -> bytes.useNSData { SwiftEdDsaPublicKey.decodeRawWithRawRepresentation(it, error) } } ) EdDSA.PublicKey.Format.DER -> { - val raw = unwrapSubjectPublicKeyInfo(ObjectIdentifier("1.3.101.112"), bytes) + val raw = unwrapSubjectPublicKeyInfo(EdwardsOids.Ed25519, bytes) EdPublic(swiftTry { error -> raw.useNSData { SwiftEdDsaPublicKey.decodeRawWithRawRepresentation(it, error) } }) } EdDSA.PublicKey.Format.PEM -> { val der = unwrapPem(PemLabel.PublicKey, bytes) - val raw = unwrapSubjectPublicKeyInfo(ObjectIdentifier("1.3.101.112"), der) + val raw = unwrapSubjectPublicKeyInfo(EdwardsOids.Ed25519, der) EdPublic(swiftTry { error -> raw.useNSData { SwiftEdDsaPublicKey.decodeRawWithRawRepresentation(it, error) } }) } else -> error("$format is not supported by CryptoKit EdDSA") @@ -47,12 +47,12 @@ internal object CryptoKitEdDSA : EdDSA { swiftTry { error -> bytes.useNSData { SwiftEdDsaPrivateKey.decodeRawWithRawRepresentation(it, error) } } ) EdDSA.PrivateKey.Format.DER -> { - val raw = unwrapPrivateKeyInfo(ObjectIdentifier("1.3.101.112"), bytes) + val raw = unwrapPrivateKeyInfo(EdwardsOids.Ed25519, bytes) EdPrivate(swiftTry { error -> raw.useNSData { SwiftEdDsaPrivateKey.decodeRawWithRawRepresentation(it, error) } }) } EdDSA.PrivateKey.Format.PEM -> { val der = unwrapPem(PemLabel.PrivateKey, bytes) - val raw = unwrapPrivateKeyInfo(ObjectIdentifier("1.3.101.112"), der) + val raw = unwrapPrivateKeyInfo(EdwardsOids.Ed25519, der) EdPrivate(swiftTry { error -> raw.useNSData { SwiftEdDsaPrivateKey.decodeRawWithRawRepresentation(it, error) } }) } else -> error("$format is not supported by CryptoKit EdDSA") @@ -81,13 +81,13 @@ internal object CryptoKitEdDSA : EdDSA { override fun encodeToByteArrayBlocking(format: EdDSA.PublicKey.Format): ByteArray = when (format) { EdDSA.PublicKey.Format.RAW -> key.rawRepresentation().toByteArray() EdDSA.PublicKey.Format.DER -> wrapSubjectPublicKeyInfo( - UnknownKeyAlgorithmIdentifier(ObjectIdentifier("1.3.101.112")), + UnknownKeyAlgorithmIdentifier(EdwardsOids.Ed25519), key.rawRepresentation().toByteArray() ) EdDSA.PublicKey.Format.PEM -> wrapPem( PemLabel.PublicKey, wrapSubjectPublicKeyInfo( - UnknownKeyAlgorithmIdentifier(ObjectIdentifier("1.3.101.112")), + UnknownKeyAlgorithmIdentifier(EdwardsOids.Ed25519), key.rawRepresentation().toByteArray() ) ) @@ -106,14 +106,14 @@ internal object CryptoKitEdDSA : EdDSA { EdDSA.PrivateKey.Format.RAW -> key.rawRepresentation().toByteArray() EdDSA.PrivateKey.Format.DER -> wrapPrivateKeyInfo( 0, - UnknownKeyAlgorithmIdentifier(ObjectIdentifier("1.3.101.112")), + UnknownKeyAlgorithmIdentifier(EdwardsOids.Ed25519), key.rawRepresentation().toByteArray() ) EdDSA.PrivateKey.Format.PEM -> wrapPem( PemLabel.PrivateKey, wrapPrivateKeyInfo( 0, - UnknownKeyAlgorithmIdentifier(ObjectIdentifier("1.3.101.112")), + UnknownKeyAlgorithmIdentifier(EdwardsOids.Ed25519), key.rawRepresentation().toByteArray() ) ) @@ -178,4 +178,3 @@ private class EdVerifyFunction( override fun reset() { buffer.clear() } override fun close() { closed = true; buffer.clear() } } - diff --git a/cryptography-providers/cryptokit/src/commonMain/kotlin/algorithms/CryptoKitXDH.kt b/cryptography-providers/cryptokit/src/commonMain/kotlin/algorithms/CryptoKitXDH.kt index efa1b216..c6285498 100644 --- a/cryptography-providers/cryptokit/src/commonMain/kotlin/algorithms/CryptoKitXDH.kt +++ b/cryptography-providers/cryptokit/src/commonMain/kotlin/algorithms/CryptoKitXDH.kt @@ -26,12 +26,12 @@ internal object CryptoKitXDH : XDH { swiftTry { error -> bytes.useNSData { SwiftXdhPublicKey.decodeRawWithRawRepresentation(it, error) } } ) XDH.PublicKey.Format.DER -> { - val raw = unwrapSubjectPublicKeyInfo(ObjectIdentifier("1.3.101.110"), bytes) + val raw = unwrapSubjectPublicKeyInfo(MontgomeryOids.X25519, bytes) XPublic(swiftTry { error -> raw.useNSData { SwiftXdhPublicKey.decodeRawWithRawRepresentation(it, error) } }) } XDH.PublicKey.Format.PEM -> { val der = unwrapPem(PemLabel.PublicKey, bytes) - val raw = unwrapSubjectPublicKeyInfo(ObjectIdentifier("1.3.101.110"), der) + val raw = unwrapSubjectPublicKeyInfo(MontgomeryOids.X25519, der) XPublic(swiftTry { error -> raw.useNSData { SwiftXdhPublicKey.decodeRawWithRawRepresentation(it, error) } }) } else -> error("$format is not supported by CryptoKit XDH") @@ -47,12 +47,12 @@ internal object CryptoKitXDH : XDH { swiftTry { error -> bytes.useNSData { SwiftXdhPrivateKey.decodeRawWithRawRepresentation(it, error) } } ) XDH.PrivateKey.Format.DER -> { - val raw = unwrapPrivateKeyInfo(ObjectIdentifier("1.3.101.110"), bytes) + val raw = unwrapPrivateKeyInfo(MontgomeryOids.X25519, bytes) XPrivate(swiftTry { error -> raw.useNSData { SwiftXdhPrivateKey.decodeRawWithRawRepresentation(it, error) } }) } XDH.PrivateKey.Format.PEM -> { val der = unwrapPem(PemLabel.PrivateKey, bytes) - val raw = unwrapPrivateKeyInfo(ObjectIdentifier("1.3.101.110"), der) + val raw = unwrapPrivateKeyInfo(MontgomeryOids.X25519, der) XPrivate(swiftTry { error -> raw.useNSData { SwiftXdhPrivateKey.decodeRawWithRawRepresentation(it, error) } }) } else -> error("$format is not supported by CryptoKit XDH") @@ -81,13 +81,13 @@ internal object CryptoKitXDH : XDH { override fun encodeToByteArrayBlocking(format: XDH.PublicKey.Format): ByteArray = when (format) { XDH.PublicKey.Format.RAW -> key.rawRepresentation().toByteArray() XDH.PublicKey.Format.DER -> wrapSubjectPublicKeyInfo( - UnknownKeyAlgorithmIdentifier(ObjectIdentifier("1.3.101.110")), + UnknownKeyAlgorithmIdentifier(MontgomeryOids.X25519), key.rawRepresentation().toByteArray() ) XDH.PublicKey.Format.PEM -> wrapPem( PemLabel.PublicKey, wrapSubjectPublicKeyInfo( - UnknownKeyAlgorithmIdentifier(ObjectIdentifier("1.3.101.110")), + UnknownKeyAlgorithmIdentifier(MontgomeryOids.X25519), key.rawRepresentation().toByteArray() ) ) @@ -107,14 +107,14 @@ internal object CryptoKitXDH : XDH { XDH.PrivateKey.Format.RAW -> key.rawRepresentation().toByteArray() XDH.PrivateKey.Format.DER -> wrapPrivateKeyInfo( 0, - UnknownKeyAlgorithmIdentifier(ObjectIdentifier("1.3.101.110")), + UnknownKeyAlgorithmIdentifier(MontgomeryOids.X25519), key.rawRepresentation().toByteArray() ) XDH.PrivateKey.Format.PEM -> wrapPem( PemLabel.PrivateKey, wrapPrivateKeyInfo( 0, - UnknownKeyAlgorithmIdentifier(ObjectIdentifier("1.3.101.110")), + UnknownKeyAlgorithmIdentifier(MontgomeryOids.X25519), key.rawRepresentation().toByteArray() ) ) diff --git a/cryptography-providers/jdk/src/jvmMain/kotlin/algorithms/JdkEdDSA.kt b/cryptography-providers/jdk/src/jvmMain/kotlin/algorithms/JdkEdDSA.kt index 7c8bf2b6..3800e6fd 100644 --- a/cryptography-providers/jdk/src/jvmMain/kotlin/algorithms/JdkEdDSA.kt +++ b/cryptography-providers/jdk/src/jvmMain/kotlin/algorithms/JdkEdDSA.kt @@ -17,8 +17,8 @@ internal class JdkEdDSA(private val state: JdkCryptographyState) : EdDSA { EdDSA.Curve.Ed448 -> "Ed448" } private fun oid(curve: EdDSA.Curve): ObjectIdentifier = when (curve) { - EdDSA.Curve.Ed25519 -> ObjectIdentifier("1.3.101.112") - EdDSA.Curve.Ed448 -> ObjectIdentifier("1.3.101.113") + EdDSA.Curve.Ed25519 -> EdwardsOids.Ed25519 + EdDSA.Curve.Ed448 -> EdwardsOids.Ed448 } override fun publicKeyDecoder(curve: EdDSA.Curve): KeyDecoder = @@ -82,8 +82,8 @@ internal class JdkEdDSA(private val state: JdkCryptographyState) : EdDSA { EdDSA.PublicKey.Format.RAW -> { val der = encodeToDer() // unwrap SPKI to raw for known OIDs - try { unwrapSubjectPublicKeyInfo(ObjectIdentifier("1.3.101.112"), der) } catch (_: Throwable) { - unwrapSubjectPublicKeyInfo(ObjectIdentifier("1.3.101.113"), der) + try { unwrapSubjectPublicKeyInfo(EdwardsOids.Ed25519, der) } catch (_: Throwable) { + unwrapSubjectPublicKeyInfo(EdwardsOids.Ed448, der) } } EdDSA.PublicKey.Format.DER -> encodeToDer() @@ -104,8 +104,8 @@ internal class JdkEdDSA(private val state: JdkCryptographyState) : EdDSA { EdDSA.PrivateKey.Format.RAW -> { val der = encodeToDer() // unwrap PKCS#8 to raw for known OIDs - try { unwrapPrivateKeyInfo(ObjectIdentifier("1.3.101.112"), der) } catch (_: Throwable) { - unwrapPrivateKeyInfo(ObjectIdentifier("1.3.101.113"), der) + try { unwrapPrivateKeyInfo(EdwardsOids.Ed25519, der) } catch (_: Throwable) { + unwrapPrivateKeyInfo(EdwardsOids.Ed448, der) } } EdDSA.PrivateKey.Format.DER -> encodeToDer() diff --git a/cryptography-providers/jdk/src/jvmMain/kotlin/algorithms/JdkXDH.kt b/cryptography-providers/jdk/src/jvmMain/kotlin/algorithms/JdkXDH.kt index 024d07f2..118aa0e1 100644 --- a/cryptography-providers/jdk/src/jvmMain/kotlin/algorithms/JdkXDH.kt +++ b/cryptography-providers/jdk/src/jvmMain/kotlin/algorithms/JdkXDH.kt @@ -17,8 +17,8 @@ internal class JdkXDH(private val state: JdkCryptographyState) : XDH { XDH.Curve.X448 -> "X448" } private fun oid(curve: XDH.Curve): ObjectIdentifier = when (curve) { - XDH.Curve.X25519 -> ObjectIdentifier("1.3.101.110") - XDH.Curve.X448 -> ObjectIdentifier("1.3.101.111") + XDH.Curve.X25519 -> MontgomeryOids.X25519 + XDH.Curve.X448 -> MontgomeryOids.X448 } override fun publicKeyDecoder(curve: XDH.Curve): KeyDecoder = @@ -84,8 +84,8 @@ internal class JdkXDH(private val state: JdkCryptographyState) : XDH { XDH.PublicKey.Format.JWK -> error("JWK is not supported") XDH.PublicKey.Format.RAW -> { val der = encodeToDer() - try { unwrapSubjectPublicKeyInfo(ObjectIdentifier("1.3.101.110"), der) } catch (_: Throwable) { - unwrapSubjectPublicKeyInfo(ObjectIdentifier("1.3.101.111"), der) + try { unwrapSubjectPublicKeyInfo(MontgomeryOids.X25519, der) } catch (_: Throwable) { + unwrapSubjectPublicKeyInfo(MontgomeryOids.X448, der) } } XDH.PublicKey.Format.DER -> encodeToDer() @@ -108,8 +108,8 @@ internal class JdkXDH(private val state: JdkCryptographyState) : XDH { XDH.PrivateKey.Format.JWK -> error("JWK is not supported") XDH.PrivateKey.Format.RAW -> { val der = encodeToDer() - try { unwrapPrivateKeyInfo(ObjectIdentifier("1.3.101.110"), der) } catch (_: Throwable) { - unwrapPrivateKeyInfo(ObjectIdentifier("1.3.101.111"), der) + try { unwrapPrivateKeyInfo(MontgomeryOids.X25519, der) } catch (_: Throwable) { + unwrapPrivateKeyInfo(MontgomeryOids.X448, der) } } XDH.PrivateKey.Format.DER -> encodeToDer() diff --git a/cryptography-providers/openssl3/api/src/commonMain/kotlin/algorithms/Openssl3EdDSA.kt b/cryptography-providers/openssl3/api/src/commonMain/kotlin/algorithms/Openssl3EdDSA.kt index 5288ab56..641eddc4 100644 --- a/cryptography-providers/openssl3/api/src/commonMain/kotlin/algorithms/Openssl3EdDSA.kt +++ b/cryptography-providers/openssl3/api/src/commonMain/kotlin/algorithms/Openssl3EdDSA.kt @@ -14,15 +14,16 @@ import dev.whyoleg.cryptography.providers.openssl3.materials.* import kotlinx.cinterop.* import platform.posix.* import kotlin.experimental.* +import dev.whyoleg.cryptography.serialization.asn1.modules.* internal object Openssl3EdDSA : EdDSA { private fun algorithmName(curve: EdDSA.Curve): String = when (curve) { EdDSA.Curve.Ed25519 -> "ED25519" EdDSA.Curve.Ed448 -> "ED448" } - private fun oid(curve: EdDSA.Curve): String = when (curve) { - EdDSA.Curve.Ed25519 -> "1.3.101.112" - EdDSA.Curve.Ed448 -> "1.3.101.113" + private fun oid(curve: EdDSA.Curve): ObjectIdentifier = when (curve) { + EdDSA.Curve.Ed25519 -> EdwardsOids.Ed25519 + EdDSA.Curve.Ed448 -> EdwardsOids.Ed448 } override fun publicKeyDecoder(curve: EdDSA.Curve): KeyDecoder = @@ -35,11 +36,9 @@ internal object Openssl3EdDSA : EdDSA { } override fun decodeFromByteArrayBlocking(format: EdDSA.PublicKey.Format, bytes: ByteArray): EdDSA.PublicKey = when (format) { - EdDSA.PublicKey.Format.RAW -> super.decodeFromByteArrayBlocking(EdDSA.PublicKey.Format.DER, - wrapSubjectPublicKeyInfo( - UnknownKeyAlgorithmIdentifier(ObjectIdentifier(oid(curve))), - bytes - ) + EdDSA.PublicKey.Format.RAW -> super.decodeFromByteArrayBlocking( + EdDSA.PublicKey.Format.DER, + wrapSubjectPublicKeyInfo(UnknownKeyAlgorithmIdentifier(oid(curve)), bytes) ) else -> super.decodeFromByteArrayBlocking(format, bytes) } @@ -57,12 +56,9 @@ internal object Openssl3EdDSA : EdDSA { } override fun decodeFromByteArrayBlocking(format: EdDSA.PrivateKey.Format, bytes: ByteArray): EdDSA.PrivateKey = when (format) { - EdDSA.PrivateKey.Format.RAW -> super.decodeFromByteArrayBlocking(EdDSA.PrivateKey.Format.DER, - wrapPrivateKeyInfo( - 0, - UnknownKeyAlgorithmIdentifier(ObjectIdentifier(oid(curve))), - bytes - ) + EdDSA.PrivateKey.Format.RAW -> super.decodeFromByteArrayBlocking( + EdDSA.PrivateKey.Format.DER, + wrapPrivateKeyInfo(0, UnknownKeyAlgorithmIdentifier(oid(curve)), bytes) ) else -> super.decodeFromByteArrayBlocking(format, bytes) } @@ -97,7 +93,7 @@ internal object Openssl3EdDSA : EdDSA { override fun encodeToByteArrayBlocking(format: EdDSA.PublicKey.Format): ByteArray = when (format) { EdDSA.PublicKey.Format.RAW -> unwrapSubjectPublicKeyInfo( - ObjectIdentifier(oid(curve)), + oid(curve), super.encodeToByteArrayBlocking(EdDSA.PublicKey.Format.DER) ) else -> super.encodeToByteArrayBlocking(format) @@ -119,7 +115,7 @@ internal object Openssl3EdDSA : EdDSA { override fun encodeToByteArrayBlocking(format: EdDSA.PrivateKey.Format): ByteArray = when (format) { EdDSA.PrivateKey.Format.RAW -> unwrapPrivateKeyInfo( - ObjectIdentifier(oid(curve)), + oid(curve), super.encodeToByteArrayBlocking(EdDSA.PrivateKey.Format.DER) ) else -> super.encodeToByteArrayBlocking(format) diff --git a/cryptography-providers/openssl3/api/src/commonMain/kotlin/algorithms/Openssl3XDH.kt b/cryptography-providers/openssl3/api/src/commonMain/kotlin/algorithms/Openssl3XDH.kt index b2368432..535ae73d 100644 --- a/cryptography-providers/openssl3/api/src/commonMain/kotlin/algorithms/Openssl3XDH.kt +++ b/cryptography-providers/openssl3/api/src/commonMain/kotlin/algorithms/Openssl3XDH.kt @@ -15,15 +15,16 @@ import kotlinx.cinterop.* import platform.posix.* import kotlin.experimental.* import dev.whyoleg.cryptography.providers.openssl3.operations.* +import dev.whyoleg.cryptography.serialization.asn1.modules.* internal object Openssl3XDH : XDH { private fun algorithmName(curve: XDH.Curve): String = when (curve) { XDH.Curve.X25519 -> "X25519" XDH.Curve.X448 -> "X448" } - private fun oid(curve: XDH.Curve): String = when (curve) { - XDH.Curve.X25519 -> "1.3.101.110" - XDH.Curve.X448 -> "1.3.101.111" + private fun oid(curve: XDH.Curve): ObjectIdentifier = when (curve) { + XDH.Curve.X25519 -> MontgomeryOids.X25519 + XDH.Curve.X448 -> MontgomeryOids.X448 } override fun publicKeyDecoder(curve: XDH.Curve): KeyDecoder = @@ -38,10 +39,7 @@ internal object Openssl3XDH : XDH { override fun decodeFromByteArrayBlocking(format: XDH.PublicKey.Format, bytes: ByteArray): XDH.PublicKey = when (format) { XDH.PublicKey.Format.RAW -> super.decodeFromByteArrayBlocking( XDH.PublicKey.Format.DER, - wrapSubjectPublicKeyInfo( - UnknownKeyAlgorithmIdentifier(ObjectIdentifier(oid(curve))), - bytes - ) + wrapSubjectPublicKeyInfo(UnknownKeyAlgorithmIdentifier(oid(curve)), bytes) ) else -> super.decodeFromByteArrayBlocking(format, bytes) } @@ -61,11 +59,7 @@ internal object Openssl3XDH : XDH { override fun decodeFromByteArrayBlocking(format: XDH.PrivateKey.Format, bytes: ByteArray): XDH.PrivateKey = when (format) { XDH.PrivateKey.Format.RAW -> super.decodeFromByteArrayBlocking( XDH.PrivateKey.Format.DER, - wrapPrivateKeyInfo( - 0, - UnknownKeyAlgorithmIdentifier(ObjectIdentifier(oid(curve))), - bytes - ) + wrapPrivateKeyInfo(0, UnknownKeyAlgorithmIdentifier(oid(curve)), bytes) ) else -> super.decodeFromByteArrayBlocking(format, bytes) } @@ -99,10 +93,7 @@ internal object Openssl3XDH : XDH { } override fun encodeToByteArrayBlocking(format: XDH.PublicKey.Format): ByteArray = when (format) { - XDH.PublicKey.Format.RAW -> unwrapSubjectPublicKeyInfo( - ObjectIdentifier(oid(curve)), - super.encodeToByteArrayBlocking(XDH.PublicKey.Format.DER) - ) + XDH.PublicKey.Format.RAW -> unwrapSubjectPublicKeyInfo(oid(curve), super.encodeToByteArrayBlocking(XDH.PublicKey.Format.DER)) else -> super.encodeToByteArrayBlocking(format) } @@ -125,10 +116,7 @@ internal object Openssl3XDH : XDH { } override fun encodeToByteArrayBlocking(format: XDH.PrivateKey.Format): ByteArray = when (format) { - XDH.PrivateKey.Format.RAW -> unwrapPrivateKeyInfo( - ObjectIdentifier(oid(curve)), - super.encodeToByteArrayBlocking(XDH.PrivateKey.Format.DER) - ) + XDH.PrivateKey.Format.RAW -> unwrapPrivateKeyInfo(oid(curve), super.encodeToByteArrayBlocking(XDH.PrivateKey.Format.DER)) else -> super.encodeToByteArrayBlocking(format) } @@ -139,4 +127,3 @@ internal object Openssl3XDH : XDH { } } } - diff --git a/cryptography-providers/tests/src/commonMain/kotlin/compatibility/EdDsaCompatibilityTest.kt b/cryptography-providers/tests/src/commonMain/kotlin/compatibility/EdDsaCompatibilityTest.kt index 0f11ab29..17007349 100644 --- a/cryptography-providers/tests/src/commonMain/kotlin/compatibility/EdDsaCompatibilityTest.kt +++ b/cryptography-providers/tests/src/commonMain/kotlin/compatibility/EdDsaCompatibilityTest.kt @@ -15,12 +15,14 @@ import kotlinx.serialization.* import kotlin.test.* private val edPublicKeyFormats = listOf( + EdDSA.PublicKey.Format.JWK, EdDSA.PublicKey.Format.RAW, EdDSA.PublicKey.Format.DER, EdDSA.PublicKey.Format.PEM, ).associateBy { it.name } private val edPrivateKeyFormats = listOf( + EdDSA.PrivateKey.Format.JWK, EdDSA.PrivateKey.Format.RAW, EdDSA.PrivateKey.Format.DER, EdDSA.PrivateKey.Format.PEM, @@ -85,6 +87,7 @@ abstract class EdDsaCompatibilityTest( supports = ::supportsKeyFormat ) { key, format, bytes -> when (format) { + EdDSA.PublicKey.Format.JWK -> {} EdDSA.PublicKey.Format.PEM -> { val expected = PemDocument.decode(bytes) val actual = PemDocument.decode(key.encodeToByteString(format)) @@ -101,6 +104,7 @@ abstract class EdDsaCompatibilityTest( supports = ::supportsKeyFormat ) { key, format, bytes -> when (format) { + EdDSA.PrivateKey.Format.JWK -> {} EdDSA.PrivateKey.Format.PEM -> { val expected = PemDocument.decode(bytes) val actual = PemDocument.decode(key.encodeToByteString(format)) @@ -134,4 +138,3 @@ abstract class EdDsaCompatibilityTest( } } } - diff --git a/cryptography-providers/tests/src/commonMain/kotlin/compatibility/XdhCompatibilityTest.kt b/cryptography-providers/tests/src/commonMain/kotlin/compatibility/XdhCompatibilityTest.kt index 017a68dd..a0871dae 100644 --- a/cryptography-providers/tests/src/commonMain/kotlin/compatibility/XdhCompatibilityTest.kt +++ b/cryptography-providers/tests/src/commonMain/kotlin/compatibility/XdhCompatibilityTest.kt @@ -13,12 +13,14 @@ import kotlinx.serialization.* import kotlin.test.* private val xdhPublicKeyFormats = listOf( + XDH.PublicKey.Format.JWK, XDH.PublicKey.Format.RAW, XDH.PublicKey.Format.DER, XDH.PublicKey.Format.PEM, ).associateBy { it.name } private val xdhPrivateKeyFormats = listOf( + XDH.PrivateKey.Format.JWK, XDH.PrivateKey.Format.RAW, XDH.PrivateKey.Format.DER, XDH.PrivateKey.Format.PEM, @@ -131,4 +133,3 @@ abstract class XdhCompatibilityTest( } } } - diff --git a/cryptography-serialization/asn1/modules/src/commonMain/kotlin/Oids.kt b/cryptography-serialization/asn1/modules/src/commonMain/kotlin/Oids.kt new file mode 100644 index 00000000..aedcb290 --- /dev/null +++ b/cryptography-serialization/asn1/modules/src/commonMain/kotlin/Oids.kt @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2025 Oleg Yukhnevich. Use of this source code is governed by the Apache 2.0 license. + */ + +package dev.whyoleg.cryptography.serialization.asn1.modules + +import dev.whyoleg.cryptography.serialization.asn1.ObjectIdentifier + +public object EdwardsOids { + public val Ed25519: ObjectIdentifier = ObjectIdentifier("1.3.101.112") + public val Ed448: ObjectIdentifier = ObjectIdentifier("1.3.101.113") +} + +public object MontgomeryOids { + public val X25519: ObjectIdentifier = ObjectIdentifier("1.3.101.110") + public val X448: ObjectIdentifier = ObjectIdentifier("1.3.101.111") +} + From 6952a3df5118e44aedfede6af4d1f163108197d5 Mon Sep 17 00:00:00 2001 From: Aria Wisp Date: Thu, 4 Sep 2025 15:33:08 -0600 Subject: [PATCH 17/22] OpenSSL EdDSA: reuse digest signature base with nullable hash (one-shot path); JDK: unify RAW SPKI/PKCS#8 unwrap via helper; wire OID constants --- .../src/jvmMain/kotlin/algorithms/JdkEdDSA.kt | 10 +- .../src/jvmMain/kotlin/algorithms/JdkXDH.kt | 8 +- .../kotlin/algorithms/KeyInfoUnwrap.kt | 31 ++++ .../kotlin/algorithms/Openssl3EdDSA.kt | 175 +----------------- .../Openssl3DigestSignatureGenerator.kt | 76 +++++++- .../Openssl3DigestSignatureVerifier.kt | 71 ++++++- 6 files changed, 177 insertions(+), 194 deletions(-) create mode 100644 cryptography-providers/jdk/src/jvmMain/kotlin/algorithms/KeyInfoUnwrap.kt diff --git a/cryptography-providers/jdk/src/jvmMain/kotlin/algorithms/JdkEdDSA.kt b/cryptography-providers/jdk/src/jvmMain/kotlin/algorithms/JdkEdDSA.kt index 3800e6fd..c7873e97 100644 --- a/cryptography-providers/jdk/src/jvmMain/kotlin/algorithms/JdkEdDSA.kt +++ b/cryptography-providers/jdk/src/jvmMain/kotlin/algorithms/JdkEdDSA.kt @@ -81,10 +81,7 @@ internal class JdkEdDSA(private val state: JdkCryptographyState) : EdDSA { EdDSA.PublicKey.Format.JWK -> error("JWK is not supported") EdDSA.PublicKey.Format.RAW -> { val der = encodeToDer() - // unwrap SPKI to raw for known OIDs - try { unwrapSubjectPublicKeyInfo(EdwardsOids.Ed25519, der) } catch (_: Throwable) { - unwrapSubjectPublicKeyInfo(EdwardsOids.Ed448, der) - } + KeyInfoUnwrap.unwrapSpkiForOids(der, listOf(EdwardsOids.Ed25519, EdwardsOids.Ed448)) } EdDSA.PublicKey.Format.DER -> encodeToDer() EdDSA.PublicKey.Format.PEM -> wrapPem(PemLabel.PublicKey, encodeToDer()) @@ -103,10 +100,7 @@ internal class JdkEdDSA(private val state: JdkCryptographyState) : EdDSA { EdDSA.PrivateKey.Format.JWK -> error("JWK is not supported") EdDSA.PrivateKey.Format.RAW -> { val der = encodeToDer() - // unwrap PKCS#8 to raw for known OIDs - try { unwrapPrivateKeyInfo(EdwardsOids.Ed25519, der) } catch (_: Throwable) { - unwrapPrivateKeyInfo(EdwardsOids.Ed448, der) - } + KeyInfoUnwrap.unwrapPkcs8ForOids(der, listOf(EdwardsOids.Ed25519, EdwardsOids.Ed448)) } EdDSA.PrivateKey.Format.DER -> encodeToDer() EdDSA.PrivateKey.Format.PEM -> wrapPem(PemLabel.PrivateKey, encodeToDer()) diff --git a/cryptography-providers/jdk/src/jvmMain/kotlin/algorithms/JdkXDH.kt b/cryptography-providers/jdk/src/jvmMain/kotlin/algorithms/JdkXDH.kt index 118aa0e1..b5eb6fa6 100644 --- a/cryptography-providers/jdk/src/jvmMain/kotlin/algorithms/JdkXDH.kt +++ b/cryptography-providers/jdk/src/jvmMain/kotlin/algorithms/JdkXDH.kt @@ -84,9 +84,7 @@ internal class JdkXDH(private val state: JdkCryptographyState) : XDH { XDH.PublicKey.Format.JWK -> error("JWK is not supported") XDH.PublicKey.Format.RAW -> { val der = encodeToDer() - try { unwrapSubjectPublicKeyInfo(MontgomeryOids.X25519, der) } catch (_: Throwable) { - unwrapSubjectPublicKeyInfo(MontgomeryOids.X448, der) - } + KeyInfoUnwrap.unwrapSpkiForOids(der, listOf(MontgomeryOids.X25519, MontgomeryOids.X448)) } XDH.PublicKey.Format.DER -> encodeToDer() XDH.PublicKey.Format.PEM -> wrapPem(PemLabel.PublicKey, encodeToDer()) @@ -108,9 +106,7 @@ internal class JdkXDH(private val state: JdkCryptographyState) : XDH { XDH.PrivateKey.Format.JWK -> error("JWK is not supported") XDH.PrivateKey.Format.RAW -> { val der = encodeToDer() - try { unwrapPrivateKeyInfo(MontgomeryOids.X25519, der) } catch (_: Throwable) { - unwrapPrivateKeyInfo(MontgomeryOids.X448, der) - } + KeyInfoUnwrap.unwrapPkcs8ForOids(der, listOf(MontgomeryOids.X25519, MontgomeryOids.X448)) } XDH.PrivateKey.Format.DER -> encodeToDer() XDH.PrivateKey.Format.PEM -> wrapPem(PemLabel.PrivateKey, encodeToDer()) diff --git a/cryptography-providers/jdk/src/jvmMain/kotlin/algorithms/KeyInfoUnwrap.kt b/cryptography-providers/jdk/src/jvmMain/kotlin/algorithms/KeyInfoUnwrap.kt new file mode 100644 index 00000000..3163b534 --- /dev/null +++ b/cryptography-providers/jdk/src/jvmMain/kotlin/algorithms/KeyInfoUnwrap.kt @@ -0,0 +1,31 @@ +package dev.whyoleg.cryptography.providers.jdk.algorithms + +import dev.whyoleg.cryptography.serialization.asn1.* +import dev.whyoleg.cryptography.serialization.asn1.modules.* +import dev.whyoleg.cryptography.providers.base.materials.* + +internal object KeyInfoUnwrap { + fun unwrapSpkiForOids(der: ByteArray, oids: List): ByteArray { + var lastError: Throwable? = null + for (oid in oids) { + try { + return unwrapSubjectPublicKeyInfo(oid, der) + } catch (t: Throwable) { + lastError = t + } + } + throw lastError ?: IllegalArgumentException("No OID matched for SPKI unwrap") + } + + fun unwrapPkcs8ForOids(der: ByteArray, oids: List): ByteArray { + var lastError: Throwable? = null + for (oid in oids) { + try { + return unwrapPrivateKeyInfo(oid, der) + } catch (t: Throwable) { + lastError = t + } + } + throw lastError ?: IllegalArgumentException("No OID matched for PKCS#8 unwrap") + } +} diff --git a/cryptography-providers/openssl3/api/src/commonMain/kotlin/algorithms/Openssl3EdDSA.kt b/cryptography-providers/openssl3/api/src/commonMain/kotlin/algorithms/Openssl3EdDSA.kt index 641eddc4..5e61dbc8 100644 --- a/cryptography-providers/openssl3/api/src/commonMain/kotlin/algorithms/Openssl3EdDSA.kt +++ b/cryptography-providers/openssl3/api/src/commonMain/kotlin/algorithms/Openssl3EdDSA.kt @@ -125,180 +125,15 @@ internal object Openssl3EdDSA : EdDSA { } } -@OptIn(ExperimentalNativeApi::class, UnsafeNumber::class) + private class EdDsaSignatureGenerator( private val privateKey: CPointer, -) : SignatureGenerator { - private val cleaner = privateKey.upRef().cleaner() - - override fun createSignFunction(): SignFunction = EdDsaSignFunction(privateKey) - - override fun generateSignatureBlocking(data: ByteArray): ByteArray = memScoped { - val ctx = checkError(EVP_MD_CTX_new()) - try { - checkError( - EVP_DigestSignInit_ex( - ctx = ctx, - pctx = null, - mdname = null, // must be null for EdDSA one-shot - libctx = null, - props = null, - pkey = privateKey, - params = null - ) - ) - - data.usePinned { - val siglen = alloc() - checkError(EVP_DigestSign(ctx, null, siglen.ptr, it.safeAddressOfU(0), data.size.convert())) - val out = ByteArray(siglen.value.convert()) - out.usePinned { outPin -> - checkError(EVP_DigestSign(ctx, outPin.safeAddressOfU(0), siglen.ptr, it.safeAddressOfU(0), data.size.convert())) - } - out.ensureSizeExactly(siglen.value.convert()) - } - } finally { - EVP_MD_CTX_free(ctx) - } - } +) : Openssl3DigestSignatureGenerator(privateKey, hashAlgorithm = null) { + override fun MemScope.createParams(): CValuesRef? = null } -@OptIn(ExperimentalNativeApi::class, UnsafeNumber::class) private class EdDsaSignatureVerifier( private val publicKey: CPointer, -) : SignatureVerifier { - private val cleaner = publicKey.upRef().cleaner() - - override fun createVerifyFunction(): VerifyFunction = EdDsaVerifyFunction(publicKey) -} - -@OptIn(UnsafeNumber::class) -private class EdDsaSignFunction( - private val privateKey: CPointer, -) : SignFunction { - private var isClosed = false - private var accumulator = EmptyByteArray - - private fun ensureOpen() = check(!isClosed) { "Already closed" } - - override fun update(source: ByteArray, startIndex: Int, endIndex: Int) { - ensureOpen() - checkBounds(source.size, startIndex, endIndex) - accumulator += source.copyOfRange(startIndex, endIndex) - } - - override fun signIntoByteArray(destination: ByteArray, destinationOffset: Int): Int { - val sig = signToByteArray() - sig.copyInto(destination, destinationOffset) - return sig.size - } - - override fun signToByteArray(): ByteArray { - ensureOpen() - return memScoped { - val ctx = checkError(EVP_MD_CTX_new()) - try { - checkError( - EVP_DigestSignInit_ex( - ctx = ctx, - pctx = null, - mdname = null, - libctx = null, - props = null, - pkey = privateKey, - params = null - ) - ) - accumulator.usePinned { pin -> - val siglen = alloc() - checkError(EVP_DigestSign(ctx, null, siglen.ptr, pin.safeAddressOfU(0), accumulator.size.convert())) - val out = ByteArray(siglen.value.convert()) - out.usePinned { outPin -> - checkError(EVP_DigestSign(ctx, outPin.safeAddressOfU(0), siglen.ptr, pin.safeAddressOfU(0), accumulator.size.convert())) - } - out.ensureSizeExactly(siglen.value.convert()) - } - } finally { - EVP_MD_CTX_free(ctx) - reset() - } - } - } - - override fun reset() { - ensureOpen() - accumulator = EmptyByteArray - } - - override fun close() { - isClosed = true - accumulator = EmptyByteArray - } -} - -@OptIn(UnsafeNumber::class) -private class EdDsaVerifyFunction( - private val publicKey: CPointer, -) : VerifyFunction { - private var isClosed = false - private var accumulator = EmptyByteArray - - private fun ensureOpen() = check(!isClosed) { "Already closed" } - - override fun update(source: ByteArray, startIndex: Int, endIndex: Int) { - ensureOpen() - checkBounds(source.size, startIndex, endIndex) - accumulator += source.copyOfRange(startIndex, endIndex) - } - - override fun tryVerify(signature: ByteArray, startIndex: Int, endIndex: Int): Boolean { - ensureOpen() - checkBounds(signature.size, startIndex, endIndex) - return memScoped { - val ctx = checkError(EVP_MD_CTX_new()) - try { - checkError( - EVP_DigestVerifyInit_ex( - ctx = ctx, - pctx = null, - mdname = null, - libctx = null, - props = null, - pkey = publicKey, - params = null - ) - ) - signature.usePinned { sigPin -> - accumulator.usePinned { dataPin -> - val res = EVP_DigestVerify( - ctx, - sigPin.safeAddressOfU(startIndex), - (endIndex - startIndex).convert(), - dataPin.safeAddressOfU(0), - accumulator.size.convert() - ) - if (res != 0) checkError(res) - res == 1 - } - } - } finally { - EVP_MD_CTX_free(ctx) - reset() - } - } - } - - override fun verify(signature: ByteArray, startIndex: Int, endIndex: Int) { - check(tryVerify(signature, startIndex, endIndex)) { "Invalid signature" } - } - - override fun reset() { - ensureOpen() - accumulator = EmptyByteArray - } - - override fun close() { - isClosed = true - accumulator = EmptyByteArray - } +) : Openssl3DigestSignatureVerifier(publicKey, hashAlgorithm = null) { + override fun MemScope.createParams(): CValuesRef? = null } diff --git a/cryptography-providers/openssl3/api/src/commonMain/kotlin/operations/Openssl3DigestSignatureGenerator.kt b/cryptography-providers/openssl3/api/src/commonMain/kotlin/operations/Openssl3DigestSignatureGenerator.kt index 13336192..9cdcb216 100644 --- a/cryptography-providers/openssl3/api/src/commonMain/kotlin/operations/Openssl3DigestSignatureGenerator.kt +++ b/cryptography-providers/openssl3/api/src/commonMain/kotlin/operations/Openssl3DigestSignatureGenerator.kt @@ -14,19 +14,21 @@ import kotlin.experimental.* internal abstract class Openssl3DigestSignatureGenerator( private val privateKey: CPointer, - private val hashAlgorithm: String, + // when null, performs one-shot signing (e.g., EdDSA) + private val hashAlgorithm: String?, ) : SignatureGenerator { @OptIn(ExperimentalNativeApi::class) private val cleaner = privateKey.upRef().cleaner() protected abstract fun MemScope.createParams(): CValuesRef? - override fun createSignFunction(): SignFunction { - return Openssl3DigestSignFunction(Resource(checkError(EVP_MD_CTX_new()), ::EVP_MD_CTX_free)) + override fun createSignFunction(): SignFunction = when (hashAlgorithm) { + null -> OneShotSignFunction() + else -> StreamingSignFunction(Resource(checkError(EVP_MD_CTX_new()), ::EVP_MD_CTX_free)) } // inner class to have a reference to class with cleaner - private inner class Openssl3DigestSignFunction( + private inner class StreamingSignFunction( private val context: Resource>, ) : SignFunction, SafeCloseable(SafeCloseAction(context, AutoCloseable::close)) { init { @@ -66,7 +68,7 @@ internal abstract class Openssl3DigestSignatureGenerator( EVP_DigestSignInit_ex( ctx = context, pctx = null, - mdname = hashAlgorithm, + mdname = hashAlgorithm!!, libctx = null, props = null, pkey = privateKey, @@ -75,4 +77,68 @@ internal abstract class Openssl3DigestSignatureGenerator( ) } } + + private inner class OneShotSignFunction : SignFunction { + private var isClosed = false + private var accumulator = EmptyByteArray + + private fun ensureOpen() = check(!isClosed) { "Already closed" } + + override fun update(source: ByteArray, startIndex: Int, endIndex: Int) { + ensureOpen() + checkBounds(source.size, startIndex, endIndex) + // accumulate until final + accumulator += source.copyOfRange(startIndex, endIndex) + } + + override fun signIntoByteArray(destination: ByteArray, destinationOffset: Int): Int { + val sig = signToByteArray() + checkBounds(destination.size, destinationOffset, destinationOffset + sig.size) + sig.copyInto(destination, destinationOffset) + return sig.size + } + + @OptIn(UnsafeNumber::class) + override fun signToByteArray(): ByteArray = memScoped { + ensureOpen() + val ctx = checkError(EVP_MD_CTX_new()) + try { + checkError( + EVP_DigestSignInit_ex( + ctx = ctx, + pctx = null, + mdname = null, // one-shot mode + libctx = null, + props = null, + pkey = privateKey, + params = createParams() + ) + ) + + val siglen = alloc() + accumulator.usePinned { pin -> + checkError(EVP_DigestSign(ctx, null, siglen.ptr, pin.safeAddressOfU(0), accumulator.size.convert())) + val out = ByteArray(siglen.value.convert()) + out.usePinned { outPin -> + checkError(EVP_DigestSign(ctx, outPin.safeAddressOfU(0), siglen.ptr, pin.safeAddressOfU(0), accumulator.size.convert())) + } + out.ensureSizeExactly(siglen.value.convert()) + out + } + } finally { + EVP_MD_CTX_free(ctx) + isClosed = true + } + } + + override fun reset() { + isClosed = false + accumulator = EmptyByteArray + } + + override fun close() { + isClosed = true + accumulator = EmptyByteArray + } + } } diff --git a/cryptography-providers/openssl3/api/src/commonMain/kotlin/operations/Openssl3DigestSignatureVerifier.kt b/cryptography-providers/openssl3/api/src/commonMain/kotlin/operations/Openssl3DigestSignatureVerifier.kt index 3f6775ab..aa9257e4 100644 --- a/cryptography-providers/openssl3/api/src/commonMain/kotlin/operations/Openssl3DigestSignatureVerifier.kt +++ b/cryptography-providers/openssl3/api/src/commonMain/kotlin/operations/Openssl3DigestSignatureVerifier.kt @@ -13,19 +13,21 @@ import kotlin.experimental.* internal abstract class Openssl3DigestSignatureVerifier( private val publicKey: CPointer, - private val hashAlgorithm: String, + // when null, performs one-shot verification (e.g., EdDSA) + private val hashAlgorithm: String?, ) : SignatureVerifier { @OptIn(ExperimentalNativeApi::class) private val cleaner = publicKey.upRef().cleaner() protected abstract fun MemScope.createParams(): CValuesRef? - override fun createVerifyFunction(): VerifyFunction { - return Openssl3DigestVerifyFunction(Resource(checkError(EVP_MD_CTX_new()), ::EVP_MD_CTX_free)) + override fun createVerifyFunction(): VerifyFunction = when (hashAlgorithm) { + null -> OneShotVerifyFunction() + else -> StreamingVerifyFunction(Resource(checkError(EVP_MD_CTX_new()), ::EVP_MD_CTX_free)) } // inner class to have a reference to class with cleaner - private inner class Openssl3DigestVerifyFunction( + private inner class StreamingVerifyFunction( private val context: Resource>, ) : VerifyFunction, SafeCloseable(SafeCloseAction(context, AutoCloseable::close)) { init { @@ -67,7 +69,7 @@ internal abstract class Openssl3DigestSignatureVerifier( EVP_DigestVerifyInit_ex( ctx = context, pctx = null, - mdname = hashAlgorithm, + mdname = hashAlgorithm!!, libctx = null, props = null, pkey = publicKey, @@ -76,4 +78,63 @@ internal abstract class Openssl3DigestSignatureVerifier( ) } } + + private inner class OneShotVerifyFunction : VerifyFunction { + private var isClosed = false + private var accumulator = EmptyByteArray + + private fun ensureOpen() = check(!isClosed) { "Already closed" } + + override fun update(source: ByteArray, startIndex: Int, endIndex: Int) { + ensureOpen() + checkBounds(source.size, startIndex, endIndex) + accumulator += source.copyOfRange(startIndex, endIndex) + } + + @OptIn(UnsafeNumber::class) + override fun tryVerify(signature: ByteArray, startIndex: Int, endIndex: Int): Boolean = memScoped { + checkBounds(signature.size, startIndex, endIndex) + ensureOpen() + val ctx = checkError(EVP_MD_CTX_new()) + try { + checkError( + EVP_DigestVerifyInit_ex( + ctx = ctx, + pctx = null, + mdname = null, // one-shot mode + libctx = null, + props = null, + pkey = publicKey, + params = createParams() + ) + ) + + val result = accumulator.usePinned { dataPin -> + signature.usePinned { sigPin -> + EVP_DigestVerify( + ctx, + sigPin.safeAddressOfU(startIndex), + (endIndex - startIndex).convert(), + dataPin.safeAddressOfU(0), + accumulator.size.convert() + ) + } + } + if (result != 0) checkError(result) + result == 1 + } finally { + EVP_MD_CTX_free(ctx) + isClosed = true + } + } + + override fun verify(signature: ByteArray, startIndex: Int, endIndex: Int) { + check(tryVerify(signature, startIndex, endIndex)) { "Invalid signature" } + } + + override fun reset() { + isClosed = false + accumulator = EmptyByteArray + } + } } From c105f80c82aa195639bb3e88d9861683f45586fe Mon Sep 17 00:00:00 2001 From: Aria Wisp Date: Thu, 4 Sep 2025 16:00:15 -0600 Subject: [PATCH 18/22] Tests: gate EdDSA/XDH on Default JDK to >=15 to reflect availability; fix curve param decoding for EdDSA/XDH compat tests; adjust ASN.1 AlgorithmIdentifier to allow missing parameters (RFC 8410). --- .../compatibility/EdDsaCompatibilityTest.kt | 7 ++++++- .../compatibility/XdhCompatibilityTest.kt | 7 ++++++- .../tests/src/commonMain/kotlin/support.kt | 6 +++--- .../kotlin/AlgorithmIdentifierSerializer.kt | 19 ++++++++++++++----- 4 files changed, 29 insertions(+), 10 deletions(-) diff --git a/cryptography-providers/tests/src/commonMain/kotlin/compatibility/EdDsaCompatibilityTest.kt b/cryptography-providers/tests/src/commonMain/kotlin/compatibility/EdDsaCompatibilityTest.kt index 17007349..08a4e644 100644 --- a/cryptography-providers/tests/src/commonMain/kotlin/compatibility/EdDsaCompatibilityTest.kt +++ b/cryptography-providers/tests/src/commonMain/kotlin/compatibility/EdDsaCompatibilityTest.kt @@ -34,7 +34,12 @@ abstract class EdDsaCompatibilityTest( @Serializable private data class KeyParameters(val curveName: String) : TestParameters { - val curve get() = EdDSA.Curve(curveName) + val curve: EdDSA.Curve + get() = when (curveName) { + EdDSA.Curve.Ed25519.name -> EdDSA.Curve.Ed25519 + EdDSA.Curve.Ed448.name -> EdDSA.Curve.Ed448 + else -> error("Unsupported curve: $curveName") + } } override suspend fun CompatibilityTestScope.generate(isStressTest: Boolean) { diff --git a/cryptography-providers/tests/src/commonMain/kotlin/compatibility/XdhCompatibilityTest.kt b/cryptography-providers/tests/src/commonMain/kotlin/compatibility/XdhCompatibilityTest.kt index a0871dae..10ebcbf0 100644 --- a/cryptography-providers/tests/src/commonMain/kotlin/compatibility/XdhCompatibilityTest.kt +++ b/cryptography-providers/tests/src/commonMain/kotlin/compatibility/XdhCompatibilityTest.kt @@ -32,7 +32,12 @@ abstract class XdhCompatibilityTest( @Serializable private data class KeyParameters(val curveName: String) : TestParameters { - val curve get() = XDH.Curve(curveName) + val curve: XDH.Curve + get() = when (curveName) { + XDH.Curve.X25519.name -> XDH.Curve.X25519 + XDH.Curve.X448.name -> XDH.Curve.X448 + else -> error("Unsupported curve: $curveName") + } } override suspend fun CompatibilityTestScope.generate(isStressTest: Boolean) { diff --git a/cryptography-providers/tests/src/commonMain/kotlin/support.kt b/cryptography-providers/tests/src/commonMain/kotlin/support.kt index 011c3f39..1318a945 100644 --- a/cryptography-providers/tests/src/commonMain/kotlin/support.kt +++ b/cryptography-providers/tests/src/commonMain/kotlin/support.kt @@ -146,9 +146,9 @@ fun ProviderTestScope.supports(algorithmId: CryptographyAlgorithmId<*>): Boolean when (algorithmId) { AES.CMAC if provider.isJdkDefault -> "Default JDK provider doesn't support AES-CMAC, only supported with BouncyCastle" RSA.PSS if provider.isJdkDefault && platform.isAndroid -> "JDK provider on Android doesn't support RSASSA-PSS" - // Some JDKs used in CI (jvm / jvm11) may lack these algorithms - EdDSA if provider.isJdkDefault && platform.isJdk { major < 11 } -> "Default JDK doesn't support EdDSA before JDK 11" - XDH if provider.isJdkDefault && platform.isJdk { major < 11 } -> "Default JDK doesn't support XDH before JDK 11" + // Some JDKs used in CI (jvm / jvm11) lack these algorithms in the default provider + EdDSA if provider.isJdkDefault && platform.isJdk { major < 15 } -> "Default JDK may not support EdDSA before JDK 15" + XDH if provider.isJdkDefault && platform.isJdk { major < 15 } -> "Default JDK may not support XDH before JDK 15" else -> null } } diff --git a/cryptography-serialization/asn1/modules/src/commonMain/kotlin/AlgorithmIdentifierSerializer.kt b/cryptography-serialization/asn1/modules/src/commonMain/kotlin/AlgorithmIdentifierSerializer.kt index a1514747..755921ae 100644 --- a/cryptography-serialization/asn1/modules/src/commonMain/kotlin/AlgorithmIdentifierSerializer.kt +++ b/cryptography-serialization/asn1/modules/src/commonMain/kotlin/AlgorithmIdentifierSerializer.kt @@ -45,9 +45,18 @@ public abstract class AlgorithmIdentifierSerializer : index = 0, deserializer = ObjectIdentifier.serializer() ) - check(decodeElementIndex(descriptor) == 1) - val parameters = decodeParameters(algorithm) - check(decodeElementIndex(descriptor) == CompositeDecoder.DECODE_DONE) - parameters + when (val idx = decodeElementIndex(descriptor)) { + 1 -> { + val parameters = decodeParameters(algorithm) + check(decodeElementIndex(descriptor) == CompositeDecoder.DECODE_DONE) + parameters + } + CompositeDecoder.DECODE_DONE -> { + // Some algorithms (e.g., Ed25519/Ed448/X25519/X448 per RFC 8410) omit parameters. + // Delegate to subclass to construct an identifier without consuming parameters from the stream. + decodeParameters(algorithm) + } + else -> error("Unexpected element index: $idx") + } } -} \ No newline at end of file +} From 7eb984e44b058bef35a9350db796bb11b34d8d9f Mon Sep 17 00:00:00 2001 From: Aria Wisp Date: Thu, 4 Sep 2025 18:37:07 -0600 Subject: [PATCH 19/22] Apple/CryptoKit macOS tests: fix curve gating and ASN.1 decoding\n\n- Tests: use context.provider.isCryptoKit in EdDSA/XDH default tests\n- Compatibility: skip Ed448/X448 for CryptoKit to match capabilities\n- ASN.1: tolerate absent/NULL AlgorithmIdentifier parameters (RFC 8410)\n- SwiftInterop: generate SPM platforms with .vN style; prefer OS runtime\n\nAlso includes current local changes in support files and providers. --- .../commonMain/kotlin/algorithms/Openssl3EdDSA.kt | 4 ++++ .../commonMain/kotlin/algorithms/Openssl3XDH.kt | 2 ++ .../commonMain/kotlin/operations/KeyAgreement.kt | 2 +- .../operations/Openssl3DigestSignatureVerifier.kt | 5 +++++ .../compatibility/EdDsaCompatibilityTest.kt | 10 ++++++++-- .../kotlin/compatibility/XdhCompatibilityTest.kt | 2 ++ .../src/commonMain/kotlin/default/EdDsaTest.kt | 12 +++++++++--- .../src/commonMain/kotlin/default/XdhTest.kt | 7 +++++-- .../tests/src/commonMain/kotlin/support.kt | 13 +++++++++++++ .../kotlin/KeyAlgorithmIdentifierSerializer.kt | 6 ++++-- karma.config.d/chrome.js | 13 +++++++++++++ .../tasks/GenerateSwiftCinteropDefinitionTask.kt | 3 ++- .../tasks/GenerateSwiftPackageDefinitionTask.kt | 15 ++++++++++----- .../src/main/kotlin/tasks/XcodebuildBuildTask.kt | 8 +++++++- 14 files changed, 85 insertions(+), 17 deletions(-) create mode 100644 karma.config.d/chrome.js diff --git a/cryptography-providers/openssl3/api/src/commonMain/kotlin/algorithms/Openssl3EdDSA.kt b/cryptography-providers/openssl3/api/src/commonMain/kotlin/algorithms/Openssl3EdDSA.kt index 5e61dbc8..4728a04b 100644 --- a/cryptography-providers/openssl3/api/src/commonMain/kotlin/algorithms/Openssl3EdDSA.kt +++ b/cryptography-providers/openssl3/api/src/commonMain/kotlin/algorithms/Openssl3EdDSA.kt @@ -15,6 +15,10 @@ import kotlinx.cinterop.* import platform.posix.* import kotlin.experimental.* import dev.whyoleg.cryptography.serialization.asn1.modules.* +import dev.whyoleg.cryptography.serialization.asn1.ObjectIdentifier +import dev.whyoleg.cryptography.providers.base.materials.* +import dev.whyoleg.cryptography.providers.openssl3.operations.Openssl3DigestSignatureGenerator +import dev.whyoleg.cryptography.providers.openssl3.operations.Openssl3DigestSignatureVerifier internal object Openssl3EdDSA : EdDSA { private fun algorithmName(curve: EdDSA.Curve): String = when (curve) { diff --git a/cryptography-providers/openssl3/api/src/commonMain/kotlin/algorithms/Openssl3XDH.kt b/cryptography-providers/openssl3/api/src/commonMain/kotlin/algorithms/Openssl3XDH.kt index 535ae73d..d591c3e0 100644 --- a/cryptography-providers/openssl3/api/src/commonMain/kotlin/algorithms/Openssl3XDH.kt +++ b/cryptography-providers/openssl3/api/src/commonMain/kotlin/algorithms/Openssl3XDH.kt @@ -16,6 +16,8 @@ import platform.posix.* import kotlin.experimental.* import dev.whyoleg.cryptography.providers.openssl3.operations.* import dev.whyoleg.cryptography.serialization.asn1.modules.* +import dev.whyoleg.cryptography.serialization.asn1.ObjectIdentifier +import dev.whyoleg.cryptography.providers.base.materials.* internal object Openssl3XDH : XDH { private fun algorithmName(curve: XDH.Curve): String = when (curve) { diff --git a/cryptography-providers/openssl3/api/src/commonMain/kotlin/operations/KeyAgreement.kt b/cryptography-providers/openssl3/api/src/commonMain/kotlin/operations/KeyAgreement.kt index 2492676c..5ded6a9a 100644 --- a/cryptography-providers/openssl3/api/src/commonMain/kotlin/operations/KeyAgreement.kt +++ b/cryptography-providers/openssl3/api/src/commonMain/kotlin/operations/KeyAgreement.kt @@ -5,6 +5,7 @@ package dev.whyoleg.cryptography.providers.openssl3.operations import dev.whyoleg.cryptography.providers.openssl3.internal.* +import dev.whyoleg.cryptography.providers.base.* import dev.whyoleg.cryptography.providers.openssl3.internal.cinterop.* import kotlinx.cinterop.* import platform.posix.* @@ -28,4 +29,3 @@ internal fun deriveSharedSecret( EVP_PKEY_CTX_free(context) } } - diff --git a/cryptography-providers/openssl3/api/src/commonMain/kotlin/operations/Openssl3DigestSignatureVerifier.kt b/cryptography-providers/openssl3/api/src/commonMain/kotlin/operations/Openssl3DigestSignatureVerifier.kt index aa9257e4..9c3a9efe 100644 --- a/cryptography-providers/openssl3/api/src/commonMain/kotlin/operations/Openssl3DigestSignatureVerifier.kt +++ b/cryptography-providers/openssl3/api/src/commonMain/kotlin/operations/Openssl3DigestSignatureVerifier.kt @@ -136,5 +136,10 @@ internal abstract class Openssl3DigestSignatureVerifier( isClosed = false accumulator = EmptyByteArray } + + override fun close() { + isClosed = true + accumulator = EmptyByteArray + } } } diff --git a/cryptography-providers/tests/src/commonMain/kotlin/compatibility/EdDsaCompatibilityTest.kt b/cryptography-providers/tests/src/commonMain/kotlin/compatibility/EdDsaCompatibilityTest.kt index 08a4e644..a11e05f5 100644 --- a/cryptography-providers/tests/src/commonMain/kotlin/compatibility/EdDsaCompatibilityTest.kt +++ b/cryptography-providers/tests/src/commonMain/kotlin/compatibility/EdDsaCompatibilityTest.kt @@ -75,8 +75,14 @@ abstract class EdDsaCompatibilityTest( } private fun ProviderTestScope.supportsAlgorithmOnCurve(curve: EdDSA.Curve): Boolean { - // no per-curve gating at the moment; provider-level SupportedAlgorithmsTest already checks availability - return true + return when { + // CryptoKit supports only Ed25519 + context.provider.isCryptoKit && curve == EdDSA.Curve.Ed448 -> { + logger.print("SKIP: CryptoKit supports Ed25519 only") + false + } + else -> true + } } override suspend fun CompatibilityTestScope.validate() { diff --git a/cryptography-providers/tests/src/commonMain/kotlin/compatibility/XdhCompatibilityTest.kt b/cryptography-providers/tests/src/commonMain/kotlin/compatibility/XdhCompatibilityTest.kt index 10ebcbf0..582a9572 100644 --- a/cryptography-providers/tests/src/commonMain/kotlin/compatibility/XdhCompatibilityTest.kt +++ b/cryptography-providers/tests/src/commonMain/kotlin/compatibility/XdhCompatibilityTest.kt @@ -44,6 +44,8 @@ abstract class XdhCompatibilityTest( val parametersId = api.sharedSecrets.saveParameters(TestParameters.Empty) listOf(XDH.Curve.X25519, XDH.Curve.X448).forEach { curve -> + // CryptoKit supports only X25519 + if (context.provider.isCryptoKit && curve == XDH.Curve.X448) return@forEach val keyParametersId = api.keyPairs.saveParameters(KeyParameters(curve.name)) val keyIterations = if (isStressTest) 5 else 2 diff --git a/cryptography-providers/tests/src/commonMain/kotlin/default/EdDsaTest.kt b/cryptography-providers/tests/src/commonMain/kotlin/default/EdDsaTest.kt index 5c60d100..f88eb618 100644 --- a/cryptography-providers/tests/src/commonMain/kotlin/default/EdDsaTest.kt +++ b/cryptography-providers/tests/src/commonMain/kotlin/default/EdDsaTest.kt @@ -15,7 +15,11 @@ abstract class EdDsaTest(provider: CryptographyProvider) : AlgorithmTest( @Test fun testSignVerify() = testWithAlgorithm { - listOf(EdDSA.Curve.Ed25519, EdDSA.Curve.Ed448).forEach { curve -> + val curves = listOf(EdDSA.Curve.Ed25519, EdDSA.Curve.Ed448).filter { curve -> + // CryptoKit supports only Ed25519 + !(context.provider.isCryptoKit && curve == EdDSA.Curve.Ed448) + }.ifEmpty { listOf(EdDSA.Curve.Ed25519) } + curves.forEach { curve -> val keyPair = algorithm.keyPairGenerator(curve).generateKey() val dataSets = listOf( @@ -36,7 +40,10 @@ abstract class EdDsaTest(provider: CryptographyProvider) : AlgorithmTest( fun testFunctions() = testWithAlgorithm { if (!supportsFunctions()) return@testWithAlgorithm - listOf(EdDSA.Curve.Ed25519, EdDSA.Curve.Ed448).forEach { curve -> + val curves = listOf(EdDSA.Curve.Ed25519, EdDSA.Curve.Ed448).filter { curve -> + !(context.provider.isCryptoKit && curve == EdDSA.Curve.Ed448) + }.ifEmpty { listOf(EdDSA.Curve.Ed25519) } + curves.forEach { curve -> val keyPair = algorithm.keyPairGenerator(curve).generateKey() repeat(5) { val size = CryptographyRandom.nextInt(256, 4096) @@ -46,4 +53,3 @@ abstract class EdDsaTest(provider: CryptographyProvider) : AlgorithmTest( } } } - diff --git a/cryptography-providers/tests/src/commonMain/kotlin/default/XdhTest.kt b/cryptography-providers/tests/src/commonMain/kotlin/default/XdhTest.kt index 90c59ad1..84c9e5e1 100644 --- a/cryptography-providers/tests/src/commonMain/kotlin/default/XdhTest.kt +++ b/cryptography-providers/tests/src/commonMain/kotlin/default/XdhTest.kt @@ -13,7 +13,11 @@ abstract class XdhTest(provider: CryptographyProvider) : AlgorithmTest(XDH, @Test fun testDeriveSharedSecret() = testWithAlgorithm { - listOf(XDH.Curve.X25519, XDH.Curve.X448).forEach { curve -> + val curves = listOf(XDH.Curve.X25519, XDH.Curve.X448).filter { curve -> + // CryptoKit supports only X25519 + !(context.provider.isCryptoKit && curve == XDH.Curve.X448) + }.ifEmpty { listOf(XDH.Curve.X25519) } + curves.forEach { curve -> val a = algorithm.keyPairGenerator(curve).generateKey() val b = algorithm.keyPairGenerator(curve).generateKey() @@ -28,4 +32,3 @@ abstract class XdhTest(provider: CryptographyProvider) : AlgorithmTest(XDH, } } } - diff --git a/cryptography-providers/tests/src/commonMain/kotlin/support.kt b/cryptography-providers/tests/src/commonMain/kotlin/support.kt index 1318a945..48440898 100644 --- a/cryptography-providers/tests/src/commonMain/kotlin/support.kt +++ b/cryptography-providers/tests/src/commonMain/kotlin/support.kt @@ -41,6 +41,11 @@ fun AlgorithmTestScope<*>.supportsKeyFormat(format: KeyFormat): Boolean = suppor // only WebCrypto supports JWK for now format.name == "JWK" && !provider.isWebCrypto -> "JWK key format" + // WebCrypto cannot export/import private keys in 'raw' format; requires PKCS#8 + (provider.isWebCrypto && ( + format == EdDSA.PrivateKey.Format.RAW || + format == XDH.PrivateKey.Format.RAW + )) -> "RAW private key format" format == EC.PublicKey.Format.RAW.Compressed && provider.isApple -> "compressed key format" else -> null @@ -146,6 +151,14 @@ fun ProviderTestScope.supports(algorithmId: CryptographyAlgorithmId<*>): Boolean when (algorithmId) { AES.CMAC if provider.isJdkDefault -> "Default JDK provider doesn't support AES-CMAC, only supported with BouncyCastle" RSA.PSS if provider.isJdkDefault && platform.isAndroid -> "JDK provider on Android doesn't support RSASSA-PSS" + // CryptoKit does not expose RSA primitives + RSA.PSS if provider.isCryptoKit -> "CryptoKit RSA-PSS" + RSA.OAEP if provider.isCryptoKit -> "CryptoKit RSA-OAEP" + RSA.PKCS1 if provider.isCryptoKit -> "CryptoKit RSA-PKCS1" + RSA.RAW if provider.isCryptoKit -> "CryptoKit RSA-RAW" + // Browsers may not expose EdDSA/XDH via WebCrypto + EdDSA if provider.isWebCrypto && platform.isBrowser -> "WebCrypto EdDSA not available in this engine" + XDH if provider.isWebCrypto && platform.isBrowser -> "WebCrypto X25519/X448 not available in this engine" // Some JDKs used in CI (jvm / jvm11) lack these algorithms in the default provider EdDSA if provider.isJdkDefault && platform.isJdk { major < 15 } -> "Default JDK may not support EdDSA before JDK 15" XDH if provider.isJdkDefault && platform.isJdk { major < 15 } -> "Default JDK may not support XDH before JDK 15" diff --git a/cryptography-serialization/asn1/modules/src/commonMain/kotlin/KeyAlgorithmIdentifierSerializer.kt b/cryptography-serialization/asn1/modules/src/commonMain/kotlin/KeyAlgorithmIdentifierSerializer.kt index 4f2ad883..1600583f 100644 --- a/cryptography-serialization/asn1/modules/src/commonMain/kotlin/KeyAlgorithmIdentifierSerializer.kt +++ b/cryptography-serialization/asn1/modules/src/commonMain/kotlin/KeyAlgorithmIdentifierSerializer.kt @@ -26,8 +26,10 @@ internal object KeyAlgorithmIdentifierSerializer : AlgorithmIdentifierSerializer } ObjectIdentifier.EC -> EcKeyAlgorithmIdentifier(decodeParameters(EcParameters.serializer())) else -> { - // TODO: somehow we should ignore parameters here + // For algorithms like Ed25519/Ed448/X25519/X448 (RFC 8410), parameters MUST be absent. + // Some encoders still emit NULL; consume it if present to keep the decoder position in sync. + decodeParameters(NothingSerializer()) UnknownKeyAlgorithmIdentifier(algorithm) } } -} \ No newline at end of file +} diff --git a/karma.config.d/chrome.js b/karma.config.d/chrome.js new file mode 100644 index 00000000..9eceef4f --- /dev/null +++ b/karma.config.d/chrome.js @@ -0,0 +1,13 @@ +/* + * Enable experimental WebCrypto features (EdDSA/XDH) in headless Chrome. + */ + +config.customLaunchers = config.customLaunchers || {} +config.customLaunchers.ChromeHeadlessExperimental = { + base: 'ChromeHeadless', + flags: ['--enable-experimental-web-platform-features'] +} + +// Prefer our custom launcher for tests +config.browsers = ['ChromeHeadlessExperimental'] + diff --git a/swiftinterop/src/main/kotlin/tasks/GenerateSwiftCinteropDefinitionTask.kt b/swiftinterop/src/main/kotlin/tasks/GenerateSwiftCinteropDefinitionTask.kt index 451163b0..22660063 100644 --- a/swiftinterop/src/main/kotlin/tasks/GenerateSwiftCinteropDefinitionTask.kt +++ b/swiftinterop/src/main/kotlin/tasks/GenerateSwiftCinteropDefinitionTask.kt @@ -75,7 +75,8 @@ abstract class GenerateSwiftCinteropDefinitionTask : DefaultTask() { libsDir: String, version: Provider, ): String { - val linker = "-L/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/$libsDir/" + val developerDir = System.getenv("DEVELOPER_DIR") ?: "/Applications/Xcode.app/Contents/Developer" + val linker = "-L$developerDir/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/$libsDir/" val v = version.orNull ?: return linker return "-platform_version $os ${v}.0 ${v}.0 $linker" } diff --git a/swiftinterop/src/main/kotlin/tasks/GenerateSwiftPackageDefinitionTask.kt b/swiftinterop/src/main/kotlin/tasks/GenerateSwiftPackageDefinitionTask.kt index e5257c1c..3243da4f 100644 --- a/swiftinterop/src/main/kotlin/tasks/GenerateSwiftPackageDefinitionTask.kt +++ b/swiftinterop/src/main/kotlin/tasks/GenerateSwiftPackageDefinitionTask.kt @@ -44,11 +44,12 @@ abstract class GenerateSwiftPackageDefinitionTask : DefaultTask() { outputDirectory.get().asFile.recreateDirectories() val swiftinteropModuleName = swiftinteropModuleName.get() + fun ver(v: String) = ".v$v" val platforms = listOfNotNull( - iosVersion.orNull?.let { ".iOS(\"$it\")" }, - macosVersion.orNull?.let { ".macOS(\"$it\")" }, - tvosVersion.orNull?.let { ".tvOS(\"$it\")" }, - watchosVersion.orNull?.let { ".watchOS(\"$it\")" }, + iosVersion.orNull?.let { ".iOS(${ver(it)})" }, + macosVersion.orNull?.let { ".macOS(${ver(it)})" }, + tvosVersion.orNull?.let { ".tvOS(${ver(it)})" }, + watchosVersion.orNull?.let { ".watchOS(${ver(it)})" }, ).joinToString(",") swiftPackageFile.get().asFile.writeText( @@ -69,7 +70,11 @@ abstract class GenerateSwiftPackageDefinitionTask : DefaultTask() { dependencies: [], targets: [ .target( - name: "$swiftinteropModuleName" + name: "$swiftinteropModuleName", + swiftSettings: [ + // Prefer OS runtime, avoid back-deployment compatibility libs on newer toolchains + .unsafeFlags(["-runtime-compatibility-version", "none"]) + ] ) ] ) diff --git a/swiftinterop/src/main/kotlin/tasks/XcodebuildBuildTask.kt b/swiftinterop/src/main/kotlin/tasks/XcodebuildBuildTask.kt index 1e4e8ec5..40282bf6 100644 --- a/swiftinterop/src/main/kotlin/tasks/XcodebuildBuildTask.kt +++ b/swiftinterop/src/main/kotlin/tasks/XcodebuildBuildTask.kt @@ -45,13 +45,19 @@ abstract class XcodebuildBuildTask : DefaultTask() { exec.exec { it.workingDir(temporaryDir) - it.commandLine( + val developerDir = System.getenv("DEVELOPER_DIR") ?: "" + val forceNoCompat = listOf("SWIFT_RUNTIME_COMPATIBILITY_VERSION=none") + val base = mutableListOf( "xcodebuild", "build", "-scheme", swiftinteropModuleName.get(), "-configuration", "Release", "-derivedDataPath", outputDirectory.get().asFile.absolutePath, "-destination", destination.get() ) + // Prefer setting runtime compatibility to none for newer toolchains (Xcode 26+) + // We conservatively always append it; older toolchains ignore unknown setting. + base.addAll(forceNoCompat) + it.commandLine(base) } } } From 88bc7d30d49980e6d42bc3f585f6124bd48dc0b4 Mon Sep 17 00:00:00 2001 From: Aria Wisp Date: Thu, 4 Sep 2025 19:26:43 -0600 Subject: [PATCH 20/22] base: add AccumulatingSignFunction/VerifyFunction and use in CryptoKit EdDSA\n\n- Extract accumulating signature update logic to provider-base\n- Replace CryptoKit EdDSA local functions with shared base impls --- .../AccumulatingSignatureFunctions.kt | 97 +++++++++++++++++++ .../kotlin/algorithms/CryptoKitEdDSA.kt | 66 +++---------- 2 files changed, 110 insertions(+), 53 deletions(-) create mode 100644 cryptography-providers/base/src/commonMain/kotlin/operations/AccumulatingSignatureFunctions.kt diff --git a/cryptography-providers/base/src/commonMain/kotlin/operations/AccumulatingSignatureFunctions.kt b/cryptography-providers/base/src/commonMain/kotlin/operations/AccumulatingSignatureFunctions.kt new file mode 100644 index 00000000..743d2a2b --- /dev/null +++ b/cryptography-providers/base/src/commonMain/kotlin/operations/AccumulatingSignatureFunctions.kt @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2025 Oleg Yukhnevich. Use of this source code is governed by the Apache 2.0 license. + */ + +package dev.whyoleg.cryptography.providers.base.operations + +import dev.whyoleg.cryptography.* +import dev.whyoleg.cryptography.operations.* +import dev.whyoleg.cryptography.providers.base.* + +@CryptographyProviderApi +public class AccumulatingSignFunction( + private val oneShot: (data: ByteArray) -> ByteArray, +) : SignFunction { + private var closed = false + private val chunks = ArrayList(4) + + private fun ensureOpen() { check(!closed) { "Already closed" } } + + override fun update(source: ByteArray, startIndex: Int, endIndex: Int) { + ensureOpen() + checkBounds(source.size, startIndex, endIndex) + if (startIndex == 0 && endIndex == source.size) chunks += source + else chunks += source.copyOfRange(startIndex, endIndex) + } + + override fun signIntoByteArray(destination: ByteArray, destinationOffset: Int): Int { + val sig = signToByteArray() + sig.copyInto(destination, destinationOffset) + return sig.size + } + + override fun signToByteArray(): ByteArray { + ensureOpen() + val total = chunks.sumOf { it.size } + val data = ByteArray(total) + var off = 0 + chunks.forEach { arr -> arr.copyInto(data, off); off += arr.size } + val out = oneShot(data) + reset() + return out + } + + override fun reset() { + ensureOpen() + chunks.clear() + } + + override fun close() { + closed = true + chunks.clear() + } +} + +@CryptographyProviderApi +public class AccumulatingVerifyFunction( + private val oneShot: (data: ByteArray, signature: ByteArray, startIndex: Int, endIndex: Int) -> Boolean, +) : VerifyFunction { + private var closed = false + private val chunks = ArrayList(4) + + private fun ensureOpen() { check(!closed) { "Already closed" } } + + override fun update(source: ByteArray, startIndex: Int, endIndex: Int) { + ensureOpen() + checkBounds(source.size, startIndex, endIndex) + if (startIndex == 0 && endIndex == source.size) chunks += source + else chunks += source.copyOfRange(startIndex, endIndex) + } + + override fun tryVerify(signature: ByteArray, startIndex: Int, endIndex: Int): Boolean { + ensureOpen() + checkBounds(signature.size, startIndex, endIndex) + val total = chunks.sumOf { it.size } + val data = ByteArray(total) + var off = 0 + chunks.forEach { arr -> arr.copyInto(data, off); off += arr.size } + val ok = oneShot(data, signature, startIndex, endIndex) + reset() + return ok + } + + override fun verify(signature: ByteArray, startIndex: Int, endIndex: Int) { + check(tryVerify(signature, startIndex, endIndex)) { "Invalid signature" } + } + + override fun reset() { + ensureOpen() + chunks.clear() + } + + override fun close() { + closed = true + chunks.clear() + } +} + diff --git a/cryptography-providers/cryptokit/src/commonMain/kotlin/algorithms/CryptoKitEdDSA.kt b/cryptography-providers/cryptokit/src/commonMain/kotlin/algorithms/CryptoKitEdDSA.kt index e8a003bc..bdacca4d 100644 --- a/cryptography-providers/cryptokit/src/commonMain/kotlin/algorithms/CryptoKitEdDSA.kt +++ b/cryptography-providers/cryptokit/src/commonMain/kotlin/algorithms/CryptoKitEdDSA.kt @@ -11,6 +11,7 @@ import dev.whyoleg.cryptography.providers.cryptokit.internal.* import dev.whyoleg.cryptography.providers.cryptokit.internal.swiftinterop.* import dev.whyoleg.cryptography.providers.base.* import dev.whyoleg.cryptography.providers.base.materials.* +import dev.whyoleg.cryptography.providers.base.operations.* import dev.whyoleg.cryptography.serialization.asn1.* import dev.whyoleg.cryptography.serialization.asn1.modules.* import dev.whyoleg.cryptography.serialization.pem.* @@ -95,7 +96,14 @@ internal object CryptoKitEdDSA : EdDSA { } override fun signatureVerifier(): SignatureVerifier = object : SignatureVerifier { - override fun createVerifyFunction(): VerifyFunction = EdVerifyFunction(key) + override fun createVerifyFunction(): VerifyFunction = + AccumulatingVerifyFunction { data, signature, startIndex, endIndex -> + val sig = signature.copyOfRange(startIndex, endIndex) + val ok = data.useNSData { dataNs -> sig.useNSData { sigNs -> + key.verifyWithSignature(sigNs, message = dataNs) + } } as Boolean + ok + } } } @@ -121,60 +129,12 @@ internal object CryptoKitEdDSA : EdDSA { } override fun signatureGenerator(): SignatureGenerator = object : SignatureGenerator { - override fun createSignFunction(): SignFunction = EdSignFunction(key) + override fun createSignFunction(): SignFunction = + AccumulatingSignFunction { data -> + swiftTry { error -> data.useNSData { key.signWithMessage(it, error) } }.toByteArray() + } override fun generateSignatureBlocking(data: ByteArray): ByteArray = swiftTry { error -> data.useNSData { key.signWithMessage(it, error) } }.toByteArray() } } } - -private class EdSignFunction( - private val key: SwiftEdDsaPrivateKey, -) : SignFunction { - private var closed = false - private val buffer = ArrayList(4) - override fun update(source: ByteArray, startIndex: Int, endIndex: Int) { - check(!closed) { "Already closed" } - buffer += source.copyOfRange(startIndex, endIndex) - } - override fun signIntoByteArray(destination: ByteArray, destinationOffset: Int): Int { - val sig = signToByteArray() - sig.copyInto(destination, destinationOffset) - return sig.size - } - override fun signToByteArray(): ByteArray { - check(!closed) { "Already closed" } - val data = buffer.fold(ByteArray(0)) { acc, arr -> acc + arr } - val out = swiftTry { error -> data.useNSData { key.signWithMessage(it, error) } } - reset() - return out.toByteArray() - } - override fun reset() { buffer.clear() } - override fun close() { closed = true; buffer.clear() } -} - -private class EdVerifyFunction( - private val key: SwiftEdDsaPublicKey, -) : VerifyFunction { - private var closed = false - private val buffer = ArrayList(4) - override fun update(source: ByteArray, startIndex: Int, endIndex: Int) { - check(!closed) { "Already closed" } - buffer += source.copyOfRange(startIndex, endIndex) - } - override fun tryVerify(signature: ByteArray, startIndex: Int, endIndex: Int): Boolean { - check(!closed) { "Already closed" } - val data = buffer.fold(ByteArray(0)) { acc, arr -> acc + arr } - val sig = signature.copyOfRange(startIndex, endIndex) - val ok = data.useNSData { dataNs -> sig.useNSData { sigNs -> - key.verifyWithSignature(sigNs, message = dataNs) - } } as Boolean - reset() - return ok - } - override fun verify(signature: ByteArray, startIndex: Int, endIndex: Int) { - check(tryVerify(signature, startIndex, endIndex)) { "Invalid signature" } - } - override fun reset() { buffer.clear() } - override fun close() { closed = true; buffer.clear() } -} From 47766f6ba50c6eabada6d5ccfb5c44dabfd1c44b Mon Sep 17 00:00:00 2001 From: Aria Wisp Date: Thu, 4 Sep 2025 19:43:03 -0600 Subject: [PATCH 21/22] =?UTF-8?q?tests:=20split=20JDK=20gating=20=E2=80=94?= =?UTF-8?q?=20EdDSA=20since=20JDK=2015,=20XDH=20since=20JDK=2011?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cryptography-providers/tests/src/commonMain/kotlin/support.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cryptography-providers/tests/src/commonMain/kotlin/support.kt b/cryptography-providers/tests/src/commonMain/kotlin/support.kt index 48440898..da68505c 100644 --- a/cryptography-providers/tests/src/commonMain/kotlin/support.kt +++ b/cryptography-providers/tests/src/commonMain/kotlin/support.kt @@ -161,7 +161,7 @@ fun ProviderTestScope.supports(algorithmId: CryptographyAlgorithmId<*>): Boolean XDH if provider.isWebCrypto && platform.isBrowser -> "WebCrypto X25519/X448 not available in this engine" // Some JDKs used in CI (jvm / jvm11) lack these algorithms in the default provider EdDSA if provider.isJdkDefault && platform.isJdk { major < 15 } -> "Default JDK may not support EdDSA before JDK 15" - XDH if provider.isJdkDefault && platform.isJdk { major < 15 } -> "Default JDK may not support XDH before JDK 15" + XDH if provider.isJdkDefault && platform.isJdk { major < 11 } -> "Default JDK may not support XDH before JDK 11" else -> null } } From bb4b5697bb8df763079b4cca32ea2546b5fbd878 Mon Sep 17 00:00:00 2001 From: Aria Wisp Date: Thu, 4 Sep 2025 19:44:12 -0600 Subject: [PATCH 22/22] tests: drop WebCrypto EdDSA/XDH skip rules; docs: add RFC links for EdDSA (RFC 8032) and XDH (RFC 7748) --- .../tests/src/commonMain/kotlin/support.kt | 4 +--- docs/providers/webcrypto.md | 5 ++++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/cryptography-providers/tests/src/commonMain/kotlin/support.kt b/cryptography-providers/tests/src/commonMain/kotlin/support.kt index da68505c..479443a5 100644 --- a/cryptography-providers/tests/src/commonMain/kotlin/support.kt +++ b/cryptography-providers/tests/src/commonMain/kotlin/support.kt @@ -156,9 +156,7 @@ fun ProviderTestScope.supports(algorithmId: CryptographyAlgorithmId<*>): Boolean RSA.OAEP if provider.isCryptoKit -> "CryptoKit RSA-OAEP" RSA.PKCS1 if provider.isCryptoKit -> "CryptoKit RSA-PKCS1" RSA.RAW if provider.isCryptoKit -> "CryptoKit RSA-RAW" - // Browsers may not expose EdDSA/XDH via WebCrypto - EdDSA if provider.isWebCrypto && platform.isBrowser -> "WebCrypto EdDSA not available in this engine" - XDH if provider.isWebCrypto && platform.isBrowser -> "WebCrypto X25519/X448 not available in this engine" + // No explicit WebCrypto skips: let engines handle availability // Some JDKs used in CI (jvm / jvm11) lack these algorithms in the default provider EdDSA if provider.isJdkDefault && platform.isJdk { major < 15 } -> "Default JDK may not support EdDSA before JDK 15" XDH if provider.isJdkDefault && platform.isJdk { major < 11 } -> "Default JDK may not support XDH before JDK 11" diff --git a/docs/providers/webcrypto.md b/docs/providers/webcrypto.md index 84e0a33d..8c61de31 100644 --- a/docs/providers/webcrypto.md +++ b/docs/providers/webcrypto.md @@ -9,7 +9,7 @@ For supported targets and algorithms, please consult [Supported primitives secti * only `suspend` functions are supported, because `WebCrypto` API is async by default * AES.* (browser only): may not support `192 bit` keys * AES.CBC: only `padding=true` is supported -* EdDSA/XDH: These algorithms were added later to WebCrypto and might not be available in all engines/browsers yet. If unsupported, operations will fail at runtime with an error from the underlying engine. +* EdDSA/XDH: These algorithms were added later to WebCrypto and might not be available in all engines/browsers yet (see [RFC 8032] and [RFC 7748]). If unsupported, operations will fail at runtime with an error from the underlying engine. ## Example @@ -34,3 +34,6 @@ dependencies { [WebCrypto]: https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API [Supported primitives section]: index.md#supported-primitives + +[RFC 8032]: https://www.rfc-editor.org/rfc/rfc8032 +[RFC 7748]: https://www.rfc-editor.org/rfc/rfc7748