diff --git a/kmp-advcrypto/src/commonMain/kotlin/io/karma/advcrypto/algorithm/delegates/KeyGeneratorDelegate.kt b/kmp-advcrypto/src/commonMain/kotlin/io/karma/advcrypto/algorithm/delegates/KeyGeneratorDelegate.kt index 05ef375..87212d5 100644 --- a/kmp-advcrypto/src/commonMain/kotlin/io/karma/advcrypto/algorithm/delegates/KeyGeneratorDelegate.kt +++ b/kmp-advcrypto/src/commonMain/kotlin/io/karma/advcrypto/algorithm/delegates/KeyGeneratorDelegate.kt @@ -48,6 +48,7 @@ class KeyGeneratorDelegate( private set private var keyPairGenerator: ((context: KeyGenContext) -> KeyPair)? = null private var keyGenerator: ((context: KeyGenContext) -> Key)? = null + private var close: ((context: KeyGenContext) -> Unit)? = null /** * This method creates an instance of a public-private keypair generator if a key pair generator @@ -75,6 +76,12 @@ class KeyGeneratorDelegate( override fun generateKeyPair(): KeyPair { return keyPairGenerator!!.invoke(context!!) } + + override fun close() { + if (close != null) { + close() + } + } } } @@ -104,7 +111,12 @@ class KeyGeneratorDelegate( override fun generateKey(): Key { return keyGenerator!!.invoke(context!!) } - override fun close() {} + + override fun close() { + if (close != null) { + close() + } + } } } @@ -179,4 +191,15 @@ class KeyGeneratorDelegate( } this.keyGenerator = closure } + + /** + * This method sets a delegate to the close function of the key/keypair generato. + * + * @author Cedric Hammes + * @since 11/06/2024 + */ + @Suppress("MemberVisibilityCanBePrivate") + fun close(closure: (context: KeyGenContext) -> Unit) { + this.close = closure + } } diff --git a/kmp-advcrypto/src/commonMain/kotlin/io/karma/advcrypto/wrapper/KeyPairGenerator.kt b/kmp-advcrypto/src/commonMain/kotlin/io/karma/advcrypto/wrapper/KeyPairGenerator.kt index 0e75428..ad62573 100644 --- a/kmp-advcrypto/src/commonMain/kotlin/io/karma/advcrypto/wrapper/KeyPairGenerator.kt +++ b/kmp-advcrypto/src/commonMain/kotlin/io/karma/advcrypto/wrapper/KeyPairGenerator.kt @@ -28,7 +28,8 @@ import io.karma.advcrypto.algorithm.delegates.KeyGeneratorDelegate * @author Cedric Hammes * @since 11/06/2024 */ -interface KeyPairGenerator { +@OptIn(ExperimentalStdlibApi::class) +interface KeyPairGenerator: AutoCloseable { /** * This method initializes the keypair generator with the specified specification. This diff --git a/kmp-advcrypto/src/linuxX64Main/kotlin/io/karma/advcrypto/linux/keys/OpenSSLKey.kt b/kmp-advcrypto/src/linuxX64Main/kotlin/io/karma/advcrypto/linux/keys/OpenSSLKey.kt index 2cdd1f7..a658fbc 100644 --- a/kmp-advcrypto/src/linuxX64Main/kotlin/io/karma/advcrypto/linux/keys/OpenSSLKey.kt +++ b/kmp-advcrypto/src/linuxX64Main/kotlin/io/karma/advcrypto/linux/keys/OpenSSLKey.kt @@ -18,6 +18,7 @@ package io.karma.advcrypto.linux.keys import io.karma.advcrypto.keys.Key import io.karma.advcrypto.linux.utils.SecureHeap +import kotlinx.cinterop.CPointer import kotlinx.cinterop.ExperimentalForeignApi import kotlinx.cinterop.UByteVar import kotlinx.cinterop.reinterpret @@ -27,22 +28,27 @@ import libssl.ERR_get_error import libssl.RAND_bytes @OptIn(ExperimentalForeignApi::class) -class OpenSSLKey(private val secureHeap: SecureHeap, val keySize: Int, - override val purposes: UByte, override val algorithm: String): Key { - private val rawDataPtr = secureHeap.allocate((keySize / 8).toULong()).reinterpret() +class OpenSSLKey(private val secureHeap: SecureHeap, + override val purposes: UByte, + override val algorithm: String, + private val rawDataPtr: CPointer, + private val rawDataSize: ULong +): Key { override fun close() { - secureHeap.free((keySize / 8).toULong(), rawDataPtr) + secureHeap.free(rawDataSize, rawDataPtr) } companion object { - fun generateRandom(secureHeap: SecureHeap, keySize: Int, purposes: UByte, - algorithm: String): OpenSSLKey = - OpenSSLKey(secureHeap, keySize, purposes, algorithm).apply { - if (RAND_bytes(rawDataPtr, 1) != 1) { - throw Exception(ERR_func_error_string(ERR_get_error())?.toKString()) - } + fun generateRandom(secureHeap: SecureHeap, keySize: Int, purposes: UByte, algorithm: String): OpenSSLKey { + val dataSize = (keySize / 8).toULong() + val rawDataPtr = secureHeap.allocate((keySize / 8).toULong()).reinterpret() + if (RAND_bytes(rawDataPtr, 1) != 1) { + throw Exception(ERR_func_error_string(ERR_get_error())?.toKString()) } + + return OpenSSLKey(secureHeap, purposes, algorithm, rawDataPtr, dataSize) + } } diff --git a/kmp-advcrypto/src/linuxX64Main/kotlin/io/karma/advcrypto/linux/providers/OpenSSLCryptoProvider.kt b/kmp-advcrypto/src/linuxX64Main/kotlin/io/karma/advcrypto/linux/providers/OpenSSLCryptoProvider.kt index 67feb45..d1bf757 100644 --- a/kmp-advcrypto/src/linuxX64Main/kotlin/io/karma/advcrypto/linux/providers/OpenSSLCryptoProvider.kt +++ b/kmp-advcrypto/src/linuxX64Main/kotlin/io/karma/advcrypto/linux/providers/OpenSSLCryptoProvider.kt @@ -20,8 +20,27 @@ import io.karma.advcrypto.AbstractProvider import io.karma.advcrypto.Providers import io.karma.advcrypto.algorithm.delegates.KeyGenContext import io.karma.advcrypto.keys.Key +import io.karma.advcrypto.keys.KeyPair import io.karma.advcrypto.linux.keys.OpenSSLKey import io.karma.advcrypto.linux.utils.SecureHeap +import kotlinx.cinterop.ExperimentalForeignApi +import kotlinx.cinterop.UByteVar +import kotlinx.cinterop.reinterpret +import libssl.BIO_ctrl_pending +import libssl.BIO_free +import libssl.BIO_new +import libssl.BIO_read +import libssl.BIO_s_mem +import libssl.BIO_s_secmem +import libssl.BN_free +import libssl.BN_new +import libssl.BN_set_word +import libssl.PEM_write_bio_RSAPrivateKey +import libssl.PEM_write_bio_RSAPublicKey +import libssl.RSA_F4 +import libssl.RSA_free +import libssl.RSA_generate_key_ex +import libssl.RSA_new class OpenSSLCryptoProvider: AbstractProvider( "Default", @@ -30,6 +49,7 @@ class OpenSSLCryptoProvider: AbstractProvider( ) { private val secureHeap = SecureHeap(UShort.MAX_VALUE.toULong() + 1u, 0u) + @OptIn(ExperimentalForeignApi::class) override fun initialize(providers: Providers) { algorithm(providers, "AES") { keyGenerator(Key.PURPOSES_SYMMETRIC, arrayOf(128, 196, 256), 256) { @@ -44,6 +64,85 @@ class OpenSSLCryptoProvider: AbstractProvider( } } } + + algorithm(providers, "RSA") { + keyGenerator(Key.PURPOSES_ALL, arrayOf(1024, 2048, 4096), 4096) { + initializer { + val bne = BN_new() + if (BN_set_word(bne, RSA_F4.toULong()) != 1) { + BN_free(bne) + throw RuntimeException("Initialization of RSA key generator failed") + } + bne!! + } + + generateKeyPair { context -> + val keySize = context.generatorSpec.keySize?: defaultKeySize + + // Generate keys + val rsa = RSA_new() + if (RSA_generate_key_ex(rsa, keySize, context.internalContext, null) != 1) { + RSA_free(rsa) + throw RuntimeException("RSA key generation failed") + } + + // Write public and private key into memory + val privKeyBio = BIO_new(BIO_s_secmem()) + if (PEM_write_bio_RSAPrivateKey(privKeyBio, rsa, null, null, 0, null, null) != 1) { + BIO_free(privKeyBio) + RSA_free(rsa) + throw RuntimeException("Writing private key into memory failed") + } + + val pubKeyBio = BIO_new(BIO_s_secmem()) + if (PEM_write_bio_RSAPublicKey(pubKeyBio, rsa) != 1) { + BIO_free(privKeyBio) + BIO_free(pubKeyBio) + RSA_free(rsa) + throw RuntimeException("Writing public key into memory failed") + } + + // Read public key into other secure memory + val pubKeyBufferSize = BIO_ctrl_pending(pubKeyBio) + val pubKeyBuffer = secureHeap.allocate(pubKeyBufferSize) + val pubLen = BIO_read(pubKeyBio, pubKeyBuffer, pubKeyBufferSize.toInt()) + if (pubLen < 0) { + secureHeap.free(pubKeyBufferSize, pubKeyBuffer) + BIO_free(privKeyBio) + BIO_free(pubKeyBio) + RSA_free(rsa) + } + + // Read public key into other secure memory + val privKeyBufferSize = BIO_ctrl_pending(privKeyBio) + val privKeyBuffer = secureHeap.allocate(privKeyBufferSize) + val privLen = BIO_read(privKeyBio, privKeyBuffer, privKeyBufferSize.toInt()) + if (privLen < 0) { + secureHeap.free(pubKeyBufferSize, pubKeyBuffer) + secureHeap.free(privKeyBufferSize, privKeyBuffer) + BIO_free(privKeyBio) + BIO_free(pubKeyBio) + RSA_free(rsa) + } + + // Free BIO data + BIO_free(privKeyBio) + BIO_free(pubKeyBio) + + // Return key pair + KeyPair( + OpenSSLKey(secureHeap, 0u, "RSA", pubKeyBuffer.reinterpret(), + pubKeyBufferSize), + OpenSSLKey(secureHeap, 0u, "RSA", privKeyBuffer.reinterpret(), + privKeyBufferSize) + ) + } + + close { context -> + BN_free(context.internalContext) + } + } + } } override fun close() { diff --git a/kmp-advcrypto/src/linuxX64Test/kotlin/io.karma.advcrypto.linux.tests/KeyGeneratorTests.kt b/kmp-advcrypto/src/linuxX64Test/kotlin/io.karma.advcrypto.linux.tests/KeyGeneratorTests.kt index be50f0b..b0c4fd1 100644 --- a/kmp-advcrypto/src/linuxX64Test/kotlin/io.karma.advcrypto.linux.tests/KeyGeneratorTests.kt +++ b/kmp-advcrypto/src/linuxX64Test/kotlin/io.karma.advcrypto.linux.tests/KeyGeneratorTests.kt @@ -4,6 +4,7 @@ import io.karma.advcrypto.Providers import io.karma.advcrypto.algorithm.specs.KeyGeneratorSpec import io.karma.advcrypto.keys.Key import io.karma.advcrypto.wrapper.KeyGenerator +import io.karma.advcrypto.wrapper.KeyPairGenerator import kotlin.test.Test class KeyGeneratorTests { @@ -16,4 +17,12 @@ class KeyGeneratorTests { providers.close() } + @Test + fun testRSA() { + val providers = Providers() + val spec = KeyGeneratorSpec.Builder(Key.PURPOSES_ALL).setKeySize(4096).build() + KeyPairGenerator.getInstance(providers, "RSA").initialize(spec).generateKeyPair().close() + providers.close() + } + } \ No newline at end of file diff --git a/kmp-advcrypto/src/nativeInterop/cinterop/libssl.def b/kmp-advcrypto/src/nativeInterop/cinterop/libssl.def index a30b3ef..8484996 100644 --- a/kmp-advcrypto/src/nativeInterop/cinterop/libssl.def +++ b/kmp-advcrypto/src/nativeInterop/cinterop/libssl.def @@ -1,3 +1,3 @@ -headers = openssl/crypto.h openssl/err.h openssl/aes.h openssl/rand.h openssl/evp.h +headers = openssl/crypto.h openssl/err.h openssl/aes.h openssl/rsa.h openssl/rand.h openssl/evp.h openssl/pem.h openssl/bio.h linkerOpts = -L/usr/lib/x86_64-linux-gnu -L/usr/lib -ldl -lpthread -lc -lm -lssl -lcrypto compilerOpts = -I/usr/include -I/usr/include/x86_64-linux-gnu \ No newline at end of file