diff --git a/build-logic/src/main/kotlin/ckbuild/tests/GenerateProviderTestsTask.kt b/build-logic/src/main/kotlin/ckbuild/tests/GenerateProviderTestsTask.kt index d3a10432..f8a75310 100644 --- a/build-logic/src/main/kotlin/ckbuild/tests/GenerateProviderTestsTask.kt +++ b/build-logic/src/main/kotlin/ckbuild/tests/GenerateProviderTestsTask.kt @@ -110,6 +110,12 @@ abstract class GenerateProviderTestsTask : DefaultTask() { "EcdsaCompatibilityTest", "EcdhCompatibilityTest", + // Edwards-family + "EdDsaTest", + "EdDsaCompatibilityTest", + "XdhTest", + "XdhCompatibilityTest", + "RsaOaepTest", "RsaOaepCompatibilityTest", "RsaPkcs1Test", diff --git a/cryptography-core/api/cryptography-core.api b/cryptography-core/api/cryptography-core.api index 5ec6874f..19feee1f 100644 --- a/cryptography-core/api/cryptography-core.api +++ b/cryptography-core/api/cryptography-core.api @@ -418,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; @@ -689,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 b853e00c..93319f4e 100644 --- a/cryptography-core/api/cryptography-core.klib.api +++ b/cryptography-core/api/cryptography-core.klib.api @@ -551,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] @@ -597,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/EdDSA.kt b/cryptography-core/src/commonMain/kotlin/algorithms/EdDSA.kt new file mode 100644 index 00000000..c9b20fee --- /dev/null +++ b/cryptography-core/src/commonMain/kotlin/algorithms/EdDSA.kt @@ -0,0 +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 : 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 : Key { + public val publicKey: PublicKey + public val privateKey: PrivateKey + } + + @SubclassOptInRequired(CryptographyProviderApi::class) + 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 : 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 new file mode 100644 index 00000000..0f7ddd34 --- /dev/null +++ b/cryptography-core/src/commonMain/kotlin/algorithms/XDH.kt @@ -0,0 +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 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 : Key { + public val publicKey: PublicKey + public val privateKey: PrivateKey + } + + @SubclassOptInRequired(CryptographyProviderApi::class) + 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 : 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/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/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..bdacca4d --- /dev/null +++ b/cryptography-providers/cryptokit/src/commonMain/kotlin/algorithms/CryptoKitEdDSA.kt @@ -0,0 +1,140 @@ +/* + * 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.providers.base.operations.* +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(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(EdwardsOids.Ed25519, 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(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(EdwardsOids.Ed25519, 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(EdwardsOids.Ed25519), + key.rawRepresentation().toByteArray() + ) + EdDSA.PublicKey.Format.PEM -> wrapPem( + PemLabel.PublicKey, + wrapSubjectPublicKeyInfo( + UnknownKeyAlgorithmIdentifier(EdwardsOids.Ed25519), + key.rawRepresentation().toByteArray() + ) + ) + else -> error("$format is not supported by CryptoKit EdDSA") + } + + override fun signatureVerifier(): SignatureVerifier = object : SignatureVerifier { + 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 + } + } + } + + 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(EdwardsOids.Ed25519), + key.rawRepresentation().toByteArray() + ) + EdDSA.PrivateKey.Format.PEM -> wrapPem( + PemLabel.PrivateKey, + wrapPrivateKeyInfo( + 0, + UnknownKeyAlgorithmIdentifier(EdwardsOids.Ed25519), + key.rawRepresentation().toByteArray() + ) + ) + else -> error("$format is not supported by CryptoKit EdDSA") + } + + override fun signatureGenerator(): SignatureGenerator = object : SignatureGenerator { + 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() + } + } +} 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..c6285498 --- /dev/null +++ b/cryptography-providers/cryptokit/src/commonMain/kotlin/algorithms/CryptoKitXDH.kt @@ -0,0 +1,129 @@ +/* + * 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(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(MontgomeryOids.X25519, 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(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(MontgomeryOids.X25519, 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(MontgomeryOids.X25519), + key.rawRepresentation().toByteArray() + ) + XDH.PublicKey.Format.PEM -> wrapPem( + PemLabel.PublicKey, + wrapSubjectPublicKeyInfo( + UnknownKeyAlgorithmIdentifier(MontgomeryOids.X25519), + 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(MontgomeryOids.X25519), + key.rawRepresentation().toByteArray() + ) + XDH.PrivateKey.Format.PEM -> wrapPem( + PemLabel.PrivateKey, + wrapPrivateKeyInfo( + 0, + UnknownKeyAlgorithmIdentifier(MontgomeryOids.X25519), + 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() + } + } +} 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/JdkCryptographyProvider.kt b/cryptography-providers/jdk/src/jvmMain/kotlin/JdkCryptographyProvider.kt index 5d978df3..8adc7eec 100644 --- a/cryptography-providers/jdk/src/jvmMain/kotlin/JdkCryptographyProvider.kt +++ b/cryptography-providers/jdk/src/jvmMain/kotlin/JdkCryptographyProvider.kt @@ -131,7 +131,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 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..c7873e97 --- /dev/null +++ b/cryptography-providers/jdk/src/jvmMain/kotlin/algorithms/JdkEdDSA.kt @@ -0,0 +1,109 @@ +package dev.whyoleg.cryptography.providers.jdk.algorithms + +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.* +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 -> EdwardsOids.Ed25519 + EdDSA.Curve.Ed448 -> EdwardsOids.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 -> decodeFromDer( + wrapSubjectPublicKeyInfo(UnknownKeyAlgorithmIdentifier(oid(curve)), bytes) + ) + 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 -> decodeFromDer( + wrapPrivateKeyInfo( + 0, + UnknownKeyAlgorithmIdentifier(oid(curve)), + bytes + ) + ) + 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, + override val privateKey: EdDSA.PrivateKey, + ) : EdDSA.KeyPair + + private class EdDsaPublicKey( + private val state: JdkCryptographyState, + private val key: JPublicKey, + ) : EdDSA.PublicKey, JdkEncodableKey(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 -> { + val der = encodeToDer() + KeyInfoUnwrap.unwrapSpkiForOids(der, listOf(EdwardsOids.Ed25519, EdwardsOids.Ed448)) + } + 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, 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 -> { + val der = encodeToDer() + 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 new file mode 100644 index 00000000..b5eb6fa6 --- /dev/null +++ b/cryptography-providers/jdk/src/jvmMain/kotlin/algorithms/JdkXDH.kt @@ -0,0 +1,117 @@ +package dev.whyoleg.cryptography.providers.jdk.algorithms + +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.* +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 -> MontgomeryOids.X25519 + XDH.Curve.X448 -> MontgomeryOids.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 -> decodeFromDer( + wrapSubjectPublicKeyInfo(UnknownKeyAlgorithmIdentifier(oid(curve)), bytes) + ) + 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 -> decodeFromDer( + wrapPrivateKeyInfo( + 0, + UnknownKeyAlgorithmIdentifier(oid(curve)), + bytes + ) + ) + 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, + override val privateKey: XDH.PrivateKey, + ) : XDH.KeyPair + + private class XdhPublicKey( + private val state: JdkCryptographyState, + val key: JPublicKey, + ) : 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 -> { + val der = encodeToDer() + KeyInfoUnwrap.unwrapSpkiForOids(der, listOf(MontgomeryOids.X25519, MontgomeryOids.X448)) + } + 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, 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 -> { + val der = encodeToDer() + 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/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/Openssl3Ecdh.kt b/cryptography-providers/openssl3/api/src/commonMain/kotlin/algorithms/Openssl3Ecdh.kt index e86c41f6..55c13841 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,3 @@ 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) - } -} 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..4728a04b --- /dev/null +++ b/cryptography-providers/openssl3/api/src/commonMain/kotlin/algorithms/Openssl3EdDSA.kt @@ -0,0 +1,143 @@ +/* + * 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.* +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) { + EdDSA.Curve.Ed25519 -> "ED25519" + EdDSA.Curve.Ed448 -> "ED448" + } + 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 = + 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("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(oid(curve)), bytes) + ) + else -> super.decodeFromByteArrayBlocking(format, bytes) + } + + override fun wrapKey(key: CPointer): EdDSA.PublicKey = EdDsaPublicKey(key, curve) + } + + 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("should not be called: handled explicitly in decodeFromBlocking") + } + + 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(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, curve), + privateKey = EdDsaPrivateKey(keyPair, curve) + ) + } + + private class EdDsaKeyPair( + override val publicKey: EdDSA.PublicKey, + override val privateKey: EdDSA.PrivateKey, + ) : EdDSA.KeyPair + + 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("should not be called: handled explicitly in encodeToBlocking") + } + + override fun encodeToByteArrayBlocking(format: EdDSA.PublicKey.Format): ByteArray = when (format) { + EdDSA.PublicKey.Format.RAW -> unwrapSubjectPublicKeyInfo( + oid(curve), + super.encodeToByteArrayBlocking(EdDSA.PublicKey.Format.DER) + ) + else -> super.encodeToByteArrayBlocking(format) + } + + override fun signatureVerifier(): SignatureVerifier = EdDsaSignatureVerifier(key) + } + + 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("should not be called: handled explicitly in encodeToBlocking") + } + + override fun encodeToByteArrayBlocking(format: EdDSA.PrivateKey.Format): ByteArray = when (format) { + EdDSA.PrivateKey.Format.RAW -> unwrapPrivateKeyInfo( + oid(curve), + super.encodeToByteArrayBlocking(EdDSA.PrivateKey.Format.DER) + ) + else -> super.encodeToByteArrayBlocking(format) + } + + override fun signatureGenerator(): SignatureGenerator = EdDsaSignatureGenerator(key) + } +} + + +private class EdDsaSignatureGenerator( + private val privateKey: CPointer, +) : Openssl3DigestSignatureGenerator(privateKey, hashAlgorithm = null) { + override fun MemScope.createParams(): CValuesRef? = null +} + +private class EdDsaSignatureVerifier( + private val publicKey: CPointer, +) : Openssl3DigestSignatureVerifier(publicKey, hashAlgorithm = null) { + override fun MemScope.createParams(): CValuesRef? = null +} 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..d591c3e0 --- /dev/null +++ b/cryptography-providers/openssl3/api/src/commonMain/kotlin/algorithms/Openssl3XDH.kt @@ -0,0 +1,131 @@ +/* + * 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.* +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) { + XDH.Curve.X25519 -> "X25519" + XDH.Curve.X448 -> "X448" + } + 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 = + 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("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(oid(curve)), bytes) + ) + else -> super.decodeFromByteArrayBlocking(format, bytes) + } + + override fun wrapKey(key: CPointer): XDH.PublicKey = XdhPublicKey(key, curve) + } + + 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("should not be called: handled explicitly in decodeFromBlocking") + } + + 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(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, curve), + privateKey = XdhPrivateKey(keyPair, curve) + ) + } + + private class XdhKeyPair( + override val publicKey: XDH.PublicKey, + override val privateKey: XDH.PrivateKey, + ) : XDH.KeyPair + + 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("should not be called: handled explicitly in encodeToBlocking") + } + + override fun encodeToByteArrayBlocking(format: XDH.PublicKey.Format): ByteArray = when (format) { + XDH.PublicKey.Format.RAW -> unwrapSubjectPublicKeyInfo(oid(curve), super.encodeToByteArrayBlocking(XDH.PublicKey.Format.DER)) + else -> super.encodeToByteArrayBlocking(format) + } + + 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, + 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("should not be called: handled explicitly in encodeToBlocking") + } + + override fun encodeToByteArrayBlocking(format: XDH.PrivateKey.Format): ByteArray = when (format) { + XDH.PrivateKey.Format.RAW -> unwrapPrivateKeyInfo(oid(curve), super.encodeToByteArrayBlocking(XDH.PrivateKey.Format.DER)) + else -> super.encodeToByteArrayBlocking(format) + } + + override fun sharedSecretGenerator(): SharedSecretGenerator = this + override fun generateSharedSecretToByteArrayBlocking(other: XDH.PublicKey): ByteArray { + check(other is XdhPublicKey) + return deriveSharedSecret(publicKey = other.key, privateKey = key) + } + } +} 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..5ded6a9a --- /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.base.* +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/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..9c3a9efe 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,68 @@ 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 + } + + 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 new file mode 100644 index 00000000..a11e05f5 --- /dev/null +++ b/cryptography-providers/tests/src/commonMain/kotlin/compatibility/EdDsaCompatibilityTest.kt @@ -0,0 +1,151 @@ +/* + * 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.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, +).associateBy { it.name } + +abstract class EdDsaCompatibilityTest( + provider: CryptographyProvider, +) : CompatibilityTest(EdDSA, provider) { + + @Serializable + private data class KeyParameters(val curveName: String) : TestParameters { + 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) { + 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 { + 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() { + // 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.JWK -> {} + 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.JWK -> {} + 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..582a9572 --- /dev/null +++ b/cryptography-providers/tests/src/commonMain/kotlin/compatibility/XdhCompatibilityTest.kt @@ -0,0 +1,142 @@ +/* + * 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.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, +).associateBy { it.name } + +abstract class XdhCompatibilityTest( + provider: CryptographyProvider, +) : CompatibilityTest(XDH, provider) { + + @Serializable + private data class KeyParameters(val curveName: String) : TestParameters { + 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) { + 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 + // 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/EdDsaTest.kt b/cryptography-providers/tests/src/commonMain/kotlin/default/EdDsaTest.kt new file mode 100644 index 00000000..f88eb618 --- /dev/null +++ b/cryptography-providers/tests/src/commonMain/kotlin/default/EdDsaTest.kt @@ -0,0 +1,55 @@ +/* + * 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 { + 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( + 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 + + 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) + 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..e364dc2d 100644 --- a/cryptography-providers/tests/src/commonMain/kotlin/default/SupportedAlgorithmsTest.kt +++ b/cryptography-providers/tests/src/commonMain/kotlin/default/SupportedAlgorithmsTest.kt @@ -51,6 +51,10 @@ abstract class SupportedAlgorithmsTest(provider: CryptographyProvider) : Provide assertSupports(ECDSA) assertSupports(ECDH, !context.provider.isApple) + // Edwards-family + assertSupports(EdDSA, !context.provider.isApple) + assertSupports(XDH, !context.provider.isApple) + 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..84c9e5e1 --- /dev/null +++ b/cryptography-providers/tests/src/commonMain/kotlin/default/XdhTest.kt @@ -0,0 +1,34 @@ +/* + * 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 { + 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() + + 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 f4e61b58..479443a5 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 @@ -96,9 +101,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 } @@ -141,6 +151,15 @@ 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" + // 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" else -> null } } diff --git a/cryptography-providers/webcrypto/src/commonMain/kotlin/WebCryptoCryptographyProvider.kt b/cryptography-providers/webcrypto/src/commonMain/kotlin/WebCryptoCryptographyProvider.kt index d92b5908..c43f7340 100644 --- a/cryptography-providers/webcrypto/src/commonMain/kotlin/WebCryptoCryptographyProvider.kt +++ b/cryptography-providers/webcrypto/src/commonMain/kotlin/WebCryptoCryptographyProvider.kt @@ -29,7 +29,9 @@ internal object WebCryptoCryptographyProvider : CryptographyProvider() { RSA.PSS -> WebCryptoRsaPss RSA.PKCS1 -> WebCryptoRsaPkcs1 ECDSA -> WebCryptoEcdsa + EdDSA -> WebCryptoEdDSA ECDH -> WebCryptoEcdh + XDH -> WebCryptoXDH PBKDF2 -> WebCryptoPbkdf2 HKDF -> WebCryptoHkdf else -> null 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 "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(publicKey.algorithm, publicKey) + } + } + + private class EdDsaPrivateKey( + val privateKey: CryptoKey, + ) : WebCryptoEncodableKey(privateKey, EdPrivateKeyProcessor), EdDSA.PrivateKey { + override fun signatureGenerator(): SignatureGenerator { + return WebCryptoSignatureGenerator(privateKey.algorithm, 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 -> "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 + 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..a6b3e768 --- /dev/null +++ b/cryptography-providers/webcrypto/src/commonMain/kotlin/algorithms/WebCryptoXDH.kt @@ -0,0 +1,135 @@ +/* + * 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 -> "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) { + 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..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,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 +53,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/jsMain/kotlin/internal/Algorithms.js.kt b/cryptography-providers/webcrypto/src/jsMain/kotlin/internal/Algorithms.js.kt index 413d12d5..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,8 +48,8 @@ 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() internal actual fun Pbkdf2DeriveAlgorithm(hash: String, iterations: Int, salt: ByteArray): Algorithm = js("{ name: 'PBKDF2', hash: hash, iterations: iterations, salt: salt }").unsafeCast() @@ -77,3 +77,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/wasmJsMain/kotlin/internal/Algorithms.wasmJs.kt b/cryptography-providers/webcrypto/src/wasmJsMain/kotlin/internal/Algorithms.wasmJs.kt index d1c8c65e..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,8 +61,8 @@ 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 })") internal actual fun Pbkdf2DeriveAlgorithm(hash: String, iterations: Int, salt: ByteArray): Algorithm = jsPbkdf2DeriveAlgorithm(hash, iterations, salt.toInt8Array()) @@ -105,3 +105,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-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 +} 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/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") +} + diff --git a/docs/providers/cryptokit.md b/docs/providers/cryptokit.md index 61ba9d2d..a8c2757e 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: CryptoKit supports only Ed25519 and X25519. DER/PEM and RAW key encodings are available via the provider APIs. ## 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/openssl3.md b/docs/providers/openssl3.md index 192cb97d..db54e406 100644 --- a/docs/providers/openssl3.md +++ b/docs/providers/openssl3.md @@ -21,6 +21,7 @@ For supported targets and algorithms, please consult [Supported primitives secti * KeyFormat: doesn't support `JWK` key format yet + ## Example ```kotlin diff --git a/docs/providers/webcrypto.md b/docs/providers/webcrypto.md index 7fc4a11b..8c61de31 100644 --- a/docs/providers/webcrypto.md +++ b/docs/providers/webcrypto.md @@ -9,10 +9,12 @@ 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 (see [RFC 8032] and [RFC 7748]). If unsupported, operations will fail at runtime with an error from the underlying engine. ## Example ```kotlin +// default provider val provider = CryptographyProvider.WebCrypto // or CryptographyProvider.Default // get some algorithm @@ -32,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 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) } } }