From 5ee298c453916f372847e622a8606c2d4835890e Mon Sep 17 00:00:00 2001 From: Davide Pianca Date: Fri, 23 Feb 2024 14:05:40 +0100 Subject: [PATCH] Add support for certificates and keys in PEM string format in the MQTTClient TLSSettings --- Readme.md | 4 +- .../commonTest/kotlin/integration/TLSTest.kt | 4 +- .../src/commonMain/kotlin/MQTTClient.kt | 1 + .../src/commonMain/kotlin/TLSClientSocket.kt | 1 + .../iosArm64Main/kotlin/TLSClientEngine.kt | 76 ++++++++++++++++-- .../kotlin/TLSClientEngine.kt | 76 ++++++++++++++++-- .../src/iosX64Main/kotlin/TLSClientEngine.kt | 76 ++++++++++++++++-- .../src/jsMain/kotlin/TLSClientSocket.kt | 30 ++++++- .../src/jvmMain/kotlin/TLSClientSocket.kt | 11 +-- .../linuxArm64Main/kotlin/TLSClientEngine.kt | 78 ++++++++++++++++--- .../linuxX64Main/kotlin/TLSClientEngine.kt | 78 ++++++++++++++++--- .../macosArm64Main/kotlin/TLSClientEngine.kt | 76 ++++++++++++++++-- .../macosX64Main/kotlin/TLSClientEngine.kt | 76 ++++++++++++++++-- .../mingwX64Main/kotlin/TLSClientEngine.kt | 78 ++++++++++++++++--- .../src/posixMain/kotlin/TLSClientEngine.kt | 1 + .../src/posixMain/kotlin/TLSClientSocket.kt | 1 + .../tvosArm64Main/kotlin/TLSClientEngine.kt | 76 ++++++++++++++++-- .../kotlin/TLSClientEngine.kt | 76 ++++++++++++++++-- .../src/tvosX64Main/kotlin/TLSClientEngine.kt | 76 ++++++++++++++++-- .../kotlin/TLSClientEngine.kt | 76 ++++++++++++++++-- .../kotlin/TLSClientEngine.kt | 76 ++++++++++++++++-- .../kotlin/TLSClientEngine.kt | 76 ++++++++++++++++-- .../watchosX64Main/kotlin/TLSClientEngine.kt | 76 ++++++++++++++++-- .../src/commonMain/kotlin/CommonUtils.kt | 4 + .../kotlin/socket/tls/TLSClientSettings.kt | 20 +++-- 25 files changed, 1081 insertions(+), 142 deletions(-) diff --git a/Readme.md b/Readme.md index 8017252..b37a222 100644 --- a/Readme.md +++ b/Readme.md @@ -109,7 +109,7 @@ fun main() { ``` #### TLS code example -The certificates and key must be in PEM format, the password can be null +The certificates and key must be in PEM format, and they can be either a path to the PEM file or the actual PEM string. The password can be null if the private key has no protection. ```kotlin fun main() { val client = MQTTClient( @@ -117,7 +117,7 @@ fun main() { "test.mosquitto.org", 8883, TLSClientSettings( - serverCertificatePath = "mosquitto.org.crt", + serverCertificate = "mosquitto.org.crt", ) ) { println(it.payload?.toByteArray()?.decodeToString()) diff --git a/kmqtt-broker/src/commonTest/kotlin/integration/TLSTest.kt b/kmqtt-broker/src/commonTest/kotlin/integration/TLSTest.kt index 72307e4..a5de9dd 100644 --- a/kmqtt-broker/src/commonTest/kotlin/integration/TLSTest.kt +++ b/kmqtt-broker/src/commonTest/kotlin/integration/TLSTest.kt @@ -1,7 +1,6 @@ package integration import MQTTClient -import TLSClientSettings import com.goncalossilva.resources.Resource import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay @@ -13,6 +12,7 @@ import mqtt.broker.Broker import mqtt.packets.Qos import mqtt.packets.mqttv5.ReasonCode import mqtt.packets.mqttv5.SubscriptionOptions +import socket.tls.TLSClientSettings import socket.tls.TLSSettings import kotlin.test.Test import kotlin.test.assertContentEquals @@ -27,7 +27,7 @@ class TLSTest { var received = false val broker = Broker(port = 8883, tlsSettings = TLSSettings(keyStoreFilePath = pathPrefix + "keyStore.p12", keyStorePassword = "changeit")) - val client = MQTTClient(MQTTVersion.MQTT5, "127.0.0.1", broker.port, TLSClientSettings(serverCertificatePath = pathPrefix + "cert.pem")) { + val client = MQTTClient(MQTTVersion.MQTT5, "127.0.0.1", broker.port, TLSClientSettings(serverCertificate = pathPrefix + "cert.pem")) { assertEquals(topic, it.topicName) assertContentEquals(sendPayload.encodeToByteArray().toUByteArray(), it.payload) assertEquals(Qos.AT_MOST_ONCE, it.qos) diff --git a/kmqtt-client/src/commonMain/kotlin/MQTTClient.kt b/kmqtt-client/src/commonMain/kotlin/MQTTClient.kt index 8fc3e21..f9ba703 100644 --- a/kmqtt-client/src/commonMain/kotlin/MQTTClient.kt +++ b/kmqtt-client/src/commonMain/kotlin/MQTTClient.kt @@ -14,6 +14,7 @@ import socket.IOException import socket.SocketClosedException import socket.SocketInterface import socket.streams.EOFException +import socket.tls.TLSClientSettings /** * MQTT 3.1.1 and 5 client diff --git a/kmqtt-client/src/commonMain/kotlin/TLSClientSocket.kt b/kmqtt-client/src/commonMain/kotlin/TLSClientSocket.kt index 7d317b6..931750e 100644 --- a/kmqtt-client/src/commonMain/kotlin/TLSClientSocket.kt +++ b/kmqtt-client/src/commonMain/kotlin/TLSClientSocket.kt @@ -1,3 +1,4 @@ +import socket.tls.TLSClientSettings import socket.tls.TLSSocket public expect class TLSClientSocket( diff --git a/kmqtt-client/src/iosArm64Main/kotlin/TLSClientEngine.kt b/kmqtt-client/src/iosArm64Main/kotlin/TLSClientEngine.kt index a8cb07d..5e5f441 100644 --- a/kmqtt-client/src/iosArm64Main/kotlin/TLSClientEngine.kt +++ b/kmqtt-client/src/iosArm64Main/kotlin/TLSClientEngine.kt @@ -4,6 +4,7 @@ import platform.posix.getenv import platform.posix.strcpy import platform.posix.strlen import socket.IOException +import socket.tls.TLSClientSettings import socket.tls.TLSEngine internal actual class TLSClientEngine actual constructor(tlsSettings: TLSClientSettings) : TLSEngine { @@ -12,6 +13,42 @@ internal actual class TLSClientEngine actual constructor(tlsSettings: TLSClientS private val readBio: CPointer private val writeBio: CPointer + fun loadServerCertsFromString(context: CPointer, certBuffer: String): Boolean { + val bio = BIO_new(BIO_s_mem()) + BIO_puts(bio, certBuffer) + + val inf = PEM_X509_INFO_read_bio(bio, null, null, null) + + if (inf == null) { + BIO_free(bio) + return false + } + + val ctx = SSL_CTX_get_cert_store(context); + + var loaded = 0 + for (i in 0..? -> + X509_INFO_free(buf) + }) + BIO_free(bio) + return false + } + loaded++ + } + } + + sk_X509_INFO_pop_free(inf, staticCFunction { buf: CPointer? -> + X509_INFO_free(buf) + }) + BIO_free(bio) + + return loaded > 0 + } + init { val readBio = BIO_new(BIO_s_mem()) ?: throw IOException("Failed allocating read BIO") @@ -24,9 +61,12 @@ internal actual class TLSClientEngine actual constructor(tlsSettings: TLSClientS val method = TLS_client_method() val sslContext = SSL_CTX_new(method)!! - if (tlsSettings.serverCertificatePath != null) { - if (SSL_CTX_load_verify_locations(sslContext, tlsSettings.serverCertificatePath, null) != 1) { - throw Exception("Server certificate path not found") + if (tlsSettings.serverCertificate != null) { + if (!loadServerCertsFromString(sslContext, tlsSettings.serverCertificate!!)) { + // Try file + if (SSL_CTX_load_verify_locations(sslContext, tlsSettings.serverCertificate, null) != 1) { + throw Exception("Server certificate path not found") + } } } else { if (SSL_CTX_load_verify_locations(sslContext, null, getenv(X509_get_default_cert_dir_env()?.toKString())?.toKString()) != 1) { @@ -34,12 +74,32 @@ internal actual class TLSClientEngine actual constructor(tlsSettings: TLSClientS } } - if (tlsSettings.clientCertificatePath != null) { - if (SSL_CTX_use_certificate_file(sslContext, tlsSettings.clientCertificatePath, SSL_FILETYPE_PEM) != 1) { - throw Exception("Cannot load client's certificate file") + if (tlsSettings.clientCertificate != null) { + val bio = BIO_new(BIO_s_mem()) + BIO_puts(bio, tlsSettings.clientCertificate) + val certificate = PEM_read_bio_X509(bio, null, null, null) + + if (certificate != null) { + if (SSL_CTX_use_certificate(sslContext, certificate) != 1) { + throw Exception("Cannot load client's certificate") + } + } else { + // Load file + if (SSL_CTX_use_certificate_file(sslContext, tlsSettings.clientCertificate, SSL_FILETYPE_PEM) != 1) { + throw Exception("Cannot load client's certificate file") + } } - if (SSL_CTX_use_PrivateKey_file(sslContext, tlsSettings.clientCertificateKeyPath!!, SSL_FILETYPE_PEM) != 1) { - throw Exception("Cannot load client's key file") + + BIO_puts(bio, tlsSettings.clientCertificateKey) + val key = PEM_read_bio_PrivateKey(bio, null, null, null) + if (key != null) { + if (SSL_CTX_use_PrivateKey(sslContext, key) != 1) { + throw Exception("Cannot load client's key") + } + } else { + if (SSL_CTX_use_PrivateKey_file(sslContext, tlsSettings.clientCertificateKey!!, SSL_FILETYPE_PEM) != 1) { + throw Exception("Cannot load client's key file") + } } if (SSL_CTX_check_private_key(sslContext) != 1) { throw Exception("Client's certificate and key don't match") diff --git a/kmqtt-client/src/iosSimulatorArm64Main/kotlin/TLSClientEngine.kt b/kmqtt-client/src/iosSimulatorArm64Main/kotlin/TLSClientEngine.kt index a8cb07d..5e5f441 100644 --- a/kmqtt-client/src/iosSimulatorArm64Main/kotlin/TLSClientEngine.kt +++ b/kmqtt-client/src/iosSimulatorArm64Main/kotlin/TLSClientEngine.kt @@ -4,6 +4,7 @@ import platform.posix.getenv import platform.posix.strcpy import platform.posix.strlen import socket.IOException +import socket.tls.TLSClientSettings import socket.tls.TLSEngine internal actual class TLSClientEngine actual constructor(tlsSettings: TLSClientSettings) : TLSEngine { @@ -12,6 +13,42 @@ internal actual class TLSClientEngine actual constructor(tlsSettings: TLSClientS private val readBio: CPointer private val writeBio: CPointer + fun loadServerCertsFromString(context: CPointer, certBuffer: String): Boolean { + val bio = BIO_new(BIO_s_mem()) + BIO_puts(bio, certBuffer) + + val inf = PEM_X509_INFO_read_bio(bio, null, null, null) + + if (inf == null) { + BIO_free(bio) + return false + } + + val ctx = SSL_CTX_get_cert_store(context); + + var loaded = 0 + for (i in 0..? -> + X509_INFO_free(buf) + }) + BIO_free(bio) + return false + } + loaded++ + } + } + + sk_X509_INFO_pop_free(inf, staticCFunction { buf: CPointer? -> + X509_INFO_free(buf) + }) + BIO_free(bio) + + return loaded > 0 + } + init { val readBio = BIO_new(BIO_s_mem()) ?: throw IOException("Failed allocating read BIO") @@ -24,9 +61,12 @@ internal actual class TLSClientEngine actual constructor(tlsSettings: TLSClientS val method = TLS_client_method() val sslContext = SSL_CTX_new(method)!! - if (tlsSettings.serverCertificatePath != null) { - if (SSL_CTX_load_verify_locations(sslContext, tlsSettings.serverCertificatePath, null) != 1) { - throw Exception("Server certificate path not found") + if (tlsSettings.serverCertificate != null) { + if (!loadServerCertsFromString(sslContext, tlsSettings.serverCertificate!!)) { + // Try file + if (SSL_CTX_load_verify_locations(sslContext, tlsSettings.serverCertificate, null) != 1) { + throw Exception("Server certificate path not found") + } } } else { if (SSL_CTX_load_verify_locations(sslContext, null, getenv(X509_get_default_cert_dir_env()?.toKString())?.toKString()) != 1) { @@ -34,12 +74,32 @@ internal actual class TLSClientEngine actual constructor(tlsSettings: TLSClientS } } - if (tlsSettings.clientCertificatePath != null) { - if (SSL_CTX_use_certificate_file(sslContext, tlsSettings.clientCertificatePath, SSL_FILETYPE_PEM) != 1) { - throw Exception("Cannot load client's certificate file") + if (tlsSettings.clientCertificate != null) { + val bio = BIO_new(BIO_s_mem()) + BIO_puts(bio, tlsSettings.clientCertificate) + val certificate = PEM_read_bio_X509(bio, null, null, null) + + if (certificate != null) { + if (SSL_CTX_use_certificate(sslContext, certificate) != 1) { + throw Exception("Cannot load client's certificate") + } + } else { + // Load file + if (SSL_CTX_use_certificate_file(sslContext, tlsSettings.clientCertificate, SSL_FILETYPE_PEM) != 1) { + throw Exception("Cannot load client's certificate file") + } } - if (SSL_CTX_use_PrivateKey_file(sslContext, tlsSettings.clientCertificateKeyPath!!, SSL_FILETYPE_PEM) != 1) { - throw Exception("Cannot load client's key file") + + BIO_puts(bio, tlsSettings.clientCertificateKey) + val key = PEM_read_bio_PrivateKey(bio, null, null, null) + if (key != null) { + if (SSL_CTX_use_PrivateKey(sslContext, key) != 1) { + throw Exception("Cannot load client's key") + } + } else { + if (SSL_CTX_use_PrivateKey_file(sslContext, tlsSettings.clientCertificateKey!!, SSL_FILETYPE_PEM) != 1) { + throw Exception("Cannot load client's key file") + } } if (SSL_CTX_check_private_key(sslContext) != 1) { throw Exception("Client's certificate and key don't match") diff --git a/kmqtt-client/src/iosX64Main/kotlin/TLSClientEngine.kt b/kmqtt-client/src/iosX64Main/kotlin/TLSClientEngine.kt index a8cb07d..5e5f441 100644 --- a/kmqtt-client/src/iosX64Main/kotlin/TLSClientEngine.kt +++ b/kmqtt-client/src/iosX64Main/kotlin/TLSClientEngine.kt @@ -4,6 +4,7 @@ import platform.posix.getenv import platform.posix.strcpy import platform.posix.strlen import socket.IOException +import socket.tls.TLSClientSettings import socket.tls.TLSEngine internal actual class TLSClientEngine actual constructor(tlsSettings: TLSClientSettings) : TLSEngine { @@ -12,6 +13,42 @@ internal actual class TLSClientEngine actual constructor(tlsSettings: TLSClientS private val readBio: CPointer private val writeBio: CPointer + fun loadServerCertsFromString(context: CPointer, certBuffer: String): Boolean { + val bio = BIO_new(BIO_s_mem()) + BIO_puts(bio, certBuffer) + + val inf = PEM_X509_INFO_read_bio(bio, null, null, null) + + if (inf == null) { + BIO_free(bio) + return false + } + + val ctx = SSL_CTX_get_cert_store(context); + + var loaded = 0 + for (i in 0..? -> + X509_INFO_free(buf) + }) + BIO_free(bio) + return false + } + loaded++ + } + } + + sk_X509_INFO_pop_free(inf, staticCFunction { buf: CPointer? -> + X509_INFO_free(buf) + }) + BIO_free(bio) + + return loaded > 0 + } + init { val readBio = BIO_new(BIO_s_mem()) ?: throw IOException("Failed allocating read BIO") @@ -24,9 +61,12 @@ internal actual class TLSClientEngine actual constructor(tlsSettings: TLSClientS val method = TLS_client_method() val sslContext = SSL_CTX_new(method)!! - if (tlsSettings.serverCertificatePath != null) { - if (SSL_CTX_load_verify_locations(sslContext, tlsSettings.serverCertificatePath, null) != 1) { - throw Exception("Server certificate path not found") + if (tlsSettings.serverCertificate != null) { + if (!loadServerCertsFromString(sslContext, tlsSettings.serverCertificate!!)) { + // Try file + if (SSL_CTX_load_verify_locations(sslContext, tlsSettings.serverCertificate, null) != 1) { + throw Exception("Server certificate path not found") + } } } else { if (SSL_CTX_load_verify_locations(sslContext, null, getenv(X509_get_default_cert_dir_env()?.toKString())?.toKString()) != 1) { @@ -34,12 +74,32 @@ internal actual class TLSClientEngine actual constructor(tlsSettings: TLSClientS } } - if (tlsSettings.clientCertificatePath != null) { - if (SSL_CTX_use_certificate_file(sslContext, tlsSettings.clientCertificatePath, SSL_FILETYPE_PEM) != 1) { - throw Exception("Cannot load client's certificate file") + if (tlsSettings.clientCertificate != null) { + val bio = BIO_new(BIO_s_mem()) + BIO_puts(bio, tlsSettings.clientCertificate) + val certificate = PEM_read_bio_X509(bio, null, null, null) + + if (certificate != null) { + if (SSL_CTX_use_certificate(sslContext, certificate) != 1) { + throw Exception("Cannot load client's certificate") + } + } else { + // Load file + if (SSL_CTX_use_certificate_file(sslContext, tlsSettings.clientCertificate, SSL_FILETYPE_PEM) != 1) { + throw Exception("Cannot load client's certificate file") + } } - if (SSL_CTX_use_PrivateKey_file(sslContext, tlsSettings.clientCertificateKeyPath!!, SSL_FILETYPE_PEM) != 1) { - throw Exception("Cannot load client's key file") + + BIO_puts(bio, tlsSettings.clientCertificateKey) + val key = PEM_read_bio_PrivateKey(bio, null, null, null) + if (key != null) { + if (SSL_CTX_use_PrivateKey(sslContext, key) != 1) { + throw Exception("Cannot load client's key") + } + } else { + if (SSL_CTX_use_PrivateKey_file(sslContext, tlsSettings.clientCertificateKey!!, SSL_FILETYPE_PEM) != 1) { + throw Exception("Cannot load client's key file") + } } if (SSL_CTX_check_private_key(sslContext) != 1) { throw Exception("Client's certificate and key don't match") diff --git a/kmqtt-client/src/jsMain/kotlin/TLSClientSocket.kt b/kmqtt-client/src/jsMain/kotlin/TLSClientSocket.kt index cc53589..98e6502 100644 --- a/kmqtt-client/src/jsMain/kotlin/TLSClientSocket.kt +++ b/kmqtt-client/src/jsMain/kotlin/TLSClientSocket.kt @@ -1,5 +1,7 @@ import node.fs.ReadFileSyncBufferOptions +import socket.IOException import socket.tls.ConnectionOptions +import socket.tls.TLSClientSettings import socket.tls.TLSSocket import socket.tls.connect import web.timers.setTimeout @@ -15,9 +17,31 @@ public actual class TLSClientSocket actual constructor( private val checkCallback: () -> Unit ) : TLSSocket(connect(port, address, TlsConnectionOptions().apply { fun ReadFileOptions(): ReadFileSyncBufferOptions = js("{}") as ReadFileSyncBufferOptions - ca = tlsSettings.serverCertificatePath?.run { node.fs.readFileSync(this, ReadFileOptions()) } - cert = tlsSettings.clientCertificatePath?.run { node.fs.readFileSync(this, ReadFileOptions()) } - key = tlsSettings.clientCertificateKeyPath?.run { node.fs.readFileSync(this, ReadFileOptions()) } + if (tlsSettings.serverCertificate != null) { + ca = if (tlsSettings.serverCertificate!!.isValidPem()) { + tlsSettings.serverCertificate?.encodeToByteArray()?.toUByteArray()?.toBuffer() + } else { + // Try to load file + tlsSettings.serverCertificate?.run { node.fs.readFileSync(this, ReadFileOptions()) } ?: throw IOException("Couldn't load server certificate") + } + } + if (tlsSettings.clientCertificate != null) { + cert = if (tlsSettings.clientCertificate!!.isValidPem()) { + tlsSettings.clientCertificate?.encodeToByteArray()?.toUByteArray()?.toBuffer() + } else { + // Try to load file + tlsSettings.clientCertificate?.run { node.fs.readFileSync(this, ReadFileOptions()) } ?: throw IOException("Couldn't load client certificate") + } + } + if (tlsSettings.clientCertificateKey != null) { + key = if (tlsSettings.clientCertificateKey!!.isValidPem()) { + tlsSettings.clientCertificateKey?.encodeToByteArray()?.toUByteArray()?.toBuffer() + } else { + // Try to load file + tlsSettings.clientCertificateKey?.run { node.fs.readFileSync(this, ReadFileOptions()) } ?: throw IOException("Couldn't load client certificate key") + } + } + passphrase = tlsSettings.clientCertificatePassword servername = address }), { _, _ -> diff --git a/kmqtt-client/src/jvmMain/kotlin/TLSClientSocket.kt b/kmqtt-client/src/jvmMain/kotlin/TLSClientSocket.kt index 04ae212..78d4bd3 100644 --- a/kmqtt-client/src/jvmMain/kotlin/TLSClientSocket.kt +++ b/kmqtt-client/src/jvmMain/kotlin/TLSClientSocket.kt @@ -1,3 +1,4 @@ +import socket.tls.TLSClientSettings import socket.tls.TLSSocket import java.io.FileInputStream import java.net.InetSocketAddress @@ -34,10 +35,10 @@ public actual class TLSClientSocket actual constructor( ByteBuffer.allocate(maximumPacketSize), ByteBuffer.allocate(maximumPacketSize), SSLContext.getInstance(tlsSettings.version).apply { - val trustManagers = if (tlsSettings.serverCertificatePath != null) { + val trustManagers = if (tlsSettings.serverCertificate != null) { val certificate = CertificateFactory .getInstance("X.509") - .generateCertificate(FileInputStream(tlsSettings.serverCertificatePath!!)) + .generateCertificate(if (tlsSettings.serverCertificate!!.isValidPem()) tlsSettings.serverCertificate?.byteInputStream() else FileInputStream(tlsSettings.serverCertificate!!)) val keyStore = KeyStore.getInstance(KeyStore.getDefaultType()) keyStore.load(null, null) @@ -51,14 +52,14 @@ public actual class TLSClientSocket actual constructor( null } - val keyManagers = if (tlsSettings.clientCertificatePath != null) { + val keyManagers = if (tlsSettings.clientCertificate != null) { val certificate = CertificateFactory .getInstance("X.509") - .generateCertificate(FileInputStream(tlsSettings.clientCertificatePath!!)) + .generateCertificate(if (tlsSettings.clientCertificate!!.isValidPem()) tlsSettings.clientCertificate?.byteInputStream() else FileInputStream(tlsSettings.clientCertificate!!)) val keyStore = KeyStore.getInstance(KeyStore.getDefaultType()) keyStore.load(null, null) - val key = getPrivateKeyFromString(FileInputStream(tlsSettings.clientCertificateKeyPath!!).bufferedReader().readText()) + val key = getPrivateKeyFromString(if (tlsSettings.clientCertificateKey!!.isValidPem()) tlsSettings.clientCertificateKey!! else FileInputStream(tlsSettings.clientCertificateKey!!).bufferedReader().readText()) keyStore.setKeyEntry("client", key, tlsSettings.clientCertificatePassword?.toCharArray(), arrayOf(certificate)) val kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()) diff --git a/kmqtt-client/src/linuxArm64Main/kotlin/TLSClientEngine.kt b/kmqtt-client/src/linuxArm64Main/kotlin/TLSClientEngine.kt index af5535b..5e5f441 100644 --- a/kmqtt-client/src/linuxArm64Main/kotlin/TLSClientEngine.kt +++ b/kmqtt-client/src/linuxArm64Main/kotlin/TLSClientEngine.kt @@ -4,6 +4,7 @@ import platform.posix.getenv import platform.posix.strcpy import platform.posix.strlen import socket.IOException +import socket.tls.TLSClientSettings import socket.tls.TLSEngine internal actual class TLSClientEngine actual constructor(tlsSettings: TLSClientSettings) : TLSEngine { @@ -12,6 +13,42 @@ internal actual class TLSClientEngine actual constructor(tlsSettings: TLSClientS private val readBio: CPointer private val writeBio: CPointer + fun loadServerCertsFromString(context: CPointer, certBuffer: String): Boolean { + val bio = BIO_new(BIO_s_mem()) + BIO_puts(bio, certBuffer) + + val inf = PEM_X509_INFO_read_bio(bio, null, null, null) + + if (inf == null) { + BIO_free(bio) + return false + } + + val ctx = SSL_CTX_get_cert_store(context); + + var loaded = 0 + for (i in 0..? -> + X509_INFO_free(buf) + }) + BIO_free(bio) + return false + } + loaded++ + } + } + + sk_X509_INFO_pop_free(inf, staticCFunction { buf: CPointer? -> + X509_INFO_free(buf) + }) + BIO_free(bio) + + return loaded > 0 + } + init { val readBio = BIO_new(BIO_s_mem()) ?: throw IOException("Failed allocating read BIO") @@ -24,9 +61,12 @@ internal actual class TLSClientEngine actual constructor(tlsSettings: TLSClientS val method = TLS_client_method() val sslContext = SSL_CTX_new(method)!! - if (tlsSettings.serverCertificatePath != null) { - if (SSL_CTX_load_verify_locations(sslContext, tlsSettings.serverCertificatePath, null) != 1) { - throw Exception("Server certificate path not found") + if (tlsSettings.serverCertificate != null) { + if (!loadServerCertsFromString(sslContext, tlsSettings.serverCertificate!!)) { + // Try file + if (SSL_CTX_load_verify_locations(sslContext, tlsSettings.serverCertificate, null) != 1) { + throw Exception("Server certificate path not found") + } } } else { if (SSL_CTX_load_verify_locations(sslContext, null, getenv(X509_get_default_cert_dir_env()?.toKString())?.toKString()) != 1) { @@ -34,12 +74,32 @@ internal actual class TLSClientEngine actual constructor(tlsSettings: TLSClientS } } - if (tlsSettings.clientCertificatePath != null) { - if (SSL_CTX_use_certificate_file(sslContext, tlsSettings.clientCertificatePath, SSL_FILETYPE_PEM) != 1) { - throw Exception("Cannot load client's certificate file") + if (tlsSettings.clientCertificate != null) { + val bio = BIO_new(BIO_s_mem()) + BIO_puts(bio, tlsSettings.clientCertificate) + val certificate = PEM_read_bio_X509(bio, null, null, null) + + if (certificate != null) { + if (SSL_CTX_use_certificate(sslContext, certificate) != 1) { + throw Exception("Cannot load client's certificate") + } + } else { + // Load file + if (SSL_CTX_use_certificate_file(sslContext, tlsSettings.clientCertificate, SSL_FILETYPE_PEM) != 1) { + throw Exception("Cannot load client's certificate file") + } } - if (SSL_CTX_use_PrivateKey_file(sslContext, tlsSettings.clientCertificateKeyPath!!, SSL_FILETYPE_PEM) != 1) { - throw Exception("Cannot load client's key file") + + BIO_puts(bio, tlsSettings.clientCertificateKey) + val key = PEM_read_bio_PrivateKey(bio, null, null, null) + if (key != null) { + if (SSL_CTX_use_PrivateKey(sslContext, key) != 1) { + throw Exception("Cannot load client's key") + } + } else { + if (SSL_CTX_use_PrivateKey_file(sslContext, tlsSettings.clientCertificateKey!!, SSL_FILETYPE_PEM) != 1) { + throw Exception("Cannot load client's key file") + } } if (SSL_CTX_check_private_key(sslContext) != 1) { throw Exception("Client's certificate and key don't match") @@ -110,4 +170,4 @@ internal actual class TLSClientEngine actual constructor(tlsSettings: TLSClientS override fun close() { SSL_free(context) } -} \ No newline at end of file +} diff --git a/kmqtt-client/src/linuxX64Main/kotlin/TLSClientEngine.kt b/kmqtt-client/src/linuxX64Main/kotlin/TLSClientEngine.kt index af5535b..5e5f441 100644 --- a/kmqtt-client/src/linuxX64Main/kotlin/TLSClientEngine.kt +++ b/kmqtt-client/src/linuxX64Main/kotlin/TLSClientEngine.kt @@ -4,6 +4,7 @@ import platform.posix.getenv import platform.posix.strcpy import platform.posix.strlen import socket.IOException +import socket.tls.TLSClientSettings import socket.tls.TLSEngine internal actual class TLSClientEngine actual constructor(tlsSettings: TLSClientSettings) : TLSEngine { @@ -12,6 +13,42 @@ internal actual class TLSClientEngine actual constructor(tlsSettings: TLSClientS private val readBio: CPointer private val writeBio: CPointer + fun loadServerCertsFromString(context: CPointer, certBuffer: String): Boolean { + val bio = BIO_new(BIO_s_mem()) + BIO_puts(bio, certBuffer) + + val inf = PEM_X509_INFO_read_bio(bio, null, null, null) + + if (inf == null) { + BIO_free(bio) + return false + } + + val ctx = SSL_CTX_get_cert_store(context); + + var loaded = 0 + for (i in 0..? -> + X509_INFO_free(buf) + }) + BIO_free(bio) + return false + } + loaded++ + } + } + + sk_X509_INFO_pop_free(inf, staticCFunction { buf: CPointer? -> + X509_INFO_free(buf) + }) + BIO_free(bio) + + return loaded > 0 + } + init { val readBio = BIO_new(BIO_s_mem()) ?: throw IOException("Failed allocating read BIO") @@ -24,9 +61,12 @@ internal actual class TLSClientEngine actual constructor(tlsSettings: TLSClientS val method = TLS_client_method() val sslContext = SSL_CTX_new(method)!! - if (tlsSettings.serverCertificatePath != null) { - if (SSL_CTX_load_verify_locations(sslContext, tlsSettings.serverCertificatePath, null) != 1) { - throw Exception("Server certificate path not found") + if (tlsSettings.serverCertificate != null) { + if (!loadServerCertsFromString(sslContext, tlsSettings.serverCertificate!!)) { + // Try file + if (SSL_CTX_load_verify_locations(sslContext, tlsSettings.serverCertificate, null) != 1) { + throw Exception("Server certificate path not found") + } } } else { if (SSL_CTX_load_verify_locations(sslContext, null, getenv(X509_get_default_cert_dir_env()?.toKString())?.toKString()) != 1) { @@ -34,12 +74,32 @@ internal actual class TLSClientEngine actual constructor(tlsSettings: TLSClientS } } - if (tlsSettings.clientCertificatePath != null) { - if (SSL_CTX_use_certificate_file(sslContext, tlsSettings.clientCertificatePath, SSL_FILETYPE_PEM) != 1) { - throw Exception("Cannot load client's certificate file") + if (tlsSettings.clientCertificate != null) { + val bio = BIO_new(BIO_s_mem()) + BIO_puts(bio, tlsSettings.clientCertificate) + val certificate = PEM_read_bio_X509(bio, null, null, null) + + if (certificate != null) { + if (SSL_CTX_use_certificate(sslContext, certificate) != 1) { + throw Exception("Cannot load client's certificate") + } + } else { + // Load file + if (SSL_CTX_use_certificate_file(sslContext, tlsSettings.clientCertificate, SSL_FILETYPE_PEM) != 1) { + throw Exception("Cannot load client's certificate file") + } } - if (SSL_CTX_use_PrivateKey_file(sslContext, tlsSettings.clientCertificateKeyPath!!, SSL_FILETYPE_PEM) != 1) { - throw Exception("Cannot load client's key file") + + BIO_puts(bio, tlsSettings.clientCertificateKey) + val key = PEM_read_bio_PrivateKey(bio, null, null, null) + if (key != null) { + if (SSL_CTX_use_PrivateKey(sslContext, key) != 1) { + throw Exception("Cannot load client's key") + } + } else { + if (SSL_CTX_use_PrivateKey_file(sslContext, tlsSettings.clientCertificateKey!!, SSL_FILETYPE_PEM) != 1) { + throw Exception("Cannot load client's key file") + } } if (SSL_CTX_check_private_key(sslContext) != 1) { throw Exception("Client's certificate and key don't match") @@ -110,4 +170,4 @@ internal actual class TLSClientEngine actual constructor(tlsSettings: TLSClientS override fun close() { SSL_free(context) } -} \ No newline at end of file +} diff --git a/kmqtt-client/src/macosArm64Main/kotlin/TLSClientEngine.kt b/kmqtt-client/src/macosArm64Main/kotlin/TLSClientEngine.kt index a8cb07d..5e5f441 100644 --- a/kmqtt-client/src/macosArm64Main/kotlin/TLSClientEngine.kt +++ b/kmqtt-client/src/macosArm64Main/kotlin/TLSClientEngine.kt @@ -4,6 +4,7 @@ import platform.posix.getenv import platform.posix.strcpy import platform.posix.strlen import socket.IOException +import socket.tls.TLSClientSettings import socket.tls.TLSEngine internal actual class TLSClientEngine actual constructor(tlsSettings: TLSClientSettings) : TLSEngine { @@ -12,6 +13,42 @@ internal actual class TLSClientEngine actual constructor(tlsSettings: TLSClientS private val readBio: CPointer private val writeBio: CPointer + fun loadServerCertsFromString(context: CPointer, certBuffer: String): Boolean { + val bio = BIO_new(BIO_s_mem()) + BIO_puts(bio, certBuffer) + + val inf = PEM_X509_INFO_read_bio(bio, null, null, null) + + if (inf == null) { + BIO_free(bio) + return false + } + + val ctx = SSL_CTX_get_cert_store(context); + + var loaded = 0 + for (i in 0..? -> + X509_INFO_free(buf) + }) + BIO_free(bio) + return false + } + loaded++ + } + } + + sk_X509_INFO_pop_free(inf, staticCFunction { buf: CPointer? -> + X509_INFO_free(buf) + }) + BIO_free(bio) + + return loaded > 0 + } + init { val readBio = BIO_new(BIO_s_mem()) ?: throw IOException("Failed allocating read BIO") @@ -24,9 +61,12 @@ internal actual class TLSClientEngine actual constructor(tlsSettings: TLSClientS val method = TLS_client_method() val sslContext = SSL_CTX_new(method)!! - if (tlsSettings.serverCertificatePath != null) { - if (SSL_CTX_load_verify_locations(sslContext, tlsSettings.serverCertificatePath, null) != 1) { - throw Exception("Server certificate path not found") + if (tlsSettings.serverCertificate != null) { + if (!loadServerCertsFromString(sslContext, tlsSettings.serverCertificate!!)) { + // Try file + if (SSL_CTX_load_verify_locations(sslContext, tlsSettings.serverCertificate, null) != 1) { + throw Exception("Server certificate path not found") + } } } else { if (SSL_CTX_load_verify_locations(sslContext, null, getenv(X509_get_default_cert_dir_env()?.toKString())?.toKString()) != 1) { @@ -34,12 +74,32 @@ internal actual class TLSClientEngine actual constructor(tlsSettings: TLSClientS } } - if (tlsSettings.clientCertificatePath != null) { - if (SSL_CTX_use_certificate_file(sslContext, tlsSettings.clientCertificatePath, SSL_FILETYPE_PEM) != 1) { - throw Exception("Cannot load client's certificate file") + if (tlsSettings.clientCertificate != null) { + val bio = BIO_new(BIO_s_mem()) + BIO_puts(bio, tlsSettings.clientCertificate) + val certificate = PEM_read_bio_X509(bio, null, null, null) + + if (certificate != null) { + if (SSL_CTX_use_certificate(sslContext, certificate) != 1) { + throw Exception("Cannot load client's certificate") + } + } else { + // Load file + if (SSL_CTX_use_certificate_file(sslContext, tlsSettings.clientCertificate, SSL_FILETYPE_PEM) != 1) { + throw Exception("Cannot load client's certificate file") + } } - if (SSL_CTX_use_PrivateKey_file(sslContext, tlsSettings.clientCertificateKeyPath!!, SSL_FILETYPE_PEM) != 1) { - throw Exception("Cannot load client's key file") + + BIO_puts(bio, tlsSettings.clientCertificateKey) + val key = PEM_read_bio_PrivateKey(bio, null, null, null) + if (key != null) { + if (SSL_CTX_use_PrivateKey(sslContext, key) != 1) { + throw Exception("Cannot load client's key") + } + } else { + if (SSL_CTX_use_PrivateKey_file(sslContext, tlsSettings.clientCertificateKey!!, SSL_FILETYPE_PEM) != 1) { + throw Exception("Cannot load client's key file") + } } if (SSL_CTX_check_private_key(sslContext) != 1) { throw Exception("Client's certificate and key don't match") diff --git a/kmqtt-client/src/macosX64Main/kotlin/TLSClientEngine.kt b/kmqtt-client/src/macosX64Main/kotlin/TLSClientEngine.kt index a8cb07d..5e5f441 100644 --- a/kmqtt-client/src/macosX64Main/kotlin/TLSClientEngine.kt +++ b/kmqtt-client/src/macosX64Main/kotlin/TLSClientEngine.kt @@ -4,6 +4,7 @@ import platform.posix.getenv import platform.posix.strcpy import platform.posix.strlen import socket.IOException +import socket.tls.TLSClientSettings import socket.tls.TLSEngine internal actual class TLSClientEngine actual constructor(tlsSettings: TLSClientSettings) : TLSEngine { @@ -12,6 +13,42 @@ internal actual class TLSClientEngine actual constructor(tlsSettings: TLSClientS private val readBio: CPointer private val writeBio: CPointer + fun loadServerCertsFromString(context: CPointer, certBuffer: String): Boolean { + val bio = BIO_new(BIO_s_mem()) + BIO_puts(bio, certBuffer) + + val inf = PEM_X509_INFO_read_bio(bio, null, null, null) + + if (inf == null) { + BIO_free(bio) + return false + } + + val ctx = SSL_CTX_get_cert_store(context); + + var loaded = 0 + for (i in 0..? -> + X509_INFO_free(buf) + }) + BIO_free(bio) + return false + } + loaded++ + } + } + + sk_X509_INFO_pop_free(inf, staticCFunction { buf: CPointer? -> + X509_INFO_free(buf) + }) + BIO_free(bio) + + return loaded > 0 + } + init { val readBio = BIO_new(BIO_s_mem()) ?: throw IOException("Failed allocating read BIO") @@ -24,9 +61,12 @@ internal actual class TLSClientEngine actual constructor(tlsSettings: TLSClientS val method = TLS_client_method() val sslContext = SSL_CTX_new(method)!! - if (tlsSettings.serverCertificatePath != null) { - if (SSL_CTX_load_verify_locations(sslContext, tlsSettings.serverCertificatePath, null) != 1) { - throw Exception("Server certificate path not found") + if (tlsSettings.serverCertificate != null) { + if (!loadServerCertsFromString(sslContext, tlsSettings.serverCertificate!!)) { + // Try file + if (SSL_CTX_load_verify_locations(sslContext, tlsSettings.serverCertificate, null) != 1) { + throw Exception("Server certificate path not found") + } } } else { if (SSL_CTX_load_verify_locations(sslContext, null, getenv(X509_get_default_cert_dir_env()?.toKString())?.toKString()) != 1) { @@ -34,12 +74,32 @@ internal actual class TLSClientEngine actual constructor(tlsSettings: TLSClientS } } - if (tlsSettings.clientCertificatePath != null) { - if (SSL_CTX_use_certificate_file(sslContext, tlsSettings.clientCertificatePath, SSL_FILETYPE_PEM) != 1) { - throw Exception("Cannot load client's certificate file") + if (tlsSettings.clientCertificate != null) { + val bio = BIO_new(BIO_s_mem()) + BIO_puts(bio, tlsSettings.clientCertificate) + val certificate = PEM_read_bio_X509(bio, null, null, null) + + if (certificate != null) { + if (SSL_CTX_use_certificate(sslContext, certificate) != 1) { + throw Exception("Cannot load client's certificate") + } + } else { + // Load file + if (SSL_CTX_use_certificate_file(sslContext, tlsSettings.clientCertificate, SSL_FILETYPE_PEM) != 1) { + throw Exception("Cannot load client's certificate file") + } } - if (SSL_CTX_use_PrivateKey_file(sslContext, tlsSettings.clientCertificateKeyPath!!, SSL_FILETYPE_PEM) != 1) { - throw Exception("Cannot load client's key file") + + BIO_puts(bio, tlsSettings.clientCertificateKey) + val key = PEM_read_bio_PrivateKey(bio, null, null, null) + if (key != null) { + if (SSL_CTX_use_PrivateKey(sslContext, key) != 1) { + throw Exception("Cannot load client's key") + } + } else { + if (SSL_CTX_use_PrivateKey_file(sslContext, tlsSettings.clientCertificateKey!!, SSL_FILETYPE_PEM) != 1) { + throw Exception("Cannot load client's key file") + } } if (SSL_CTX_check_private_key(sslContext) != 1) { throw Exception("Client's certificate and key don't match") diff --git a/kmqtt-client/src/mingwX64Main/kotlin/TLSClientEngine.kt b/kmqtt-client/src/mingwX64Main/kotlin/TLSClientEngine.kt index af5535b..5e5f441 100644 --- a/kmqtt-client/src/mingwX64Main/kotlin/TLSClientEngine.kt +++ b/kmqtt-client/src/mingwX64Main/kotlin/TLSClientEngine.kt @@ -4,6 +4,7 @@ import platform.posix.getenv import platform.posix.strcpy import platform.posix.strlen import socket.IOException +import socket.tls.TLSClientSettings import socket.tls.TLSEngine internal actual class TLSClientEngine actual constructor(tlsSettings: TLSClientSettings) : TLSEngine { @@ -12,6 +13,42 @@ internal actual class TLSClientEngine actual constructor(tlsSettings: TLSClientS private val readBio: CPointer private val writeBio: CPointer + fun loadServerCertsFromString(context: CPointer, certBuffer: String): Boolean { + val bio = BIO_new(BIO_s_mem()) + BIO_puts(bio, certBuffer) + + val inf = PEM_X509_INFO_read_bio(bio, null, null, null) + + if (inf == null) { + BIO_free(bio) + return false + } + + val ctx = SSL_CTX_get_cert_store(context); + + var loaded = 0 + for (i in 0..? -> + X509_INFO_free(buf) + }) + BIO_free(bio) + return false + } + loaded++ + } + } + + sk_X509_INFO_pop_free(inf, staticCFunction { buf: CPointer? -> + X509_INFO_free(buf) + }) + BIO_free(bio) + + return loaded > 0 + } + init { val readBio = BIO_new(BIO_s_mem()) ?: throw IOException("Failed allocating read BIO") @@ -24,9 +61,12 @@ internal actual class TLSClientEngine actual constructor(tlsSettings: TLSClientS val method = TLS_client_method() val sslContext = SSL_CTX_new(method)!! - if (tlsSettings.serverCertificatePath != null) { - if (SSL_CTX_load_verify_locations(sslContext, tlsSettings.serverCertificatePath, null) != 1) { - throw Exception("Server certificate path not found") + if (tlsSettings.serverCertificate != null) { + if (!loadServerCertsFromString(sslContext, tlsSettings.serverCertificate!!)) { + // Try file + if (SSL_CTX_load_verify_locations(sslContext, tlsSettings.serverCertificate, null) != 1) { + throw Exception("Server certificate path not found") + } } } else { if (SSL_CTX_load_verify_locations(sslContext, null, getenv(X509_get_default_cert_dir_env()?.toKString())?.toKString()) != 1) { @@ -34,12 +74,32 @@ internal actual class TLSClientEngine actual constructor(tlsSettings: TLSClientS } } - if (tlsSettings.clientCertificatePath != null) { - if (SSL_CTX_use_certificate_file(sslContext, tlsSettings.clientCertificatePath, SSL_FILETYPE_PEM) != 1) { - throw Exception("Cannot load client's certificate file") + if (tlsSettings.clientCertificate != null) { + val bio = BIO_new(BIO_s_mem()) + BIO_puts(bio, tlsSettings.clientCertificate) + val certificate = PEM_read_bio_X509(bio, null, null, null) + + if (certificate != null) { + if (SSL_CTX_use_certificate(sslContext, certificate) != 1) { + throw Exception("Cannot load client's certificate") + } + } else { + // Load file + if (SSL_CTX_use_certificate_file(sslContext, tlsSettings.clientCertificate, SSL_FILETYPE_PEM) != 1) { + throw Exception("Cannot load client's certificate file") + } } - if (SSL_CTX_use_PrivateKey_file(sslContext, tlsSettings.clientCertificateKeyPath!!, SSL_FILETYPE_PEM) != 1) { - throw Exception("Cannot load client's key file") + + BIO_puts(bio, tlsSettings.clientCertificateKey) + val key = PEM_read_bio_PrivateKey(bio, null, null, null) + if (key != null) { + if (SSL_CTX_use_PrivateKey(sslContext, key) != 1) { + throw Exception("Cannot load client's key") + } + } else { + if (SSL_CTX_use_PrivateKey_file(sslContext, tlsSettings.clientCertificateKey!!, SSL_FILETYPE_PEM) != 1) { + throw Exception("Cannot load client's key file") + } } if (SSL_CTX_check_private_key(sslContext) != 1) { throw Exception("Client's certificate and key don't match") @@ -110,4 +170,4 @@ internal actual class TLSClientEngine actual constructor(tlsSettings: TLSClientS override fun close() { SSL_free(context) } -} \ No newline at end of file +} diff --git a/kmqtt-client/src/posixMain/kotlin/TLSClientEngine.kt b/kmqtt-client/src/posixMain/kotlin/TLSClientEngine.kt index 8d4b9b1..12ee868 100644 --- a/kmqtt-client/src/posixMain/kotlin/TLSClientEngine.kt +++ b/kmqtt-client/src/posixMain/kotlin/TLSClientEngine.kt @@ -1,3 +1,4 @@ +import socket.tls.TLSClientSettings import socket.tls.TLSEngine internal expect class TLSClientEngine(tlsSettings: TLSClientSettings) : TLSEngine \ No newline at end of file diff --git a/kmqtt-client/src/posixMain/kotlin/TLSClientSocket.kt b/kmqtt-client/src/posixMain/kotlin/TLSClientSocket.kt index 2f7aa29..8b2f6cf 100644 --- a/kmqtt-client/src/posixMain/kotlin/TLSClientSocket.kt +++ b/kmqtt-client/src/posixMain/kotlin/TLSClientSocket.kt @@ -1,6 +1,7 @@ import kotlinx.cinterop.* import platform.posix.* import socket.IOException +import socket.tls.TLSClientSettings import socket.tls.TLSSocket public actual class TLSClientSocket actual constructor( diff --git a/kmqtt-client/src/tvosArm64Main/kotlin/TLSClientEngine.kt b/kmqtt-client/src/tvosArm64Main/kotlin/TLSClientEngine.kt index a8cb07d..5e5f441 100644 --- a/kmqtt-client/src/tvosArm64Main/kotlin/TLSClientEngine.kt +++ b/kmqtt-client/src/tvosArm64Main/kotlin/TLSClientEngine.kt @@ -4,6 +4,7 @@ import platform.posix.getenv import platform.posix.strcpy import platform.posix.strlen import socket.IOException +import socket.tls.TLSClientSettings import socket.tls.TLSEngine internal actual class TLSClientEngine actual constructor(tlsSettings: TLSClientSettings) : TLSEngine { @@ -12,6 +13,42 @@ internal actual class TLSClientEngine actual constructor(tlsSettings: TLSClientS private val readBio: CPointer private val writeBio: CPointer + fun loadServerCertsFromString(context: CPointer, certBuffer: String): Boolean { + val bio = BIO_new(BIO_s_mem()) + BIO_puts(bio, certBuffer) + + val inf = PEM_X509_INFO_read_bio(bio, null, null, null) + + if (inf == null) { + BIO_free(bio) + return false + } + + val ctx = SSL_CTX_get_cert_store(context); + + var loaded = 0 + for (i in 0..? -> + X509_INFO_free(buf) + }) + BIO_free(bio) + return false + } + loaded++ + } + } + + sk_X509_INFO_pop_free(inf, staticCFunction { buf: CPointer? -> + X509_INFO_free(buf) + }) + BIO_free(bio) + + return loaded > 0 + } + init { val readBio = BIO_new(BIO_s_mem()) ?: throw IOException("Failed allocating read BIO") @@ -24,9 +61,12 @@ internal actual class TLSClientEngine actual constructor(tlsSettings: TLSClientS val method = TLS_client_method() val sslContext = SSL_CTX_new(method)!! - if (tlsSettings.serverCertificatePath != null) { - if (SSL_CTX_load_verify_locations(sslContext, tlsSettings.serverCertificatePath, null) != 1) { - throw Exception("Server certificate path not found") + if (tlsSettings.serverCertificate != null) { + if (!loadServerCertsFromString(sslContext, tlsSettings.serverCertificate!!)) { + // Try file + if (SSL_CTX_load_verify_locations(sslContext, tlsSettings.serverCertificate, null) != 1) { + throw Exception("Server certificate path not found") + } } } else { if (SSL_CTX_load_verify_locations(sslContext, null, getenv(X509_get_default_cert_dir_env()?.toKString())?.toKString()) != 1) { @@ -34,12 +74,32 @@ internal actual class TLSClientEngine actual constructor(tlsSettings: TLSClientS } } - if (tlsSettings.clientCertificatePath != null) { - if (SSL_CTX_use_certificate_file(sslContext, tlsSettings.clientCertificatePath, SSL_FILETYPE_PEM) != 1) { - throw Exception("Cannot load client's certificate file") + if (tlsSettings.clientCertificate != null) { + val bio = BIO_new(BIO_s_mem()) + BIO_puts(bio, tlsSettings.clientCertificate) + val certificate = PEM_read_bio_X509(bio, null, null, null) + + if (certificate != null) { + if (SSL_CTX_use_certificate(sslContext, certificate) != 1) { + throw Exception("Cannot load client's certificate") + } + } else { + // Load file + if (SSL_CTX_use_certificate_file(sslContext, tlsSettings.clientCertificate, SSL_FILETYPE_PEM) != 1) { + throw Exception("Cannot load client's certificate file") + } } - if (SSL_CTX_use_PrivateKey_file(sslContext, tlsSettings.clientCertificateKeyPath!!, SSL_FILETYPE_PEM) != 1) { - throw Exception("Cannot load client's key file") + + BIO_puts(bio, tlsSettings.clientCertificateKey) + val key = PEM_read_bio_PrivateKey(bio, null, null, null) + if (key != null) { + if (SSL_CTX_use_PrivateKey(sslContext, key) != 1) { + throw Exception("Cannot load client's key") + } + } else { + if (SSL_CTX_use_PrivateKey_file(sslContext, tlsSettings.clientCertificateKey!!, SSL_FILETYPE_PEM) != 1) { + throw Exception("Cannot load client's key file") + } } if (SSL_CTX_check_private_key(sslContext) != 1) { throw Exception("Client's certificate and key don't match") diff --git a/kmqtt-client/src/tvosSimulatorArm64Main/kotlin/TLSClientEngine.kt b/kmqtt-client/src/tvosSimulatorArm64Main/kotlin/TLSClientEngine.kt index a8cb07d..5e5f441 100644 --- a/kmqtt-client/src/tvosSimulatorArm64Main/kotlin/TLSClientEngine.kt +++ b/kmqtt-client/src/tvosSimulatorArm64Main/kotlin/TLSClientEngine.kt @@ -4,6 +4,7 @@ import platform.posix.getenv import platform.posix.strcpy import platform.posix.strlen import socket.IOException +import socket.tls.TLSClientSettings import socket.tls.TLSEngine internal actual class TLSClientEngine actual constructor(tlsSettings: TLSClientSettings) : TLSEngine { @@ -12,6 +13,42 @@ internal actual class TLSClientEngine actual constructor(tlsSettings: TLSClientS private val readBio: CPointer private val writeBio: CPointer + fun loadServerCertsFromString(context: CPointer, certBuffer: String): Boolean { + val bio = BIO_new(BIO_s_mem()) + BIO_puts(bio, certBuffer) + + val inf = PEM_X509_INFO_read_bio(bio, null, null, null) + + if (inf == null) { + BIO_free(bio) + return false + } + + val ctx = SSL_CTX_get_cert_store(context); + + var loaded = 0 + for (i in 0..? -> + X509_INFO_free(buf) + }) + BIO_free(bio) + return false + } + loaded++ + } + } + + sk_X509_INFO_pop_free(inf, staticCFunction { buf: CPointer? -> + X509_INFO_free(buf) + }) + BIO_free(bio) + + return loaded > 0 + } + init { val readBio = BIO_new(BIO_s_mem()) ?: throw IOException("Failed allocating read BIO") @@ -24,9 +61,12 @@ internal actual class TLSClientEngine actual constructor(tlsSettings: TLSClientS val method = TLS_client_method() val sslContext = SSL_CTX_new(method)!! - if (tlsSettings.serverCertificatePath != null) { - if (SSL_CTX_load_verify_locations(sslContext, tlsSettings.serverCertificatePath, null) != 1) { - throw Exception("Server certificate path not found") + if (tlsSettings.serverCertificate != null) { + if (!loadServerCertsFromString(sslContext, tlsSettings.serverCertificate!!)) { + // Try file + if (SSL_CTX_load_verify_locations(sslContext, tlsSettings.serverCertificate, null) != 1) { + throw Exception("Server certificate path not found") + } } } else { if (SSL_CTX_load_verify_locations(sslContext, null, getenv(X509_get_default_cert_dir_env()?.toKString())?.toKString()) != 1) { @@ -34,12 +74,32 @@ internal actual class TLSClientEngine actual constructor(tlsSettings: TLSClientS } } - if (tlsSettings.clientCertificatePath != null) { - if (SSL_CTX_use_certificate_file(sslContext, tlsSettings.clientCertificatePath, SSL_FILETYPE_PEM) != 1) { - throw Exception("Cannot load client's certificate file") + if (tlsSettings.clientCertificate != null) { + val bio = BIO_new(BIO_s_mem()) + BIO_puts(bio, tlsSettings.clientCertificate) + val certificate = PEM_read_bio_X509(bio, null, null, null) + + if (certificate != null) { + if (SSL_CTX_use_certificate(sslContext, certificate) != 1) { + throw Exception("Cannot load client's certificate") + } + } else { + // Load file + if (SSL_CTX_use_certificate_file(sslContext, tlsSettings.clientCertificate, SSL_FILETYPE_PEM) != 1) { + throw Exception("Cannot load client's certificate file") + } } - if (SSL_CTX_use_PrivateKey_file(sslContext, tlsSettings.clientCertificateKeyPath!!, SSL_FILETYPE_PEM) != 1) { - throw Exception("Cannot load client's key file") + + BIO_puts(bio, tlsSettings.clientCertificateKey) + val key = PEM_read_bio_PrivateKey(bio, null, null, null) + if (key != null) { + if (SSL_CTX_use_PrivateKey(sslContext, key) != 1) { + throw Exception("Cannot load client's key") + } + } else { + if (SSL_CTX_use_PrivateKey_file(sslContext, tlsSettings.clientCertificateKey!!, SSL_FILETYPE_PEM) != 1) { + throw Exception("Cannot load client's key file") + } } if (SSL_CTX_check_private_key(sslContext) != 1) { throw Exception("Client's certificate and key don't match") diff --git a/kmqtt-client/src/tvosX64Main/kotlin/TLSClientEngine.kt b/kmqtt-client/src/tvosX64Main/kotlin/TLSClientEngine.kt index a8cb07d..5e5f441 100644 --- a/kmqtt-client/src/tvosX64Main/kotlin/TLSClientEngine.kt +++ b/kmqtt-client/src/tvosX64Main/kotlin/TLSClientEngine.kt @@ -4,6 +4,7 @@ import platform.posix.getenv import platform.posix.strcpy import platform.posix.strlen import socket.IOException +import socket.tls.TLSClientSettings import socket.tls.TLSEngine internal actual class TLSClientEngine actual constructor(tlsSettings: TLSClientSettings) : TLSEngine { @@ -12,6 +13,42 @@ internal actual class TLSClientEngine actual constructor(tlsSettings: TLSClientS private val readBio: CPointer private val writeBio: CPointer + fun loadServerCertsFromString(context: CPointer, certBuffer: String): Boolean { + val bio = BIO_new(BIO_s_mem()) + BIO_puts(bio, certBuffer) + + val inf = PEM_X509_INFO_read_bio(bio, null, null, null) + + if (inf == null) { + BIO_free(bio) + return false + } + + val ctx = SSL_CTX_get_cert_store(context); + + var loaded = 0 + for (i in 0..? -> + X509_INFO_free(buf) + }) + BIO_free(bio) + return false + } + loaded++ + } + } + + sk_X509_INFO_pop_free(inf, staticCFunction { buf: CPointer? -> + X509_INFO_free(buf) + }) + BIO_free(bio) + + return loaded > 0 + } + init { val readBio = BIO_new(BIO_s_mem()) ?: throw IOException("Failed allocating read BIO") @@ -24,9 +61,12 @@ internal actual class TLSClientEngine actual constructor(tlsSettings: TLSClientS val method = TLS_client_method() val sslContext = SSL_CTX_new(method)!! - if (tlsSettings.serverCertificatePath != null) { - if (SSL_CTX_load_verify_locations(sslContext, tlsSettings.serverCertificatePath, null) != 1) { - throw Exception("Server certificate path not found") + if (tlsSettings.serverCertificate != null) { + if (!loadServerCertsFromString(sslContext, tlsSettings.serverCertificate!!)) { + // Try file + if (SSL_CTX_load_verify_locations(sslContext, tlsSettings.serverCertificate, null) != 1) { + throw Exception("Server certificate path not found") + } } } else { if (SSL_CTX_load_verify_locations(sslContext, null, getenv(X509_get_default_cert_dir_env()?.toKString())?.toKString()) != 1) { @@ -34,12 +74,32 @@ internal actual class TLSClientEngine actual constructor(tlsSettings: TLSClientS } } - if (tlsSettings.clientCertificatePath != null) { - if (SSL_CTX_use_certificate_file(sslContext, tlsSettings.clientCertificatePath, SSL_FILETYPE_PEM) != 1) { - throw Exception("Cannot load client's certificate file") + if (tlsSettings.clientCertificate != null) { + val bio = BIO_new(BIO_s_mem()) + BIO_puts(bio, tlsSettings.clientCertificate) + val certificate = PEM_read_bio_X509(bio, null, null, null) + + if (certificate != null) { + if (SSL_CTX_use_certificate(sslContext, certificate) != 1) { + throw Exception("Cannot load client's certificate") + } + } else { + // Load file + if (SSL_CTX_use_certificate_file(sslContext, tlsSettings.clientCertificate, SSL_FILETYPE_PEM) != 1) { + throw Exception("Cannot load client's certificate file") + } } - if (SSL_CTX_use_PrivateKey_file(sslContext, tlsSettings.clientCertificateKeyPath!!, SSL_FILETYPE_PEM) != 1) { - throw Exception("Cannot load client's key file") + + BIO_puts(bio, tlsSettings.clientCertificateKey) + val key = PEM_read_bio_PrivateKey(bio, null, null, null) + if (key != null) { + if (SSL_CTX_use_PrivateKey(sslContext, key) != 1) { + throw Exception("Cannot load client's key") + } + } else { + if (SSL_CTX_use_PrivateKey_file(sslContext, tlsSettings.clientCertificateKey!!, SSL_FILETYPE_PEM) != 1) { + throw Exception("Cannot load client's key file") + } } if (SSL_CTX_check_private_key(sslContext) != 1) { throw Exception("Client's certificate and key don't match") diff --git a/kmqtt-client/src/watchosArm32Main/kotlin/TLSClientEngine.kt b/kmqtt-client/src/watchosArm32Main/kotlin/TLSClientEngine.kt index a8cb07d..5e5f441 100644 --- a/kmqtt-client/src/watchosArm32Main/kotlin/TLSClientEngine.kt +++ b/kmqtt-client/src/watchosArm32Main/kotlin/TLSClientEngine.kt @@ -4,6 +4,7 @@ import platform.posix.getenv import platform.posix.strcpy import platform.posix.strlen import socket.IOException +import socket.tls.TLSClientSettings import socket.tls.TLSEngine internal actual class TLSClientEngine actual constructor(tlsSettings: TLSClientSettings) : TLSEngine { @@ -12,6 +13,42 @@ internal actual class TLSClientEngine actual constructor(tlsSettings: TLSClientS private val readBio: CPointer private val writeBio: CPointer + fun loadServerCertsFromString(context: CPointer, certBuffer: String): Boolean { + val bio = BIO_new(BIO_s_mem()) + BIO_puts(bio, certBuffer) + + val inf = PEM_X509_INFO_read_bio(bio, null, null, null) + + if (inf == null) { + BIO_free(bio) + return false + } + + val ctx = SSL_CTX_get_cert_store(context); + + var loaded = 0 + for (i in 0..? -> + X509_INFO_free(buf) + }) + BIO_free(bio) + return false + } + loaded++ + } + } + + sk_X509_INFO_pop_free(inf, staticCFunction { buf: CPointer? -> + X509_INFO_free(buf) + }) + BIO_free(bio) + + return loaded > 0 + } + init { val readBio = BIO_new(BIO_s_mem()) ?: throw IOException("Failed allocating read BIO") @@ -24,9 +61,12 @@ internal actual class TLSClientEngine actual constructor(tlsSettings: TLSClientS val method = TLS_client_method() val sslContext = SSL_CTX_new(method)!! - if (tlsSettings.serverCertificatePath != null) { - if (SSL_CTX_load_verify_locations(sslContext, tlsSettings.serverCertificatePath, null) != 1) { - throw Exception("Server certificate path not found") + if (tlsSettings.serverCertificate != null) { + if (!loadServerCertsFromString(sslContext, tlsSettings.serverCertificate!!)) { + // Try file + if (SSL_CTX_load_verify_locations(sslContext, tlsSettings.serverCertificate, null) != 1) { + throw Exception("Server certificate path not found") + } } } else { if (SSL_CTX_load_verify_locations(sslContext, null, getenv(X509_get_default_cert_dir_env()?.toKString())?.toKString()) != 1) { @@ -34,12 +74,32 @@ internal actual class TLSClientEngine actual constructor(tlsSettings: TLSClientS } } - if (tlsSettings.clientCertificatePath != null) { - if (SSL_CTX_use_certificate_file(sslContext, tlsSettings.clientCertificatePath, SSL_FILETYPE_PEM) != 1) { - throw Exception("Cannot load client's certificate file") + if (tlsSettings.clientCertificate != null) { + val bio = BIO_new(BIO_s_mem()) + BIO_puts(bio, tlsSettings.clientCertificate) + val certificate = PEM_read_bio_X509(bio, null, null, null) + + if (certificate != null) { + if (SSL_CTX_use_certificate(sslContext, certificate) != 1) { + throw Exception("Cannot load client's certificate") + } + } else { + // Load file + if (SSL_CTX_use_certificate_file(sslContext, tlsSettings.clientCertificate, SSL_FILETYPE_PEM) != 1) { + throw Exception("Cannot load client's certificate file") + } } - if (SSL_CTX_use_PrivateKey_file(sslContext, tlsSettings.clientCertificateKeyPath!!, SSL_FILETYPE_PEM) != 1) { - throw Exception("Cannot load client's key file") + + BIO_puts(bio, tlsSettings.clientCertificateKey) + val key = PEM_read_bio_PrivateKey(bio, null, null, null) + if (key != null) { + if (SSL_CTX_use_PrivateKey(sslContext, key) != 1) { + throw Exception("Cannot load client's key") + } + } else { + if (SSL_CTX_use_PrivateKey_file(sslContext, tlsSettings.clientCertificateKey!!, SSL_FILETYPE_PEM) != 1) { + throw Exception("Cannot load client's key file") + } } if (SSL_CTX_check_private_key(sslContext) != 1) { throw Exception("Client's certificate and key don't match") diff --git a/kmqtt-client/src/watchosArm64Main/kotlin/TLSClientEngine.kt b/kmqtt-client/src/watchosArm64Main/kotlin/TLSClientEngine.kt index a8cb07d..5e5f441 100644 --- a/kmqtt-client/src/watchosArm64Main/kotlin/TLSClientEngine.kt +++ b/kmqtt-client/src/watchosArm64Main/kotlin/TLSClientEngine.kt @@ -4,6 +4,7 @@ import platform.posix.getenv import platform.posix.strcpy import platform.posix.strlen import socket.IOException +import socket.tls.TLSClientSettings import socket.tls.TLSEngine internal actual class TLSClientEngine actual constructor(tlsSettings: TLSClientSettings) : TLSEngine { @@ -12,6 +13,42 @@ internal actual class TLSClientEngine actual constructor(tlsSettings: TLSClientS private val readBio: CPointer private val writeBio: CPointer + fun loadServerCertsFromString(context: CPointer, certBuffer: String): Boolean { + val bio = BIO_new(BIO_s_mem()) + BIO_puts(bio, certBuffer) + + val inf = PEM_X509_INFO_read_bio(bio, null, null, null) + + if (inf == null) { + BIO_free(bio) + return false + } + + val ctx = SSL_CTX_get_cert_store(context); + + var loaded = 0 + for (i in 0..? -> + X509_INFO_free(buf) + }) + BIO_free(bio) + return false + } + loaded++ + } + } + + sk_X509_INFO_pop_free(inf, staticCFunction { buf: CPointer? -> + X509_INFO_free(buf) + }) + BIO_free(bio) + + return loaded > 0 + } + init { val readBio = BIO_new(BIO_s_mem()) ?: throw IOException("Failed allocating read BIO") @@ -24,9 +61,12 @@ internal actual class TLSClientEngine actual constructor(tlsSettings: TLSClientS val method = TLS_client_method() val sslContext = SSL_CTX_new(method)!! - if (tlsSettings.serverCertificatePath != null) { - if (SSL_CTX_load_verify_locations(sslContext, tlsSettings.serverCertificatePath, null) != 1) { - throw Exception("Server certificate path not found") + if (tlsSettings.serverCertificate != null) { + if (!loadServerCertsFromString(sslContext, tlsSettings.serverCertificate!!)) { + // Try file + if (SSL_CTX_load_verify_locations(sslContext, tlsSettings.serverCertificate, null) != 1) { + throw Exception("Server certificate path not found") + } } } else { if (SSL_CTX_load_verify_locations(sslContext, null, getenv(X509_get_default_cert_dir_env()?.toKString())?.toKString()) != 1) { @@ -34,12 +74,32 @@ internal actual class TLSClientEngine actual constructor(tlsSettings: TLSClientS } } - if (tlsSettings.clientCertificatePath != null) { - if (SSL_CTX_use_certificate_file(sslContext, tlsSettings.clientCertificatePath, SSL_FILETYPE_PEM) != 1) { - throw Exception("Cannot load client's certificate file") + if (tlsSettings.clientCertificate != null) { + val bio = BIO_new(BIO_s_mem()) + BIO_puts(bio, tlsSettings.clientCertificate) + val certificate = PEM_read_bio_X509(bio, null, null, null) + + if (certificate != null) { + if (SSL_CTX_use_certificate(sslContext, certificate) != 1) { + throw Exception("Cannot load client's certificate") + } + } else { + // Load file + if (SSL_CTX_use_certificate_file(sslContext, tlsSettings.clientCertificate, SSL_FILETYPE_PEM) != 1) { + throw Exception("Cannot load client's certificate file") + } } - if (SSL_CTX_use_PrivateKey_file(sslContext, tlsSettings.clientCertificateKeyPath!!, SSL_FILETYPE_PEM) != 1) { - throw Exception("Cannot load client's key file") + + BIO_puts(bio, tlsSettings.clientCertificateKey) + val key = PEM_read_bio_PrivateKey(bio, null, null, null) + if (key != null) { + if (SSL_CTX_use_PrivateKey(sslContext, key) != 1) { + throw Exception("Cannot load client's key") + } + } else { + if (SSL_CTX_use_PrivateKey_file(sslContext, tlsSettings.clientCertificateKey!!, SSL_FILETYPE_PEM) != 1) { + throw Exception("Cannot load client's key file") + } } if (SSL_CTX_check_private_key(sslContext) != 1) { throw Exception("Client's certificate and key don't match") diff --git a/kmqtt-client/src/watchosSimulatorArm64Main/kotlin/TLSClientEngine.kt b/kmqtt-client/src/watchosSimulatorArm64Main/kotlin/TLSClientEngine.kt index a8cb07d..5e5f441 100644 --- a/kmqtt-client/src/watchosSimulatorArm64Main/kotlin/TLSClientEngine.kt +++ b/kmqtt-client/src/watchosSimulatorArm64Main/kotlin/TLSClientEngine.kt @@ -4,6 +4,7 @@ import platform.posix.getenv import platform.posix.strcpy import platform.posix.strlen import socket.IOException +import socket.tls.TLSClientSettings import socket.tls.TLSEngine internal actual class TLSClientEngine actual constructor(tlsSettings: TLSClientSettings) : TLSEngine { @@ -12,6 +13,42 @@ internal actual class TLSClientEngine actual constructor(tlsSettings: TLSClientS private val readBio: CPointer private val writeBio: CPointer + fun loadServerCertsFromString(context: CPointer, certBuffer: String): Boolean { + val bio = BIO_new(BIO_s_mem()) + BIO_puts(bio, certBuffer) + + val inf = PEM_X509_INFO_read_bio(bio, null, null, null) + + if (inf == null) { + BIO_free(bio) + return false + } + + val ctx = SSL_CTX_get_cert_store(context); + + var loaded = 0 + for (i in 0..? -> + X509_INFO_free(buf) + }) + BIO_free(bio) + return false + } + loaded++ + } + } + + sk_X509_INFO_pop_free(inf, staticCFunction { buf: CPointer? -> + X509_INFO_free(buf) + }) + BIO_free(bio) + + return loaded > 0 + } + init { val readBio = BIO_new(BIO_s_mem()) ?: throw IOException("Failed allocating read BIO") @@ -24,9 +61,12 @@ internal actual class TLSClientEngine actual constructor(tlsSettings: TLSClientS val method = TLS_client_method() val sslContext = SSL_CTX_new(method)!! - if (tlsSettings.serverCertificatePath != null) { - if (SSL_CTX_load_verify_locations(sslContext, tlsSettings.serverCertificatePath, null) != 1) { - throw Exception("Server certificate path not found") + if (tlsSettings.serverCertificate != null) { + if (!loadServerCertsFromString(sslContext, tlsSettings.serverCertificate!!)) { + // Try file + if (SSL_CTX_load_verify_locations(sslContext, tlsSettings.serverCertificate, null) != 1) { + throw Exception("Server certificate path not found") + } } } else { if (SSL_CTX_load_verify_locations(sslContext, null, getenv(X509_get_default_cert_dir_env()?.toKString())?.toKString()) != 1) { @@ -34,12 +74,32 @@ internal actual class TLSClientEngine actual constructor(tlsSettings: TLSClientS } } - if (tlsSettings.clientCertificatePath != null) { - if (SSL_CTX_use_certificate_file(sslContext, tlsSettings.clientCertificatePath, SSL_FILETYPE_PEM) != 1) { - throw Exception("Cannot load client's certificate file") + if (tlsSettings.clientCertificate != null) { + val bio = BIO_new(BIO_s_mem()) + BIO_puts(bio, tlsSettings.clientCertificate) + val certificate = PEM_read_bio_X509(bio, null, null, null) + + if (certificate != null) { + if (SSL_CTX_use_certificate(sslContext, certificate) != 1) { + throw Exception("Cannot load client's certificate") + } + } else { + // Load file + if (SSL_CTX_use_certificate_file(sslContext, tlsSettings.clientCertificate, SSL_FILETYPE_PEM) != 1) { + throw Exception("Cannot load client's certificate file") + } } - if (SSL_CTX_use_PrivateKey_file(sslContext, tlsSettings.clientCertificateKeyPath!!, SSL_FILETYPE_PEM) != 1) { - throw Exception("Cannot load client's key file") + + BIO_puts(bio, tlsSettings.clientCertificateKey) + val key = PEM_read_bio_PrivateKey(bio, null, null, null) + if (key != null) { + if (SSL_CTX_use_PrivateKey(sslContext, key) != 1) { + throw Exception("Cannot load client's key") + } + } else { + if (SSL_CTX_use_PrivateKey_file(sslContext, tlsSettings.clientCertificateKey!!, SSL_FILETYPE_PEM) != 1) { + throw Exception("Cannot load client's key file") + } } if (SSL_CTX_check_private_key(sslContext) != 1) { throw Exception("Client's certificate and key don't match") diff --git a/kmqtt-client/src/watchosX64Main/kotlin/TLSClientEngine.kt b/kmqtt-client/src/watchosX64Main/kotlin/TLSClientEngine.kt index a8cb07d..5e5f441 100644 --- a/kmqtt-client/src/watchosX64Main/kotlin/TLSClientEngine.kt +++ b/kmqtt-client/src/watchosX64Main/kotlin/TLSClientEngine.kt @@ -4,6 +4,7 @@ import platform.posix.getenv import platform.posix.strcpy import platform.posix.strlen import socket.IOException +import socket.tls.TLSClientSettings import socket.tls.TLSEngine internal actual class TLSClientEngine actual constructor(tlsSettings: TLSClientSettings) : TLSEngine { @@ -12,6 +13,42 @@ internal actual class TLSClientEngine actual constructor(tlsSettings: TLSClientS private val readBio: CPointer private val writeBio: CPointer + fun loadServerCertsFromString(context: CPointer, certBuffer: String): Boolean { + val bio = BIO_new(BIO_s_mem()) + BIO_puts(bio, certBuffer) + + val inf = PEM_X509_INFO_read_bio(bio, null, null, null) + + if (inf == null) { + BIO_free(bio) + return false + } + + val ctx = SSL_CTX_get_cert_store(context); + + var loaded = 0 + for (i in 0..? -> + X509_INFO_free(buf) + }) + BIO_free(bio) + return false + } + loaded++ + } + } + + sk_X509_INFO_pop_free(inf, staticCFunction { buf: CPointer? -> + X509_INFO_free(buf) + }) + BIO_free(bio) + + return loaded > 0 + } + init { val readBio = BIO_new(BIO_s_mem()) ?: throw IOException("Failed allocating read BIO") @@ -24,9 +61,12 @@ internal actual class TLSClientEngine actual constructor(tlsSettings: TLSClientS val method = TLS_client_method() val sslContext = SSL_CTX_new(method)!! - if (tlsSettings.serverCertificatePath != null) { - if (SSL_CTX_load_verify_locations(sslContext, tlsSettings.serverCertificatePath, null) != 1) { - throw Exception("Server certificate path not found") + if (tlsSettings.serverCertificate != null) { + if (!loadServerCertsFromString(sslContext, tlsSettings.serverCertificate!!)) { + // Try file + if (SSL_CTX_load_verify_locations(sslContext, tlsSettings.serverCertificate, null) != 1) { + throw Exception("Server certificate path not found") + } } } else { if (SSL_CTX_load_verify_locations(sslContext, null, getenv(X509_get_default_cert_dir_env()?.toKString())?.toKString()) != 1) { @@ -34,12 +74,32 @@ internal actual class TLSClientEngine actual constructor(tlsSettings: TLSClientS } } - if (tlsSettings.clientCertificatePath != null) { - if (SSL_CTX_use_certificate_file(sslContext, tlsSettings.clientCertificatePath, SSL_FILETYPE_PEM) != 1) { - throw Exception("Cannot load client's certificate file") + if (tlsSettings.clientCertificate != null) { + val bio = BIO_new(BIO_s_mem()) + BIO_puts(bio, tlsSettings.clientCertificate) + val certificate = PEM_read_bio_X509(bio, null, null, null) + + if (certificate != null) { + if (SSL_CTX_use_certificate(sslContext, certificate) != 1) { + throw Exception("Cannot load client's certificate") + } + } else { + // Load file + if (SSL_CTX_use_certificate_file(sslContext, tlsSettings.clientCertificate, SSL_FILETYPE_PEM) != 1) { + throw Exception("Cannot load client's certificate file") + } } - if (SSL_CTX_use_PrivateKey_file(sslContext, tlsSettings.clientCertificateKeyPath!!, SSL_FILETYPE_PEM) != 1) { - throw Exception("Cannot load client's key file") + + BIO_puts(bio, tlsSettings.clientCertificateKey) + val key = PEM_read_bio_PrivateKey(bio, null, null, null) + if (key != null) { + if (SSL_CTX_use_PrivateKey(sslContext, key) != 1) { + throw Exception("Cannot load client's key") + } + } else { + if (SSL_CTX_use_PrivateKey_file(sslContext, tlsSettings.clientCertificateKey!!, SSL_FILETYPE_PEM) != 1) { + throw Exception("Cannot load client's key file") + } } if (SSL_CTX_check_private_key(sslContext) != 1) { throw Exception("Client's certificate and key don't match") diff --git a/kmqtt-common/src/commonMain/kotlin/CommonUtils.kt b/kmqtt-common/src/commonMain/kotlin/CommonUtils.kt index df9580f..a155695 100644 --- a/kmqtt-common/src/commonMain/kotlin/CommonUtils.kt +++ b/kmqtt-common/src/commonMain/kotlin/CommonUtils.kt @@ -176,3 +176,7 @@ public fun ByteArray.toBase64(): String { return r.substring(0, r.length - p.length) + p } + +public fun String.isValidPem(): Boolean { + return matches("(-----BEGIN PUBLIC KEY-----(\\n|\\r|\\r\\n)([0-9a-zA-Z+/=]{64}(\\n|\\r|\\r\\n))*([0-9a-zA-Z+/=]{1,63}(\\n|\\r|\\r\\n))?-----END PUBLIC KEY-----(\\n|\\r|\\r\\n)?)|(-----BEGIN PRIVATE KEY-----(\\n|\\r|\\r\\n)([0-9a-zA-Z+/=]{64}(\\n|\\r|\\r\\n))*([0-9a-zA-Z+/=]{1,63}(\\n|\\r|\\r\\n))?-----END PRIVATE KEY-----(\\n|\\r|\\r\\n)?)|(-----BEGIN CERTIFICATE-----(\\n|\\r|\\r\\n)([0-9a-zA-Z+/=]{64}(\\n|\\r|\\r\\n))*([0-9a-zA-Z+/=]{1,63}(\\n|\\r|\\r\\n))?-----END CERTIFICATE-----(\\n|\\r|\\r\\n)?)".toRegex()) +} diff --git a/kmqtt-common/src/commonMain/kotlin/socket/tls/TLSClientSettings.kt b/kmqtt-common/src/commonMain/kotlin/socket/tls/TLSClientSettings.kt index f2e8560..03d0c69 100644 --- a/kmqtt-common/src/commonMain/kotlin/socket/tls/TLSClientSettings.kt +++ b/kmqtt-common/src/commonMain/kotlin/socket/tls/TLSClientSettings.kt @@ -1,16 +1,22 @@ +package socket.tls + /** * TLS settings * * @param version the TLS version - * @param serverCertificatePath if the server certificate cannot be verified with the default chain, set this to the path of the server certificate file in PEM format, null otherwise - * @param clientCertificatePath the path to the client certificate file in PEM format if client verification necessary, null otherwise - * @param clientCertificateKeyPath the path to the client certificate key file in PEM format if client verification necessary, null otherwise - * @param clientCertificatePassword the password to the client certificate key if client verification necessary and the key has a password, null otherwise + * @param serverCertificate if the server certificate cannot be verified with the default chain, set this to the path + * of the server certificate file in PEM format, null otherwise + * @param clientCertificate the PEM client certificate string or the path to the client certificate file in PEM format + * if client verification is necessary, null otherwise + * @param clientCertificateKey the PEM client certificate key string or the path to the client certificate key file in + * PEM format if client verification necessary, null otherwise + * @param clientCertificatePassword the password to the client certificate key if client verification necessary and the + * key has a password, null otherwise */ public data class TLSClientSettings( val version: String = "TLS", - val serverCertificatePath: String? = null, - val clientCertificatePath: String? = null, - val clientCertificateKeyPath: String? = null, + val serverCertificate: String? = null, + val clientCertificate: String? = null, + val clientCertificateKey: String? = null, val clientCertificatePassword: String? = null )