diff --git a/cryptography-core/api/cryptography-core.api b/cryptography-core/api/cryptography-core.api index 327b419d..ae4712bf 100644 --- a/cryptography-core/api/cryptography-core.api +++ b/cryptography-core/api/cryptography-core.api @@ -253,9 +253,13 @@ public final class dev/whyoleg/cryptography/algorithms/EC$Curve { } public final class dev/whyoleg/cryptography/algorithms/EC$Curve$Companion { + public final fun getBrainpoolP256r1-pVITJAk ()Ljava/lang/String; + public final fun getBrainpoolP384r1-pVITJAk ()Ljava/lang/String; + public final fun getBrainpoolP512r1-pVITJAk ()Ljava/lang/String; public final fun getP256-pVITJAk ()Ljava/lang/String; public final fun getP384-pVITJAk ()Ljava/lang/String; public final fun getP521-pVITJAk ()Ljava/lang/String; + public final fun getSecp256k1-pVITJAk ()Ljava/lang/String; } public abstract interface class dev/whyoleg/cryptography/algorithms/EC$KeyPair : dev/whyoleg/cryptography/materials/key/Key { diff --git a/cryptography-core/api/cryptography-core.klib.api b/cryptography-core/api/cryptography-core.klib.api index 4d43f5b9..1dda659d 100644 --- a/cryptography-core/api/cryptography-core.klib.api +++ b/cryptography-core/api/cryptography-core.klib.api @@ -265,6 +265,14 @@ abstract interface <#A: dev.whyoleg.cryptography.algorithms/EC.PublicKey, #B: de final fun (): dev.whyoleg.cryptography.algorithms/EC.Curve // dev.whyoleg.cryptography.algorithms/EC.Curve.Companion.P384.|(){}[0] final val P521 // dev.whyoleg.cryptography.algorithms/EC.Curve.Companion.P521|{}P521[0] final fun (): dev.whyoleg.cryptography.algorithms/EC.Curve // dev.whyoleg.cryptography.algorithms/EC.Curve.Companion.P521.|(){}[0] + final val brainpoolP256r1 // dev.whyoleg.cryptography.algorithms/EC.Curve.Companion.brainpoolP256r1|{}brainpoolP256r1[0] + final fun (): dev.whyoleg.cryptography.algorithms/EC.Curve // dev.whyoleg.cryptography.algorithms/EC.Curve.Companion.brainpoolP256r1.|(){}[0] + final val brainpoolP384r1 // dev.whyoleg.cryptography.algorithms/EC.Curve.Companion.brainpoolP384r1|{}brainpoolP384r1[0] + final fun (): dev.whyoleg.cryptography.algorithms/EC.Curve // dev.whyoleg.cryptography.algorithms/EC.Curve.Companion.brainpoolP384r1.|(){}[0] + final val brainpoolP512r1 // dev.whyoleg.cryptography.algorithms/EC.Curve.Companion.brainpoolP512r1|{}brainpoolP512r1[0] + final fun (): dev.whyoleg.cryptography.algorithms/EC.Curve // dev.whyoleg.cryptography.algorithms/EC.Curve.Companion.brainpoolP512r1.|(){}[0] + final val secp256k1 // dev.whyoleg.cryptography.algorithms/EC.Curve.Companion.secp256k1|{}secp256k1[0] + final fun (): dev.whyoleg.cryptography.algorithms/EC.Curve // dev.whyoleg.cryptography.algorithms/EC.Curve.Companion.secp256k1.|(){}[0] } } } diff --git a/cryptography-core/src/commonMain/kotlin/algorithms/EC.kt b/cryptography-core/src/commonMain/kotlin/algorithms/EC.kt index aa914f25..c669316b 100644 --- a/cryptography-core/src/commonMain/kotlin/algorithms/EC.kt +++ b/cryptography-core/src/commonMain/kotlin/algorithms/EC.kt @@ -20,6 +20,13 @@ public interface EC.supportsEncryption(): Boolean = supports { fun AlgorithmTestScope>.supportsCurve(curve: EC.Curve): Boolean = supports { when { - // JDK default, WebCrypto and Apple doesn't support secp256k1 - curve.name == "secp256k1" && ( + // JDK default/WebCrypto/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 ) -> "ECDSA ${curve.name}" + else -> null } } diff --git a/cryptography-providers-tests/src/commonMain/kotlin/compatibility/EcCompatibilityTest.kt b/cryptography-providers-tests/src/commonMain/kotlin/compatibility/EcCompatibilityTest.kt index f57206d0..d54dbf6d 100644 --- a/cryptography-providers-tests/src/commonMain/kotlin/compatibility/EcCompatibilityTest.kt +++ b/cryptography-providers-tests/src/commonMain/kotlin/compatibility/EcCompatibilityTest.kt @@ -41,7 +41,11 @@ abstract class EcCompatibilityTest Unit) { - generate(block, EC.Curve.P256, EC.Curve.P384, EC.Curve.P521, EC.Curve("secp256k1")) + generate(block, + EC.Curve.P256, EC.Curve.P384, EC.Curve.P521, + EC.Curve.secp256k1, + EC.Curve.brainpoolP256r1, EC.Curve.brainpoolP384r1, EC.Curve.brainpoolP512r1, + ) } protected suspend fun CompatibilityTestScope.generateKeys( diff --git a/cryptography-providers-tests/src/commonMain/kotlin/default/EcdsaTest.kt b/cryptography-providers-tests/src/commonMain/kotlin/default/EcdsaTest.kt index e8551f2c..4008cc63 100644 --- a/cryptography-providers-tests/src/commonMain/kotlin/default/EcdsaTest.kt +++ b/cryptography-providers-tests/src/commonMain/kotlin/default/EcdsaTest.kt @@ -24,7 +24,7 @@ abstract class EcdsaTest(provider: CryptographyProvider) : AlgorithmTest( data class EcdsaSize( val curve: EC.Curve, val rawSignatureSize: Int, - val derSignatureSizes: List, + val derSignatureSizes: IntRange, val publicKeySize: Int, val privateKeySizes: List, ) @@ -32,32 +32,86 @@ abstract class EcdsaTest(provider: CryptographyProvider) : AlgorithmTest( @Test fun testSizes() = testWithAlgorithm { listOf( - EcdsaSize(EC.Curve.P256, 64, listOf(68, 69, 70, 71, 72), 91, listOf(67, 138, 150)), - EcdsaSize(EC.Curve.P384, 96, listOf(100, 101, 102, 103, 104), 120, listOf(80, 185, 194)), - EcdsaSize(EC.Curve.P521, 132, listOf(136, 137, 138, 139), 158, listOf(98, 241, 250)), - EcdsaSize(EC.Curve("secp256k1"), 64, listOf(68, 69, 70, 71, 72), 88, listOf(135, 144)), + // NIST curves + EcdsaSize(EC.Curve.P256, 64, 68.rangeTo(72), 91, listOf(67, 138, 150)), + EcdsaSize(EC.Curve.P384, 96, 100.rangeTo(104), 120, listOf(80, 185, 194)), + EcdsaSize(EC.Curve.P521, 132, 136.rangeTo(139), 158, listOf(98, 241, 250)), + + // Note "private key sizes": smaller = openssl, larger = BouncyCastle + + // SECP256k1 + EcdsaSize(EC.Curve.secp256k1, 64, 68.rangeTo(72), 88, listOf(135, 144)), + + // Brainpool curves + EcdsaSize(EC.Curve.brainpoolP256r1, 64, 68.rangeTo(72), 92, listOf(139, 152)), + EcdsaSize(EC.Curve.brainpoolP384r1, 96, 100.rangeTo(104), 124, listOf(189, 202)), + EcdsaSize( + EC.Curve.brainpoolP512r1, + 128, + 132.rangeTo(139), + 158, + listOf(239, 252) + ) // Raw 128, DER sig slightly larger; PubKey ~154; PrivKey ~P521 + + ).forEach { (curve, rawSignatureSize, derSignatureSizes, publicKeySize, privateKeySizes) -> - if (!supportsCurve(curve)) return@forEach + if (!supportsCurve(curve)) { + println("Skipping size test for unsupported curve: ${curve.name}") + return@forEach + } + println("\nRunning size test for curve: ${curve.name}") val keyPair = algorithm.keyPairGenerator(curve).generateKey() - assertEquals(publicKeySize, keyPair.publicKey.encodeToByteString(EC.PublicKey.Format.DER).size) - assertContains(privateKeySizes, keyPair.privateKey.encodeToByteString(EC.PrivateKey.Format.DER).size) + val actualPublicKeySize = keyPair.publicKey.encodeToByteString(EC.PublicKey.Format.DER).size + println("Got ${curve.name} public key size: $actualPublicKeySize (expected $publicKeySize)") + assertEquals( + publicKeySize, + actualPublicKeySize, + "Public key size mismatch for ${curve.name}, expected: $publicKeySize, but got $actualPublicKeySize" + ) + val actualPrivateKeySize = keyPair.privateKey.encodeToByteString(EC.PrivateKey.Format.DER).size + println("Got ${curve.name} private key size: $actualPrivateKeySize (allowed $privateKeySizes)") + assertContains( + privateKeySizes, + actualPrivateKeySize, + "Private key size mismatch for ${curve.name}, expected one of $privateKeySizes, but got $actualPrivateKeySize" + ) generateDigests { digest, _ -> - if (!supportsDigest(digest)) return@generateDigests + if (!supportsDigest(digest)) { + println("Skipping digest $digest for curve ${curve.name}") + return@generateDigests + } // RAW signature run { val verifier = keyPair.publicKey.signatureVerifier(digest, ECDSA.SignatureFormat.RAW) keyPair.privateKey.signatureGenerator(digest, ECDSA.SignatureFormat.RAW).run { - assertEquals(rawSignatureSize, generateSignature(ByteArray(0)).size) + val sigEmpty = generateSignature(ByteArray(0)) + assertEquals( + rawSignatureSize, + sigEmpty.size, + "RAW signature size mismatch for empty data on ${curve.name} / ${digest.name}" + ) + assertTrue( + verifier.tryVerifySignature(ByteArray(0), sigEmpty), + "RAW signature verification failed for empty data on ${curve.name} / ${digest.name}" + ) + repeat(8) { n -> val size = 10.0.pow(n).toInt() val data = CryptographyRandom.nextBytes(size) val signature = generateSignature(data) - assertEquals(rawSignatureSize, signature.size) - assertTrue(verifier.tryVerifySignature(data, signature)) + assertEquals( + rawSignatureSize, + signature.size, + "RAW signature size mismatch for data size $size on ${curve.name} / ${digest.name}" + ) + assertTrue( + verifier.tryVerifySignature(data, signature), + "RAW signature verification failed for data size $size on ${curve.name} / ${digest.name}" + ) } } } @@ -68,7 +122,12 @@ abstract class EcdsaTest(provider: CryptographyProvider) : AlgorithmTest( fun assertSignatureSize(signature: ByteArray) { if (signature.size in derSignatureSizes) return // enhance a message with Base64 encoded signature - assertContains(derSignatureSizes, signature.size, "DER: ${Base64.encode(signature)}") + + assertContains( + derSignatureSizes, signature.size, "DER signature size mismatch on ${curve.name} / ${digest.name}. " + + "Expected one of $derSignatureSizes, got ${signature.size}. " + + "Signature (Base64): ${Base64.encode(signature)}" + ) } assertSignatureSize(generateSignature(ByteArray(0))) @@ -77,7 +136,10 @@ abstract class EcdsaTest(provider: CryptographyProvider) : AlgorithmTest( val data = CryptographyRandom.nextBytes(size) val signature = generateSignature(data) assertSignatureSize(signature) - assertTrue(verifier.tryVerifySignature(data, signature)) + assertTrue( + verifier.tryVerifySignature(data, signature), + "DER signature verification failed for data size $size on ${curve.name} / ${digest.name}" + ) } } } @@ -87,27 +149,41 @@ abstract class EcdsaTest(provider: CryptographyProvider) : AlgorithmTest( @Test fun testFunctions() = testWithAlgorithm { - if (!supportsFunctions()) return@testWithAlgorithm + if (!supportsFunctions()) { + println("Skipping function test because functions are not supported by provider") + return@testWithAlgorithm + } listOf( EC.Curve.P256, EC.Curve.P384, EC.Curve.P521, - EC.Curve("secp256k1"), + EC.Curve.secp256k1, + EC.Curve.brainpoolP256r1, + EC.Curve.brainpoolP384r1, + EC.Curve.brainpoolP512r1, ).forEach { curve -> - if (!supportsCurve(curve)) return@forEach + if (!supportsCurve(curve)) { + println("Skipping function test for unsupported curve: ${curve.name}") + return@forEach + } + println("Running function test for curve: ${curve.name}") val keyPair = algorithm.keyPairGenerator(curve).generateKey() generateDigests { digest, _ -> - if (!supportsDigest(digest)) return@generateDigests + if (!supportsDigest(digest)) { + println("Skipping digest $digest for curve ${curve.name}") + return@generateDigests + } ECDSA.SignatureFormat.entries.forEach { format -> + println("Testing format $format for ${curve.name} / ${digest.name}") val signatureGenerator = keyPair.privateKey.signatureGenerator(digest, format) val signatureVerifier = keyPair.publicKey.signatureVerifier(digest, format) repeat(10) { - val size = CryptographyRandom.nextInt(20000) + val size = CryptographyRandom.nextInt(1024, 20000) // Ensure non-trivial size val data = ByteString(CryptographyRandom.nextBytes(size)) assertSignaturesViaFunction(signatureGenerator, signatureVerifier, data) } diff --git a/cryptography-providers/jdk/README.md b/cryptography-providers/jdk/README.md index 797667d4..1c875061 100644 --- a/cryptography-providers/jdk/README.md +++ b/cryptography-providers/jdk/README.md @@ -11,7 +11,7 @@ For supported targets and algorithms, please consult [Supported primitives secti ## Custom Java providers Some specific algorithms (SHA3 family of digests on JDK 8) or parameters (`secp256k1` curve for ECDSA) could be not supported by default JDK -provider, but it doesn't mean, that you can not use them with `cryptography-kotlin`. +provider, but it doesn't mean, that you cannot use them with `cryptography-kotlin`. There is a possibility to create [CryptographyProvider][CryptographyProvider] from [java.util.Provider](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/security/Provider.html), f.e. using [BouncyCastle](https://www.bouncycastle.org):