From 861fed46fd200688c9c4d71bfdde7044335b7a0a Mon Sep 17 00:00:00 2001 From: olme04 Date: Wed, 30 Mar 2022 11:38:51 +0300 Subject: [PATCH 1/9] implement JVM tls support via SSLEngine --- .../network/tls/SSLEngineBufferAllocator.kt | 63 +++++++ .../io/ktor/network/tls/SSLEngineSocket.kt | 168 ++++++++++++++++++ .../io/ktor/network/tls/SSLEngineUnwrapper.kt | 128 +++++++++++++ .../io/ktor/network/tls/SSLEngineWrapper.kt | 65 +++++++ .../jvm/src/io/ktor/network/tls/TLS.kt | 6 + .../ktor/network/tls/tests/ConnectionTest.kt | 91 +++++++++- 6 files changed, 519 insertions(+), 2 deletions(-) create mode 100644 ktor-network/ktor-network-tls/jvm/src/io/ktor/network/tls/SSLEngineBufferAllocator.kt create mode 100644 ktor-network/ktor-network-tls/jvm/src/io/ktor/network/tls/SSLEngineSocket.kt create mode 100644 ktor-network/ktor-network-tls/jvm/src/io/ktor/network/tls/SSLEngineUnwrapper.kt create mode 100644 ktor-network/ktor-network-tls/jvm/src/io/ktor/network/tls/SSLEngineWrapper.kt diff --git a/ktor-network/ktor-network-tls/jvm/src/io/ktor/network/tls/SSLEngineBufferAllocator.kt b/ktor-network/ktor-network-tls/jvm/src/io/ktor/network/tls/SSLEngineBufferAllocator.kt new file mode 100644 index 00000000000..78e7efbeab6 --- /dev/null +++ b/ktor-network/ktor-network-tls/jvm/src/io/ktor/network/tls/SSLEngineBufferAllocator.kt @@ -0,0 +1,63 @@ +/* + * Copyright 2014-2022 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package io.ktor.network.tls + +import java.nio.* +import javax.net.ssl.* + +internal class SSLEngineBufferAllocator(private val engine: SSLEngine) { + private var packetBufferSize = 0 + private var applicationBufferSize = 0 + + fun allocatePacket(length: Int): ByteBuffer = allocate( + length, + get = { packetBufferSize }, + set = { packetBufferSize = it }, + new = { engine.session.packetBufferSize } + ) + + fun allocateApplication(length: Int): ByteBuffer = allocate( + length, + get = { applicationBufferSize }, + set = { applicationBufferSize = it }, + new = { engine.session.applicationBufferSize } + ) + + fun reallocatePacket(buffer: ByteBuffer, flip: Boolean): ByteBuffer = + reallocate(buffer, flip, ::allocatePacket) + + fun reallocateApplication(buffer: ByteBuffer, flip: Boolean): ByteBuffer = + reallocate(buffer, flip, ::allocateApplication) + + private inline fun allocate( + length: Int, + get: () -> Int, + set: (Int) -> Unit, + new: () -> Int + ): ByteBuffer = synchronized(this) { + if (get() == 0) { + set(new()) + } + if (length > get()) { + set(length) + } + return ByteBuffer.allocate(get()) + } + + private inline fun reallocate( + buffer: ByteBuffer, + flip: Boolean, + allocate: (length: Int) -> ByteBuffer + ): ByteBuffer { + val newSize = buffer.capacity() * 2 + val new = allocate(newSize) + if (flip) { + new.flip() + } + new.put(buffer) + return new + } + +} diff --git a/ktor-network/ktor-network-tls/jvm/src/io/ktor/network/tls/SSLEngineSocket.kt b/ktor-network/ktor-network-tls/jvm/src/io/ktor/network/tls/SSLEngineSocket.kt new file mode 100644 index 00000000000..1bfbd531bf2 --- /dev/null +++ b/ktor-network/ktor-network-tls/jvm/src/io/ktor/network/tls/SSLEngineSocket.kt @@ -0,0 +1,168 @@ +/* + * Copyright 2014-2022 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package io.ktor.network.tls + +import io.ktor.network.sockets.* +import io.ktor.utils.io.* +import kotlinx.atomicfu.* +import kotlinx.coroutines.* +import kotlinx.coroutines.sync.* +import javax.net.ssl.* +import kotlin.coroutines.* + +internal class SSLEngineSocket( + override val coroutineContext: CoroutineContext, + private val engine: SSLEngine, + connection: Connection +) : Socket, CoroutineScope { + private val socket = connection.socket + + override val socketContext: Job get() = socket.socketContext + override val remoteAddress: SocketAddress get() = socket.remoteAddress + override val localAddress: SocketAddress get() = socket.localAddress + + private val debugString = coroutineContext[CoroutineName]?.name ?: "DEBUG" + + private val lock = Mutex() + private val closed = atomic(false) + + private val bufferAllocator = SSLEngineBufferAllocator(engine) + private val wrapper = SSLEngineWrapper(engine, bufferAllocator, connection.output, debugString) + private val unwrapper = SSLEngineUnwrapper(engine, bufferAllocator, connection.input, debugString) + + override fun attachForReading(channel: ByteChannel): WriterJob = + writer(CoroutineName("network-tls-input"), channel) { + var error: Throwable? = null + try { + var destination = bufferAllocator.allocateApplication(0) + loop@ while (true) { + destination.clear() + //println("[$debugString] READING: readAndUnwrap.START") + //println("[$debugString] READING_BEFORE_UNWRAP: $destination") + val result = unwrapper.readAndUnwrap(destination) { destination = it } ?: break@loop + //println("[$debugString] READING_AFTER_UNWRAP: $destination") + //println("[$debugString] READING: readAndUnwrap.STOP") + + destination.flip() + //println("[$debugString] READING_WRITE_BEFORE: $destination") + if (destination.remaining() > 0) { + this.channel.writeFully(destination) + this.channel.flush() + } + //println("[$debugString] READING_WRITE_AFTER: $destination") + + handleResult(result) + if (result.status == SSLEngineResult.Status.CLOSED) break@loop + } + } catch (cause: Throwable) { + error = cause + throw cause + } finally { + //println(error) + unwrapper.cancel(error) + engine.closeOutbound() + doClose(error) + } + } + + @Suppress("BlockingMethodInNonBlockingContext") + override fun attachForWriting(channel: ByteChannel): ReaderJob = + reader(CoroutineName("network-tls-output"), channel) { + var error: Throwable? = null + try { + val source = bufferAllocator.allocateApplication(0) + loop@ while (true) { + source.clear() + //println("[$debugString] WRITING_READ_BEFORE: $source") + if (this.channel.readAvailable(source) == -1) break@loop + //println("[$debugString] WRITING_READ_AFTER: $source") + source.flip() + //println("[$debugString] WRITING_BEFORE_SOURCE: $source") + while (source.remaining() > 0) { + //println("[$debugString] WRITING: wrapAndWrite.START") + //println("[$debugString] WRITING_BEFORE_WRAP: $source") + val result = wrapper.wrapAndWrite(source) + //println("[$debugString] WRITING_AFTER_WRAP: $source") + //println("[$debugString] WRITING: wrapAndWrite.STOP") + + handleResult(result) + if (result.status == SSLEngineResult.Status.CLOSED) break@loop + } + } + } catch (cause: Throwable) { + error = cause + throw cause + } finally { + //println(error) + engine.closeInbound() //TODO: when this should be called??? + doClose(error) + } + } + + //TODO proper close implementation? + override fun close() { + engine.closeOutbound() + if (isActive) launch { + doClose(null) + } else { + if (closed.compareAndSet(expect = false, update = true)) socket.close() + } + } + + private suspend fun handleResult(result: SSLEngineResult) { + if (result.status == SSLEngineResult.Status.CLOSED) { + doClose(null) + return + } + if (result.handshakeStatus.needHandshake) { + doHandshake(result.handshakeStatus) + } + } + + private suspend fun doClose(cause: Throwable?) = lock.withLock { + if (closed.compareAndSet(expect = false, update = true)) { + socket.use { + wrapper.close(cause) + } + } + } + + private suspend fun doHandshake(initialStatus: SSLEngineResult.HandshakeStatus) = lock.withLock { + var temp = bufferAllocator.allocateApplication(0) + var status = initialStatus + while (true) { + //println("[$debugString] HANDSHAKE: $status") + when (status) { + SSLEngineResult.HandshakeStatus.NEED_TASK -> { + coroutineScope { + while (true) { + val task = engine.delegatedTask ?: break + launch(Dispatchers.IO) { task.run() } + } + } + status = engine.handshakeStatus + } + SSLEngineResult.HandshakeStatus.NEED_WRAP -> { + temp.clear() + temp.flip() + //println("[$debugString] HANDSHAKE: wrapAndWrite.START") + status = wrapper.wrapAndWrite(temp).handshakeStatus + //println("[$debugString] HANDSHAKE: wrapAndWrite.STOP") + } + SSLEngineResult.HandshakeStatus.NEED_UNWRAP -> { + temp.clear() + //println("[$debugString] HANDSHAKE: readAndUnwrap.START") + status = unwrapper.readAndUnwrap(temp) { temp = it }?.handshakeStatus ?: break + //println("[$debugString] HANDSHAKE: readAndUnwrap.STOP") + } + else -> break + } + } + } + + private val SSLEngineResult.HandshakeStatus.needHandshake: Boolean + get() = this != SSLEngineResult.HandshakeStatus.FINISHED && this != SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING + +} diff --git a/ktor-network/ktor-network-tls/jvm/src/io/ktor/network/tls/SSLEngineUnwrapper.kt b/ktor-network/ktor-network-tls/jvm/src/io/ktor/network/tls/SSLEngineUnwrapper.kt new file mode 100644 index 00000000000..e68609a69e1 --- /dev/null +++ b/ktor-network/ktor-network-tls/jvm/src/io/ktor/network/tls/SSLEngineUnwrapper.kt @@ -0,0 +1,128 @@ +/* + * Copyright 2014-2022 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package io.ktor.network.tls + +import io.ktor.utils.io.* +import kotlinx.coroutines.* +import kotlinx.coroutines.sync.* +import java.nio.* +import javax.net.ssl.* +import kotlin.coroutines.* + +internal class SSLEngineUnwrapper( + private val engine: SSLEngine, + private val bufferAllocator: SSLEngineBufferAllocator, + private val input: ByteReadChannel, + private val debugString: String +) { + private val unwrapLock = Mutex() + private var unwrapSource = bufferAllocator.allocatePacket(0) + private var unwrapRemaining = 0 + + //TODO revisit + private var unwrapResultCont: CancellableContinuation? = null + + fun cancel(cause: Throwable?) { + input.cancel(cause) + } + + @Suppress("BlockingMethodInNonBlockingContext") + suspend inline fun readAndUnwrap( + initialUnwrapDestination: ByteBuffer, + updateUnwrapDestination: (ByteBuffer) -> Unit + ): SSLEngineResult? { + if (!unwrapLock.tryLock()) return suspendCancellableCoroutine { + synchronized(this) { + unwrapResultCont = it + } + } + try { + var unwrapDestination: ByteBuffer = initialUnwrapDestination + var result: SSLEngineResult? + + if (unwrapRemaining > 0) { + unwrapSource.compact() + unwrapSource.flip() + } else { + unwrapSource.clear() + if (!readData()) { + synchronized(this) { + unwrapResultCont?.resume(null) + unwrapResultCont = null + } + return null + } + } + + //println("[$debugString] UNWRAP_INIT[DST]: $unwrapDestination") + //println("[$debugString] UNWRAP_INIT[SRC]: $unwrapSource") + while (true) { + //println("[$debugString] UNWRAP_BEFORE[DST]: $unwrapDestination") + //println("[$debugString] UNWRAP_BEFORE[SRC]: $unwrapSource") + result = engine.unwrap(unwrapSource, unwrapDestination) + //println("[$debugString] UNWRAP_RESULT: $result") + //println("[$debugString] UNWRAP_AFTER[DST]: $unwrapDestination") + //println("[$debugString] UNWRAP_AFTER[SRC]: $unwrapSource") + + when (result.status!!) { + SSLEngineResult.Status.BUFFER_UNDERFLOW -> { + //println("[$debugString] UNWRAP_UNDERFLOW_BEFORE[SRC]: $unwrapSource") + if (unwrapSource.limit() == unwrapSource.capacity()) { + //println("[$debugString] UNWRAP_UNDERFLOW_1") + //buffer is too small to read all needed data + unwrapSource = bufferAllocator.reallocatePacket(unwrapSource, flip = false) + } else { + //println("[$debugString] UNWRAP_UNDERFLOW_2") + //not all data received + unwrapSource.position(unwrapSource.limit()) + unwrapSource.limit(unwrapSource.capacity()) + } + //println("[$debugString] UNWRAP_UNDERFLOW_AFTER[SRC]: $unwrapSource") + if (!readData()) { + synchronized(this) { + unwrapResultCont?.resume(null) + unwrapResultCont = null + } + return null + } + } + SSLEngineResult.Status.BUFFER_OVERFLOW -> { + //println("[$debugString] UNWRAP_OVERFLOW_BEFORE[DST]: $unwrapDestination") + unwrapDestination = bufferAllocator.reallocateApplication(unwrapDestination, flip = true) + //println("[$debugString] UNWRAP_OVERFLOW_AFTER[DST]: $unwrapDestination") + } + else -> break + } + } + //println("[$debugString] UNWRAP_FINAL[DST]: $unwrapDestination") + //println("[$debugString] UNWRAP_FINAL[SRC]: $unwrapSource") + unwrapRemaining = unwrapSource.remaining() + if (unwrapDestination !== initialUnwrapDestination) updateUnwrapDestination(unwrapDestination) + synchronized(this) { + unwrapResultCont?.resume(result) + unwrapResultCont = null + } + return result + } catch (cause: Throwable) { + synchronized(this) { + unwrapResultCont?.resumeWithException(cause) + unwrapResultCont = null + } + throw cause + } finally { + unwrapLock.unlock() + } + } + + private suspend fun readData(): Boolean { + //println("[$debugString] UNWRAP_READ_BEFORE[SRC]: $unwrapSource") + val read = input.readAvailable(unwrapSource) + unwrapSource.flip() + //println("[$debugString] UNWRAP_READ_AFTER[SRC]: $unwrapSource") + + //println("[$debugString] UNWRAP_READ_COMPLETE[SRC]: $unwrapSource") + return read != -1 + } +} diff --git a/ktor-network/ktor-network-tls/jvm/src/io/ktor/network/tls/SSLEngineWrapper.kt b/ktor-network/ktor-network-tls/jvm/src/io/ktor/network/tls/SSLEngineWrapper.kt new file mode 100644 index 00000000000..2173af6940d --- /dev/null +++ b/ktor-network/ktor-network-tls/jvm/src/io/ktor/network/tls/SSLEngineWrapper.kt @@ -0,0 +1,65 @@ +/* + * Copyright 2014-2022 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package io.ktor.network.tls + +import io.ktor.utils.io.* +import kotlinx.coroutines.sync.* +import java.nio.* +import javax.net.ssl.* + +internal class SSLEngineWrapper( + private val engine: SSLEngine, + private val bufferAllocator: SSLEngineBufferAllocator, + private val output: ByteWriteChannel, + private val debugString: String +) { + private val wrapLock = Mutex() + private var wrapDestination = bufferAllocator.allocatePacket(0) + + suspend fun wrapAndWrite(wrapSource: ByteBuffer): SSLEngineResult = wrapLock.withLock { + wrapAndWriteX(wrapSource) + } + + @Suppress("BlockingMethodInNonBlockingContext") + private suspend fun wrapAndWriteX(wrapSource: ByteBuffer): SSLEngineResult { + var result: SSLEngineResult + wrapDestination.clear() + while (true) { + //println("[$debugString] WRAP_BEFORE: $wrapDestination") + result = engine.wrap(wrapSource, wrapDestination) + //println("[$debugString] WRAP_RESULT: $result") + //println("[$debugString] WRAP_AFTER: $wrapDestination") + if (result.status != SSLEngineResult.Status.BUFFER_OVERFLOW) break + //println("[$debugString] WRAP_OVERFLOW: $wrapDestination") + wrapDestination = bufferAllocator.reallocatePacket(wrapDestination, flip = true) + //println("[$debugString] WRAP_OVERFLOW_REALLOCATE: $wrapDestination") + } + //println("[$debugString] WRAP_WRITE_BEFORE: $wrapDestination") + if (result.bytesProduced() > 0) { + wrapDestination.flip() + //println("[$debugString] WRAP_WRITE: $wrapDestination") + output.writeFully(wrapDestination) + output.flush() + } + //println("[$debugString] WRAP_WRITE_AFTER: $wrapDestination") + return result + } + + suspend fun close(cause: Throwable?): SSLEngineResult = wrapLock.withLock { + //println("[$debugString] CLOSE: $cause") + val temp = bufferAllocator.allocateApplication(0) + var result: SSLEngineResult + do { + result = wrapAndWriteX(temp) + } while ( + result.status != SSLEngineResult.Status.CLOSED && + !(result.status == SSLEngineResult.Status.OK && result.handshakeStatus == SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) + ) + + output.close(cause) + + result + } +} diff --git a/ktor-network/ktor-network-tls/jvm/src/io/ktor/network/tls/TLS.kt b/ktor-network/ktor-network-tls/jvm/src/io/ktor/network/tls/TLS.kt index d6df5d2eb4b..8db26c85590 100644 --- a/ktor-network/ktor-network-tls/jvm/src/io/ktor/network/tls/TLS.kt +++ b/ktor-network/ktor-network-tls/jvm/src/io/ktor/network/tls/TLS.kt @@ -5,6 +5,7 @@ package io.ktor.network.tls import io.ktor.network.sockets.* +import io.ktor.util.* import java.security.* import javax.net.ssl.* import kotlin.coroutines.* @@ -50,3 +51,8 @@ public suspend fun Socket.tls( */ public actual suspend fun Socket.tls(coroutineContext: CoroutineContext, block: TLSConfigBuilder.() -> Unit): Socket = tls(coroutineContext, TLSConfigBuilder().apply(block).build()) + +@InternalAPI +public fun Socket.tls(coroutineContext: CoroutineContext, engine: SSLEngine): Socket { + return SSLEngineSocket(coroutineContext, engine, connection()) +} diff --git a/ktor-network/ktor-network-tls/jvm/test/io/ktor/network/tls/tests/ConnectionTest.kt b/ktor-network/ktor-network-tls/jvm/test/io/ktor/network/tls/tests/ConnectionTest.kt index bcff909eca3..c49e6f5252a 100644 --- a/ktor-network/ktor-network-tls/jvm/test/io/ktor/network/tls/tests/ConnectionTest.kt +++ b/ktor-network/ktor-network-tls/jvm/test/io/ktor/network/tls/tests/ConnectionTest.kt @@ -10,8 +10,10 @@ import io.ktor.network.sockets.* import io.ktor.network.sockets.InetSocketAddress import io.ktor.network.tls.* import io.ktor.network.tls.certificates.* +import io.ktor.util.* import io.ktor.util.cio.* import io.ktor.utils.io.* +import io.ktor.utils.io.core.* import io.netty.bootstrap.* import io.netty.channel.* import io.netty.channel.nio.* @@ -53,8 +55,93 @@ class ConnectionTest { flush() } - socket.openReadChannel().readRemaining() - Unit + assertTrue(socket.openReadChannel().readRemaining().isNotEmpty) + } + + @OptIn(InternalAPI::class) + @Test + fun tlsEngineWithoutCloseTest(): Unit = runBlocking { + val context = SSLContext.getInstance("TLS").apply { + val trustManager = object : X509TrustManager { + override fun checkClientTrusted(chain: Array?, authType: String?) { + } + + override fun checkServerTrusted(chain: Array?, authType: String?) { + } + + override fun getAcceptedIssuers(): Array = arrayOf() + } + init(null, arrayOf(trustManager), null) + } + val engine = context.createSSLEngine().apply { + useClientMode = true + } + val selectorManager = ActorSelectorManager(Dispatchers.IO) + val socket = aSocket(selectorManager) + .tcp() + .connect("www.google.com", port = 443) + .tls(Dispatchers.Default, engine) + + val channel = socket.openWriteChannel() + channel.apply { + writeStringUtf8("GET / HTTP/1.1\r\n") + writeStringUtf8("Host: www.google.com\r\n") + writeStringUtf8("Connection: close\r\n\r\n") + flush() + } + assertTrue(socket.openReadChannel().readRemaining().isNotEmpty) + } + + @OptIn(InternalAPI::class) + @Test + fun testClientServer() = runBlocking { + val context = SSLContext.getInstance("TLS").apply { + val keyStore = generateCertificate() + val trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()).apply { + init(keyStore) + } + val keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()).apply { + init(keyStore, "changeit".toCharArray()) + } + init(keyManagerFactory.keyManagers, trustManagerFactory.trustManagers, null) + } + SelectorManager(Dispatchers.IO).use { selector -> + val tcp = aSocket(selector).tcp() + + tcp.bind().use { serverSocket -> + val serverJob = GlobalScope.launch { + while (true) serverSocket.accept() + .tls(Dispatchers.Default, context.createSSLEngine().apply { useClientMode = false }) + .use { socket -> + val reader = socket.openReadChannel() + val writer = socket.openWriteChannel() + repeat(3) { + val line = assertNotNull(reader.readUTF8Line()) + println("SSS: $line") + writer.writeStringUtf8("$line\r\n") + writer.flush() + } + delay(2000) //await reading from client socket + } + } + + tcp.connect(serverSocket.localAddress) + .tls(Dispatchers.Default, context.createSSLEngine().apply { useClientMode = true }) + .use { socket -> + socket.openWriteChannel().apply { + writeStringUtf8("GET / HTTP/1.1\r\n") + writeStringUtf8("Host: www.google.com\r\n") + writeStringUtf8("Connection: close\r\n") + flush() + } + val reader = socket.openReadChannel() + repeat(3) { + println("CCC: ${assertNotNull(reader.readUTF8Line())}") + } + } + serverJob.cancelAndJoin() + } + } } @Test From fb91fd659113b055dede2c7bf354767b7f4a275f Mon Sep 17 00:00:00 2001 From: olme04 Date: Wed, 30 Mar 2022 12:12:40 +0300 Subject: [PATCH 2/9] implement JVM server CIO TLS support via SSLEngine TODO: test failed - HttpServerCommonTestSuite.testProxyHeaders --- ktor-server/ktor-server-cio/build.gradle.kts | 6 +- .../ktor/server/cio/HttpServerSettingsJvm.kt | 80 +++++++++++++++++++ .../tests/server/cio/CIOClientCertTest.kt | 10 +-- .../ktor/tests/server/cio/CIOEngineTestJvm.kt | 8 +- .../ktor/server/cio/CIOApplicationEngine.kt | 17 ++-- .../src/io/ktor/server/cio/HttpServer.kt | 5 +- .../io/ktor/server/cio/backend/HttpServer.kt | 2 +- .../io/ktor/tests/server/cio/CIOEngineTest.kt | 3 +- .../ktor/server/cio/HttpServerSettingsNix.kt | 16 ++++ 9 files changed, 124 insertions(+), 23 deletions(-) create mode 100644 ktor-server/ktor-server-cio/jvm/src/io/ktor/server/cio/HttpServerSettingsJvm.kt create mode 100644 ktor-server/ktor-server-cio/nix/src/io/ktor/server/cio/HttpServerSettingsNix.kt diff --git a/ktor-server/ktor-server-cio/build.gradle.kts b/ktor-server/ktor-server-cio/build.gradle.kts index 72c9a1813d2..856bb29fd6b 100644 --- a/ktor-server/ktor-server-cio/build.gradle.kts +++ b/ktor-server/ktor-server-cio/build.gradle.kts @@ -5,10 +5,14 @@ kotlin.sourceSets { dependencies { api(project(":ktor-server:ktor-server-host-common")) api(project(":ktor-http:ktor-http-cio")) - api(project(":ktor-shared:ktor-websockets")) api(project(":ktor-network")) } } + jvmMain { + dependencies { + api(project(":ktor-network:ktor-network-tls")) + } + } val jvmAndNixTest by getting { dependencies { api(project(":ktor-server:ktor-server-core")) diff --git a/ktor-server/ktor-server-cio/jvm/src/io/ktor/server/cio/HttpServerSettingsJvm.kt b/ktor-server/ktor-server-cio/jvm/src/io/ktor/server/cio/HttpServerSettingsJvm.kt new file mode 100644 index 00000000000..c0ba5146633 --- /dev/null +++ b/ktor-server/ktor-server-cio/jvm/src/io/ktor/server/cio/HttpServerSettingsJvm.kt @@ -0,0 +1,80 @@ +/* + * Copyright 2014-2022 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package io.ktor.server.cio + +import io.ktor.network.sockets.* +import io.ktor.network.tls.* +import io.ktor.server.engine.* +import io.ktor.util.* +import java.io.* +import java.security.* +import javax.net.ssl.* +import kotlin.coroutines.* + +@OptIn(InternalAPI::class) +internal actual fun HttpServerSettings( + connectionIdleTimeoutSeconds: Long, + connectorConfig: EngineConnectorConfig +): HttpServerSettings { + val interceptor: (Socket, CoroutineContext) -> Socket = when (connectorConfig) { + is EngineSSLConnectorConfig -> { + val sslContext = SSLContext.getInstance("TLS").apply { + init( + connectorConfig.keyManagerFactory().keyManagers, + connectorConfig.trustManagerFactory()?.trustManagers, + null + ) + }; + + { socket, context -> + val engine = sslContext.createSSLEngine().apply { + useClientMode = false + if (connectorConfig.hasTrustStore()) { + needClientAuth = true + } + } + + socket.tls(context, engine) + } + } + else -> { socket, _ -> socket } + } + + return HttpServerSettings( + host = connectorConfig.host, + port = connectorConfig.port, + connectionIdleTimeoutSeconds = connectionIdleTimeoutSeconds, + interceptor = interceptor + ) +} + +private fun EngineSSLConnectorConfig.hasTrustStore() = trustStore != null || trustStorePath != null + +private fun EngineSSLConnectorConfig.trustManagerFactory(): TrustManagerFactory? { + val trustStore = trustStore ?: trustStorePath?.let { file -> + FileInputStream(file).use { fis -> + KeyStore.getInstance("JKS").also { it.load(fis, null) } + } + } + return trustStore?.let { store -> + TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()).also { it.init(store) } + } +} + +private fun EngineSSLConnectorConfig.keyManagerFactory(): KeyManagerFactory { + val keyStore = keyStorePath?.let { file -> + FileInputStream(file).use { fis -> + val password = keyStorePassword() + val instance = KeyStore.getInstance("JKS").also { it.load(fis, password) } + password.fill('\u0000') + instance + } + } ?: keyStore + val kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()) + val password = privateKeyPassword() + kmf.init(keyStore, password) + password.fill('\u0000') + return kmf +} diff --git a/ktor-server/ktor-server-cio/jvm/test/io/ktor/tests/server/cio/CIOClientCertTest.kt b/ktor-server/ktor-server-cio/jvm/test/io/ktor/tests/server/cio/CIOClientCertTest.kt index 3f8a808149d..772c69eb833 100644 --- a/ktor-server/ktor-server-cio/jvm/test/io/ktor/tests/server/cio/CIOClientCertTest.kt +++ b/ktor-server/ktor-server-cio/jvm/test/io/ktor/tests/server/cio/CIOClientCertTest.kt @@ -6,13 +6,5 @@ package io.ktor.tests.server.cio import io.ktor.server.cio.* import io.ktor.server.testing.suites.* -import kotlin.test.* -class CIOClientCertTest : ClientCertTestSuite(CIO) { - @Test - public override fun `Server requesting Client Certificate from CIO Client`() { - assertFailsWith { - super.`Server requesting Client Certificate from CIO Client`() - } - } -} +class CIOClientCertTest : ClientCertTestSuite(CIO) diff --git a/ktor-server/ktor-server-cio/jvm/test/io/ktor/tests/server/cio/CIOEngineTestJvm.kt b/ktor-server/ktor-server-cio/jvm/test/io/ktor/tests/server/cio/CIOEngineTestJvm.kt index 3c9fbf48374..99db58bc80b 100644 --- a/ktor-server/ktor-server-cio/jvm/test/io/ktor/tests/server/cio/CIOEngineTestJvm.kt +++ b/ktor-server/ktor-server-cio/jvm/test/io/ktor/tests/server/cio/CIOEngineTestJvm.kt @@ -11,21 +11,21 @@ import kotlin.test.* class CIOCompressionTest : CompressionTestSuite(CIO) { init { enableHttp2 = false - enableSsl = false + enableSsl = true } } class CIOContentTest : ContentTestSuite(CIO) { init { enableHttp2 = false - enableSsl = false + enableSsl = true } } class CIOHttpServerJvmTest : HttpServerJvmTestSuite(CIO) { init { enableHttp2 = false - enableSsl = false + enableSsl = true } @Ignore @@ -40,7 +40,7 @@ class CIOHttpServerJvmTest : HttpServerJvmTestSuite(CIO) { init { enableHttp2 = false - enableSsl = false + enableSsl = true } } diff --git a/ktor-server/ktor-server-cio/jvmAndNix/src/io/ktor/server/cio/CIOApplicationEngine.kt b/ktor-server/ktor-server-cio/jvmAndNix/src/io/ktor/server/cio/CIOApplicationEngine.kt index d0bce82cdd7..0faf95c31cb 100644 --- a/ktor-server/ktor-server-cio/jvmAndNix/src/io/ktor/server/cio/CIOApplicationEngine.kt +++ b/ktor-server/ktor-server-cio/jvmAndNix/src/io/ktor/server/cio/CIOApplicationEngine.kt @@ -9,6 +9,7 @@ import io.ktor.server.application.* import io.ktor.server.cio.backend.* import io.ktor.server.cio.internal.* import io.ktor.server.engine.* +import io.ktor.util.* import io.ktor.util.pipeline.* import kotlinx.atomicfu.* import kotlinx.coroutines.* @@ -95,11 +96,10 @@ public class CIOApplicationEngine( } } - private fun CoroutineScope.startConnector(host: String, port: Int): HttpServer { + private fun CoroutineScope.startConnector(connectorConfig: EngineConnectorConfig): HttpServer { val settings = HttpServerSettings( - host = host, - port = port, - connectionIdleTimeoutSeconds = configuration.connectionIdleTimeoutSeconds.toLong() + connectionIdleTimeoutSeconds = configuration.connectionIdleTimeoutSeconds.toLong(), + connectorConfig = connectorConfig, ) return httpServer(settings) { request -> @@ -145,7 +145,7 @@ public class CIOApplicationEngine( try { environment.connectors.forEach { connectorSpec -> - if (connectorSpec.type == ConnectorType.HTTPS) { + if (connectorSpec.type == ConnectorType.HTTPS && !PlatformUtils.IS_JVM) { throw UnsupportedOperationException( "CIO Engine does not currently support HTTPS. Please " + "consider using a different engine if you require HTTPS" @@ -158,7 +158,7 @@ public class CIOApplicationEngine( } val connectorsAndServers = environment.connectors.map { connectorSpec -> - connectorSpec to startConnector(connectorSpec.host, connectorSpec.port) + connectorSpec to startConnector(connectorSpec) } connectors.addAll(connectorsAndServers.map { it.second }) @@ -185,3 +185,8 @@ public class CIOApplicationEngine( } } } + +internal expect fun HttpServerSettings( + connectionIdleTimeoutSeconds: Long, + connectorConfig: EngineConnectorConfig +): HttpServerSettings diff --git a/ktor-server/ktor-server-cio/jvmAndNix/src/io/ktor/server/cio/HttpServer.kt b/ktor-server/ktor-server-cio/jvmAndNix/src/io/ktor/server/cio/HttpServer.kt index d6ce0e20a90..67c59424e20 100644 --- a/ktor-server/ktor-server-cio/jvmAndNix/src/io/ktor/server/cio/HttpServer.kt +++ b/ktor-server/ktor-server-cio/jvmAndNix/src/io/ktor/server/cio/HttpServer.kt @@ -9,6 +9,7 @@ import io.ktor.network.sockets.* import io.ktor.server.cio.backend.* import io.ktor.utils.io.* import kotlinx.coroutines.* +import kotlin.coroutines.* /** * Represents a server instance @@ -27,11 +28,13 @@ public class HttpServer( * @property host to listen to * @property port to listen to * @property connectionIdleTimeoutSeconds time to live for IDLE connections + * @property interceptor a server socket interceptor, f.e. for handling TLS */ public data class HttpServerSettings( val host: String = "0.0.0.0", val port: Int = 8080, - val connectionIdleTimeoutSeconds: Long = 45 + val connectionIdleTimeoutSeconds: Long = 45, + val interceptor: (socket: Socket, context: CoroutineContext) -> Socket = { socket, _ -> socket } ) /** diff --git a/ktor-server/ktor-server-cio/jvmAndNix/src/io/ktor/server/cio/backend/HttpServer.kt b/ktor-server/ktor-server-cio/jvmAndNix/src/io/ktor/server/cio/backend/HttpServer.kt index f31c9501683..69b667cf1fc 100644 --- a/ktor-server/ktor-server-cio/jvmAndNix/src/io/ktor/server/cio/backend/HttpServer.kt +++ b/ktor-server/ktor-server-cio/jvmAndNix/src/io/ktor/server/cio/backend/HttpServer.kt @@ -60,7 +60,7 @@ public fun CoroutineScope.httpServer( try { while (true) { - val client: Socket = server.accept() + val client: Socket = settings.interceptor(server.accept(), connectionScope.coroutineContext) val connection = ServerIncomingConnection( client.openReadChannel(), diff --git a/ktor-server/ktor-server-cio/jvmAndNix/test/io/ktor/tests/server/cio/CIOEngineTest.kt b/ktor-server/ktor-server-cio/jvmAndNix/test/io/ktor/tests/server/cio/CIOEngineTest.kt index 2b19250cba8..0964d21032b 100644 --- a/ktor-server/ktor-server-cio/jvmAndNix/test/io/ktor/tests/server/cio/CIOEngineTest.kt +++ b/ktor-server/ktor-server-cio/jvmAndNix/test/io/ktor/tests/server/cio/CIOEngineTest.kt @@ -13,13 +13,14 @@ import io.ktor.server.cio.* import io.ktor.server.response.* import io.ktor.server.routing.* import io.ktor.server.testing.suites.* +import io.ktor.util.* import io.ktor.utils.io.* import kotlin.test.* class CIOHttpServerTest : HttpServerCommonTestSuite(CIO) { init { enableHttp2 = false - enableSsl = false + enableSsl = PlatformUtils.IS_JVM } @Test diff --git a/ktor-server/ktor-server-cio/nix/src/io/ktor/server/cio/HttpServerSettingsNix.kt b/ktor-server/ktor-server-cio/nix/src/io/ktor/server/cio/HttpServerSettingsNix.kt new file mode 100644 index 00000000000..ff6666ea207 --- /dev/null +++ b/ktor-server/ktor-server-cio/nix/src/io/ktor/server/cio/HttpServerSettingsNix.kt @@ -0,0 +1,16 @@ +/* + * Copyright 2014-2022 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package io.ktor.server.cio + +import io.ktor.server.engine.* + +internal actual fun HttpServerSettings( + connectionIdleTimeoutSeconds: Long, + connectorConfig: EngineConnectorConfig +): HttpServerSettings = HttpServerSettings( + host = connectorConfig.host, + port = connectorConfig.port, + connectionIdleTimeoutSeconds = connectionIdleTimeoutSeconds +) From b6a2f3c3343ea1534be56f9b8af2a5a4d631cc27 Mon Sep 17 00:00:00 2001 From: olme04 Date: Wed, 30 Mar 2022 12:13:28 +0300 Subject: [PATCH 3/9] add apiDump --- ktor-network/ktor-network-tls/api/ktor-network-tls.api | 1 + ktor-server/ktor-server-cio/api/ktor-server-cio.api | 10 ++++++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/ktor-network/ktor-network-tls/api/ktor-network-tls.api b/ktor-network/ktor-network-tls/api/ktor-network-tls.api index fcf0749c678..108652ac4c8 100644 --- a/ktor-network/ktor-network-tls/api/ktor-network-tls.api +++ b/ktor-network/ktor-network-tls/api/ktor-network-tls.api @@ -240,6 +240,7 @@ public final class io/ktor/network/tls/TLSHandshakeType$Companion { public final class io/ktor/network/tls/TLSKt { public static final fun tls (Lio/ktor/network/sockets/Socket;Lkotlin/coroutines/CoroutineContext;Lio/ktor/network/tls/TLSConfig;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final fun tls (Lio/ktor/network/sockets/Socket;Lkotlin/coroutines/CoroutineContext;Ljavax/net/ssl/SSLEngine;)Lio/ktor/network/sockets/Socket; public static final fun tls (Lio/ktor/network/sockets/Socket;Lkotlin/coroutines/CoroutineContext;Ljavax/net/ssl/X509TrustManager;Ljava/lang/String;Ljava/util/List;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final fun tls (Lio/ktor/network/sockets/Socket;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun tls$default (Lio/ktor/network/sockets/Socket;Lkotlin/coroutines/CoroutineContext;Ljavax/net/ssl/X509TrustManager;Ljava/lang/String;Ljava/util/List;Ljava/lang/String;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; diff --git a/ktor-server/ktor-server-cio/api/ktor-server-cio.api b/ktor-server/ktor-server-cio/api/ktor-server-cio.api index bbd4849e7a0..3ace6a9508a 100644 --- a/ktor-server/ktor-server-cio/api/ktor-server-cio.api +++ b/ktor-server/ktor-server-cio/api/ktor-server-cio.api @@ -34,16 +34,18 @@ public final class io/ktor/server/cio/HttpServerKt { public final class io/ktor/server/cio/HttpServerSettings { public fun ()V - public fun (Ljava/lang/String;IJ)V - public synthetic fun (Ljava/lang/String;IJILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Ljava/lang/String;IJLkotlin/jvm/functions/Function2;)V + public synthetic fun (Ljava/lang/String;IJLkotlin/jvm/functions/Function2;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Ljava/lang/String; public final fun component2 ()I public final fun component3 ()J - public final fun copy (Ljava/lang/String;IJ)Lio/ktor/server/cio/HttpServerSettings; - public static synthetic fun copy$default (Lio/ktor/server/cio/HttpServerSettings;Ljava/lang/String;IJILjava/lang/Object;)Lio/ktor/server/cio/HttpServerSettings; + public final fun component4 ()Lkotlin/jvm/functions/Function2; + public final fun copy (Ljava/lang/String;IJLkotlin/jvm/functions/Function2;)Lio/ktor/server/cio/HttpServerSettings; + public static synthetic fun copy$default (Lio/ktor/server/cio/HttpServerSettings;Ljava/lang/String;IJLkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lio/ktor/server/cio/HttpServerSettings; public fun equals (Ljava/lang/Object;)Z public final fun getConnectionIdleTimeoutSeconds ()J public final fun getHost ()Ljava/lang/String; + public final fun getInterceptor ()Lkotlin/jvm/functions/Function2; public final fun getPort ()I public fun hashCode ()I public fun toString ()Ljava/lang/String; From a6c32fe252c964e16585c9e877cbdcf986168727 Mon Sep 17 00:00:00 2001 From: olme04 Date: Wed, 30 Mar 2022 12:14:34 +0300 Subject: [PATCH 4/9] fix formatting --- .../network/tls/SSLEngineBufferAllocator.kt | 1 - .../io/ktor/network/tls/SSLEngineSocket.kt | 45 +++++++++---------- .../io/ktor/network/tls/SSLEngineUnwrapper.kt | 42 ++++++++--------- .../io/ktor/network/tls/SSLEngineWrapper.kt | 18 ++++---- .../ktor/network/tls/tests/ConnectionTest.kt | 2 +- 5 files changed, 53 insertions(+), 55 deletions(-) diff --git a/ktor-network/ktor-network-tls/jvm/src/io/ktor/network/tls/SSLEngineBufferAllocator.kt b/ktor-network/ktor-network-tls/jvm/src/io/ktor/network/tls/SSLEngineBufferAllocator.kt index 78e7efbeab6..fe0cda426d0 100644 --- a/ktor-network/ktor-network-tls/jvm/src/io/ktor/network/tls/SSLEngineBufferAllocator.kt +++ b/ktor-network/ktor-network-tls/jvm/src/io/ktor/network/tls/SSLEngineBufferAllocator.kt @@ -59,5 +59,4 @@ internal class SSLEngineBufferAllocator(private val engine: SSLEngine) { new.put(buffer) return new } - } diff --git a/ktor-network/ktor-network-tls/jvm/src/io/ktor/network/tls/SSLEngineSocket.kt b/ktor-network/ktor-network-tls/jvm/src/io/ktor/network/tls/SSLEngineSocket.kt index 1bfbd531bf2..d08dd836352 100644 --- a/ktor-network/ktor-network-tls/jvm/src/io/ktor/network/tls/SSLEngineSocket.kt +++ b/ktor-network/ktor-network-tls/jvm/src/io/ktor/network/tls/SSLEngineSocket.kt @@ -39,19 +39,19 @@ internal class SSLEngineSocket( var destination = bufferAllocator.allocateApplication(0) loop@ while (true) { destination.clear() - //println("[$debugString] READING: readAndUnwrap.START") - //println("[$debugString] READING_BEFORE_UNWRAP: $destination") + // println("[$debugString] READING: readAndUnwrap.START") + // println("[$debugString] READING_BEFORE_UNWRAP: $destination") val result = unwrapper.readAndUnwrap(destination) { destination = it } ?: break@loop - //println("[$debugString] READING_AFTER_UNWRAP: $destination") - //println("[$debugString] READING: readAndUnwrap.STOP") + // println("[$debugString] READING_AFTER_UNWRAP: $destination") + // println("[$debugString] READING: readAndUnwrap.STOP") destination.flip() - //println("[$debugString] READING_WRITE_BEFORE: $destination") + // println("[$debugString] READING_WRITE_BEFORE: $destination") if (destination.remaining() > 0) { this.channel.writeFully(destination) this.channel.flush() } - //println("[$debugString] READING_WRITE_AFTER: $destination") + // println("[$debugString] READING_WRITE_AFTER: $destination") handleResult(result) if (result.status == SSLEngineResult.Status.CLOSED) break@loop @@ -60,7 +60,7 @@ internal class SSLEngineSocket( error = cause throw cause } finally { - //println(error) + // println(error) unwrapper.cancel(error) engine.closeOutbound() doClose(error) @@ -75,17 +75,17 @@ internal class SSLEngineSocket( val source = bufferAllocator.allocateApplication(0) loop@ while (true) { source.clear() - //println("[$debugString] WRITING_READ_BEFORE: $source") + // println("[$debugString] WRITING_READ_BEFORE: $source") if (this.channel.readAvailable(source) == -1) break@loop - //println("[$debugString] WRITING_READ_AFTER: $source") + // println("[$debugString] WRITING_READ_AFTER: $source") source.flip() - //println("[$debugString] WRITING_BEFORE_SOURCE: $source") + // println("[$debugString] WRITING_BEFORE_SOURCE: $source") while (source.remaining() > 0) { - //println("[$debugString] WRITING: wrapAndWrite.START") - //println("[$debugString] WRITING_BEFORE_WRAP: $source") + // println("[$debugString] WRITING: wrapAndWrite.START") + // println("[$debugString] WRITING_BEFORE_WRAP: $source") val result = wrapper.wrapAndWrite(source) - //println("[$debugString] WRITING_AFTER_WRAP: $source") - //println("[$debugString] WRITING: wrapAndWrite.STOP") + // println("[$debugString] WRITING_AFTER_WRAP: $source") + // println("[$debugString] WRITING: wrapAndWrite.STOP") handleResult(result) if (result.status == SSLEngineResult.Status.CLOSED) break@loop @@ -95,13 +95,13 @@ internal class SSLEngineSocket( error = cause throw cause } finally { - //println(error) - engine.closeInbound() //TODO: when this should be called??? + // println(error) + engine.closeInbound() // TODO: when this should be called??? doClose(error) } } - //TODO proper close implementation? + // TODO proper close implementation? override fun close() { engine.closeOutbound() if (isActive) launch { @@ -133,7 +133,7 @@ internal class SSLEngineSocket( var temp = bufferAllocator.allocateApplication(0) var status = initialStatus while (true) { - //println("[$debugString] HANDSHAKE: $status") + // println("[$debugString] HANDSHAKE: $status") when (status) { SSLEngineResult.HandshakeStatus.NEED_TASK -> { coroutineScope { @@ -147,15 +147,15 @@ internal class SSLEngineSocket( SSLEngineResult.HandshakeStatus.NEED_WRAP -> { temp.clear() temp.flip() - //println("[$debugString] HANDSHAKE: wrapAndWrite.START") + // println("[$debugString] HANDSHAKE: wrapAndWrite.START") status = wrapper.wrapAndWrite(temp).handshakeStatus - //println("[$debugString] HANDSHAKE: wrapAndWrite.STOP") + // println("[$debugString] HANDSHAKE: wrapAndWrite.STOP") } SSLEngineResult.HandshakeStatus.NEED_UNWRAP -> { temp.clear() - //println("[$debugString] HANDSHAKE: readAndUnwrap.START") + // println("[$debugString] HANDSHAKE: readAndUnwrap.START") status = unwrapper.readAndUnwrap(temp) { temp = it }?.handshakeStatus ?: break - //println("[$debugString] HANDSHAKE: readAndUnwrap.STOP") + // println("[$debugString] HANDSHAKE: readAndUnwrap.STOP") } else -> break } @@ -164,5 +164,4 @@ internal class SSLEngineSocket( private val SSLEngineResult.HandshakeStatus.needHandshake: Boolean get() = this != SSLEngineResult.HandshakeStatus.FINISHED && this != SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING - } diff --git a/ktor-network/ktor-network-tls/jvm/src/io/ktor/network/tls/SSLEngineUnwrapper.kt b/ktor-network/ktor-network-tls/jvm/src/io/ktor/network/tls/SSLEngineUnwrapper.kt index e68609a69e1..8211413a73a 100644 --- a/ktor-network/ktor-network-tls/jvm/src/io/ktor/network/tls/SSLEngineUnwrapper.kt +++ b/ktor-network/ktor-network-tls/jvm/src/io/ktor/network/tls/SSLEngineUnwrapper.kt @@ -21,7 +21,7 @@ internal class SSLEngineUnwrapper( private var unwrapSource = bufferAllocator.allocatePacket(0) private var unwrapRemaining = 0 - //TODO revisit + // TODO revisit private var unwrapResultCont: CancellableContinuation? = null fun cancel(cause: Throwable?) { @@ -56,30 +56,30 @@ internal class SSLEngineUnwrapper( } } - //println("[$debugString] UNWRAP_INIT[DST]: $unwrapDestination") - //println("[$debugString] UNWRAP_INIT[SRC]: $unwrapSource") + // println("[$debugString] UNWRAP_INIT[DST]: $unwrapDestination") + // println("[$debugString] UNWRAP_INIT[SRC]: $unwrapSource") while (true) { - //println("[$debugString] UNWRAP_BEFORE[DST]: $unwrapDestination") - //println("[$debugString] UNWRAP_BEFORE[SRC]: $unwrapSource") + // println("[$debugString] UNWRAP_BEFORE[DST]: $unwrapDestination") + // println("[$debugString] UNWRAP_BEFORE[SRC]: $unwrapSource") result = engine.unwrap(unwrapSource, unwrapDestination) - //println("[$debugString] UNWRAP_RESULT: $result") - //println("[$debugString] UNWRAP_AFTER[DST]: $unwrapDestination") - //println("[$debugString] UNWRAP_AFTER[SRC]: $unwrapSource") + // println("[$debugString] UNWRAP_RESULT: $result") + // println("[$debugString] UNWRAP_AFTER[DST]: $unwrapDestination") + // println("[$debugString] UNWRAP_AFTER[SRC]: $unwrapSource") when (result.status!!) { SSLEngineResult.Status.BUFFER_UNDERFLOW -> { - //println("[$debugString] UNWRAP_UNDERFLOW_BEFORE[SRC]: $unwrapSource") + // println("[$debugString] UNWRAP_UNDERFLOW_BEFORE[SRC]: $unwrapSource") if (unwrapSource.limit() == unwrapSource.capacity()) { - //println("[$debugString] UNWRAP_UNDERFLOW_1") - //buffer is too small to read all needed data + // println("[$debugString] UNWRAP_UNDERFLOW_1") + // buffer is too small to read all needed data unwrapSource = bufferAllocator.reallocatePacket(unwrapSource, flip = false) } else { - //println("[$debugString] UNWRAP_UNDERFLOW_2") - //not all data received + // println("[$debugString] UNWRAP_UNDERFLOW_2") + // not all data received unwrapSource.position(unwrapSource.limit()) unwrapSource.limit(unwrapSource.capacity()) } - //println("[$debugString] UNWRAP_UNDERFLOW_AFTER[SRC]: $unwrapSource") + // println("[$debugString] UNWRAP_UNDERFLOW_AFTER[SRC]: $unwrapSource") if (!readData()) { synchronized(this) { unwrapResultCont?.resume(null) @@ -89,15 +89,15 @@ internal class SSLEngineUnwrapper( } } SSLEngineResult.Status.BUFFER_OVERFLOW -> { - //println("[$debugString] UNWRAP_OVERFLOW_BEFORE[DST]: $unwrapDestination") + // println("[$debugString] UNWRAP_OVERFLOW_BEFORE[DST]: $unwrapDestination") unwrapDestination = bufferAllocator.reallocateApplication(unwrapDestination, flip = true) - //println("[$debugString] UNWRAP_OVERFLOW_AFTER[DST]: $unwrapDestination") + // println("[$debugString] UNWRAP_OVERFLOW_AFTER[DST]: $unwrapDestination") } else -> break } } - //println("[$debugString] UNWRAP_FINAL[DST]: $unwrapDestination") - //println("[$debugString] UNWRAP_FINAL[SRC]: $unwrapSource") + // println("[$debugString] UNWRAP_FINAL[DST]: $unwrapDestination") + // println("[$debugString] UNWRAP_FINAL[SRC]: $unwrapSource") unwrapRemaining = unwrapSource.remaining() if (unwrapDestination !== initialUnwrapDestination) updateUnwrapDestination(unwrapDestination) synchronized(this) { @@ -117,12 +117,12 @@ internal class SSLEngineUnwrapper( } private suspend fun readData(): Boolean { - //println("[$debugString] UNWRAP_READ_BEFORE[SRC]: $unwrapSource") + // println("[$debugString] UNWRAP_READ_BEFORE[SRC]: $unwrapSource") val read = input.readAvailable(unwrapSource) unwrapSource.flip() - //println("[$debugString] UNWRAP_READ_AFTER[SRC]: $unwrapSource") + // println("[$debugString] UNWRAP_READ_AFTER[SRC]: $unwrapSource") - //println("[$debugString] UNWRAP_READ_COMPLETE[SRC]: $unwrapSource") + // println("[$debugString] UNWRAP_READ_COMPLETE[SRC]: $unwrapSource") return read != -1 } } diff --git a/ktor-network/ktor-network-tls/jvm/src/io/ktor/network/tls/SSLEngineWrapper.kt b/ktor-network/ktor-network-tls/jvm/src/io/ktor/network/tls/SSLEngineWrapper.kt index 2173af6940d..f9896ce64e6 100644 --- a/ktor-network/ktor-network-tls/jvm/src/io/ktor/network/tls/SSLEngineWrapper.kt +++ b/ktor-network/ktor-network-tls/jvm/src/io/ktor/network/tls/SSLEngineWrapper.kt @@ -27,28 +27,28 @@ internal class SSLEngineWrapper( var result: SSLEngineResult wrapDestination.clear() while (true) { - //println("[$debugString] WRAP_BEFORE: $wrapDestination") + // println("[$debugString] WRAP_BEFORE: $wrapDestination") result = engine.wrap(wrapSource, wrapDestination) - //println("[$debugString] WRAP_RESULT: $result") - //println("[$debugString] WRAP_AFTER: $wrapDestination") + // println("[$debugString] WRAP_RESULT: $result") + // println("[$debugString] WRAP_AFTER: $wrapDestination") if (result.status != SSLEngineResult.Status.BUFFER_OVERFLOW) break - //println("[$debugString] WRAP_OVERFLOW: $wrapDestination") + // println("[$debugString] WRAP_OVERFLOW: $wrapDestination") wrapDestination = bufferAllocator.reallocatePacket(wrapDestination, flip = true) - //println("[$debugString] WRAP_OVERFLOW_REALLOCATE: $wrapDestination") + // println("[$debugString] WRAP_OVERFLOW_REALLOCATE: $wrapDestination") } - //println("[$debugString] WRAP_WRITE_BEFORE: $wrapDestination") + // println("[$debugString] WRAP_WRITE_BEFORE: $wrapDestination") if (result.bytesProduced() > 0) { wrapDestination.flip() - //println("[$debugString] WRAP_WRITE: $wrapDestination") + // println("[$debugString] WRAP_WRITE: $wrapDestination") output.writeFully(wrapDestination) output.flush() } - //println("[$debugString] WRAP_WRITE_AFTER: $wrapDestination") + // println("[$debugString] WRAP_WRITE_AFTER: $wrapDestination") return result } suspend fun close(cause: Throwable?): SSLEngineResult = wrapLock.withLock { - //println("[$debugString] CLOSE: $cause") + // println("[$debugString] CLOSE: $cause") val temp = bufferAllocator.allocateApplication(0) var result: SSLEngineResult do { diff --git a/ktor-network/ktor-network-tls/jvm/test/io/ktor/network/tls/tests/ConnectionTest.kt b/ktor-network/ktor-network-tls/jvm/test/io/ktor/network/tls/tests/ConnectionTest.kt index c49e6f5252a..6466770aeb7 100644 --- a/ktor-network/ktor-network-tls/jvm/test/io/ktor/network/tls/tests/ConnectionTest.kt +++ b/ktor-network/ktor-network-tls/jvm/test/io/ktor/network/tls/tests/ConnectionTest.kt @@ -121,7 +121,7 @@ class ConnectionTest { writer.writeStringUtf8("$line\r\n") writer.flush() } - delay(2000) //await reading from client socket + delay(2000) // await reading from client socket } } From b8266d7b395504a3eeb1507718caa1717266dbdf Mon Sep 17 00:00:00 2001 From: olme04 Date: Wed, 30 Mar 2022 12:34:44 +0300 Subject: [PATCH 5/9] remove printlns and add comment --- .../io/ktor/network/tls/SSLEngineSocket.kt | 32 ++----------- .../io/ktor/network/tls/SSLEngineUnwrapper.kt | 47 +++++-------------- .../io/ktor/network/tls/SSLEngineWrapper.kt | 12 +---- 3 files changed, 19 insertions(+), 72 deletions(-) diff --git a/ktor-network/ktor-network-tls/jvm/src/io/ktor/network/tls/SSLEngineSocket.kt b/ktor-network/ktor-network-tls/jvm/src/io/ktor/network/tls/SSLEngineSocket.kt index d08dd836352..f85171bc3f2 100644 --- a/ktor-network/ktor-network-tls/jvm/src/io/ktor/network/tls/SSLEngineSocket.kt +++ b/ktor-network/ktor-network-tls/jvm/src/io/ktor/network/tls/SSLEngineSocket.kt @@ -23,14 +23,12 @@ internal class SSLEngineSocket( override val remoteAddress: SocketAddress get() = socket.remoteAddress override val localAddress: SocketAddress get() = socket.localAddress - private val debugString = coroutineContext[CoroutineName]?.name ?: "DEBUG" - private val lock = Mutex() private val closed = atomic(false) private val bufferAllocator = SSLEngineBufferAllocator(engine) - private val wrapper = SSLEngineWrapper(engine, bufferAllocator, connection.output, debugString) - private val unwrapper = SSLEngineUnwrapper(engine, bufferAllocator, connection.input, debugString) + private val wrapper = SSLEngineWrapper(engine, bufferAllocator, connection.output) + private val unwrapper = SSLEngineUnwrapper(engine, bufferAllocator, connection.input) override fun attachForReading(channel: ByteChannel): WriterJob = writer(CoroutineName("network-tls-input"), channel) { @@ -39,19 +37,13 @@ internal class SSLEngineSocket( var destination = bufferAllocator.allocateApplication(0) loop@ while (true) { destination.clear() - // println("[$debugString] READING: readAndUnwrap.START") - // println("[$debugString] READING_BEFORE_UNWRAP: $destination") val result = unwrapper.readAndUnwrap(destination) { destination = it } ?: break@loop - // println("[$debugString] READING_AFTER_UNWRAP: $destination") - // println("[$debugString] READING: readAndUnwrap.STOP") - destination.flip() - // println("[$debugString] READING_WRITE_BEFORE: $destination") + if (destination.remaining() > 0) { this.channel.writeFully(destination) this.channel.flush() } - // println("[$debugString] READING_WRITE_AFTER: $destination") handleResult(result) if (result.status == SSLEngineResult.Status.CLOSED) break@loop @@ -60,7 +52,6 @@ internal class SSLEngineSocket( error = cause throw cause } finally { - // println(error) unwrapper.cancel(error) engine.closeOutbound() doClose(error) @@ -75,17 +66,11 @@ internal class SSLEngineSocket( val source = bufferAllocator.allocateApplication(0) loop@ while (true) { source.clear() - // println("[$debugString] WRITING_READ_BEFORE: $source") if (this.channel.readAvailable(source) == -1) break@loop - // println("[$debugString] WRITING_READ_AFTER: $source") source.flip() - // println("[$debugString] WRITING_BEFORE_SOURCE: $source") + while (source.remaining() > 0) { - // println("[$debugString] WRITING: wrapAndWrite.START") - // println("[$debugString] WRITING_BEFORE_WRAP: $source") val result = wrapper.wrapAndWrite(source) - // println("[$debugString] WRITING_AFTER_WRAP: $source") - // println("[$debugString] WRITING: wrapAndWrite.STOP") handleResult(result) if (result.status == SSLEngineResult.Status.CLOSED) break@loop @@ -95,13 +80,11 @@ internal class SSLEngineSocket( error = cause throw cause } finally { - // println(error) - engine.closeInbound() // TODO: when this should be called??? + engine.closeInbound() doClose(error) } } - // TODO proper close implementation? override fun close() { engine.closeOutbound() if (isActive) launch { @@ -133,7 +116,6 @@ internal class SSLEngineSocket( var temp = bufferAllocator.allocateApplication(0) var status = initialStatus while (true) { - // println("[$debugString] HANDSHAKE: $status") when (status) { SSLEngineResult.HandshakeStatus.NEED_TASK -> { coroutineScope { @@ -147,15 +129,11 @@ internal class SSLEngineSocket( SSLEngineResult.HandshakeStatus.NEED_WRAP -> { temp.clear() temp.flip() - // println("[$debugString] HANDSHAKE: wrapAndWrite.START") status = wrapper.wrapAndWrite(temp).handshakeStatus - // println("[$debugString] HANDSHAKE: wrapAndWrite.STOP") } SSLEngineResult.HandshakeStatus.NEED_UNWRAP -> { temp.clear() - // println("[$debugString] HANDSHAKE: readAndUnwrap.START") status = unwrapper.readAndUnwrap(temp) { temp = it }?.handshakeStatus ?: break - // println("[$debugString] HANDSHAKE: readAndUnwrap.STOP") } else -> break } diff --git a/ktor-network/ktor-network-tls/jvm/src/io/ktor/network/tls/SSLEngineUnwrapper.kt b/ktor-network/ktor-network-tls/jvm/src/io/ktor/network/tls/SSLEngineUnwrapper.kt index 8211413a73a..486342a0355 100644 --- a/ktor-network/ktor-network-tls/jvm/src/io/ktor/network/tls/SSLEngineUnwrapper.kt +++ b/ktor-network/ktor-network-tls/jvm/src/io/ktor/network/tls/SSLEngineUnwrapper.kt @@ -14,14 +14,14 @@ import kotlin.coroutines.* internal class SSLEngineUnwrapper( private val engine: SSLEngine, private val bufferAllocator: SSLEngineBufferAllocator, - private val input: ByteReadChannel, - private val debugString: String + private val input: ByteReadChannel ) { private val unwrapLock = Mutex() private var unwrapSource = bufferAllocator.allocatePacket(0) private var unwrapRemaining = 0 - // TODO revisit + // continuation is needed when unwrap is requested both while reading more data and during handshake (started from writing data) + // in case unwrap is requested for reading more data DURING handshake, result should be handled by handshake loop private var unwrapResultCont: CancellableContinuation? = null fun cancel(cause: Throwable?) { @@ -48,62 +48,38 @@ internal class SSLEngineUnwrapper( } else { unwrapSource.clear() if (!readData()) { - synchronized(this) { - unwrapResultCont?.resume(null) - unwrapResultCont = null - } + resumeUnwrapContinuation(null) return null } } - // println("[$debugString] UNWRAP_INIT[DST]: $unwrapDestination") - // println("[$debugString] UNWRAP_INIT[SRC]: $unwrapSource") while (true) { - // println("[$debugString] UNWRAP_BEFORE[DST]: $unwrapDestination") - // println("[$debugString] UNWRAP_BEFORE[SRC]: $unwrapSource") result = engine.unwrap(unwrapSource, unwrapDestination) - // println("[$debugString] UNWRAP_RESULT: $result") - // println("[$debugString] UNWRAP_AFTER[DST]: $unwrapDestination") - // println("[$debugString] UNWRAP_AFTER[SRC]: $unwrapSource") when (result.status!!) { SSLEngineResult.Status.BUFFER_UNDERFLOW -> { - // println("[$debugString] UNWRAP_UNDERFLOW_BEFORE[SRC]: $unwrapSource") if (unwrapSource.limit() == unwrapSource.capacity()) { - // println("[$debugString] UNWRAP_UNDERFLOW_1") // buffer is too small to read all needed data unwrapSource = bufferAllocator.reallocatePacket(unwrapSource, flip = false) } else { - // println("[$debugString] UNWRAP_UNDERFLOW_2") // not all data received unwrapSource.position(unwrapSource.limit()) unwrapSource.limit(unwrapSource.capacity()) } - // println("[$debugString] UNWRAP_UNDERFLOW_AFTER[SRC]: $unwrapSource") if (!readData()) { - synchronized(this) { - unwrapResultCont?.resume(null) - unwrapResultCont = null - } + resumeUnwrapContinuation(null) return null } } SSLEngineResult.Status.BUFFER_OVERFLOW -> { - // println("[$debugString] UNWRAP_OVERFLOW_BEFORE[DST]: $unwrapDestination") unwrapDestination = bufferAllocator.reallocateApplication(unwrapDestination, flip = true) - // println("[$debugString] UNWRAP_OVERFLOW_AFTER[DST]: $unwrapDestination") } else -> break } } - // println("[$debugString] UNWRAP_FINAL[DST]: $unwrapDestination") - // println("[$debugString] UNWRAP_FINAL[SRC]: $unwrapSource") unwrapRemaining = unwrapSource.remaining() if (unwrapDestination !== initialUnwrapDestination) updateUnwrapDestination(unwrapDestination) - synchronized(this) { - unwrapResultCont?.resume(result) - unwrapResultCont = null - } + resumeUnwrapContinuation(result) return result } catch (cause: Throwable) { synchronized(this) { @@ -116,13 +92,16 @@ internal class SSLEngineUnwrapper( } } + private fun resumeUnwrapContinuation(result: SSLEngineResult?) { + synchronized(this) { + unwrapResultCont?.resume(result) + unwrapResultCont = null + } + } + private suspend fun readData(): Boolean { - // println("[$debugString] UNWRAP_READ_BEFORE[SRC]: $unwrapSource") val read = input.readAvailable(unwrapSource) unwrapSource.flip() - // println("[$debugString] UNWRAP_READ_AFTER[SRC]: $unwrapSource") - - // println("[$debugString] UNWRAP_READ_COMPLETE[SRC]: $unwrapSource") return read != -1 } } diff --git a/ktor-network/ktor-network-tls/jvm/src/io/ktor/network/tls/SSLEngineWrapper.kt b/ktor-network/ktor-network-tls/jvm/src/io/ktor/network/tls/SSLEngineWrapper.kt index f9896ce64e6..e5eff5d8cd2 100644 --- a/ktor-network/ktor-network-tls/jvm/src/io/ktor/network/tls/SSLEngineWrapper.kt +++ b/ktor-network/ktor-network-tls/jvm/src/io/ktor/network/tls/SSLEngineWrapper.kt @@ -12,8 +12,7 @@ import javax.net.ssl.* internal class SSLEngineWrapper( private val engine: SSLEngine, private val bufferAllocator: SSLEngineBufferAllocator, - private val output: ByteWriteChannel, - private val debugString: String + private val output: ByteWriteChannel ) { private val wrapLock = Mutex() private var wrapDestination = bufferAllocator.allocatePacket(0) @@ -27,28 +26,19 @@ internal class SSLEngineWrapper( var result: SSLEngineResult wrapDestination.clear() while (true) { - // println("[$debugString] WRAP_BEFORE: $wrapDestination") result = engine.wrap(wrapSource, wrapDestination) - // println("[$debugString] WRAP_RESULT: $result") - // println("[$debugString] WRAP_AFTER: $wrapDestination") if (result.status != SSLEngineResult.Status.BUFFER_OVERFLOW) break - // println("[$debugString] WRAP_OVERFLOW: $wrapDestination") wrapDestination = bufferAllocator.reallocatePacket(wrapDestination, flip = true) - // println("[$debugString] WRAP_OVERFLOW_REALLOCATE: $wrapDestination") } - // println("[$debugString] WRAP_WRITE_BEFORE: $wrapDestination") if (result.bytesProduced() > 0) { wrapDestination.flip() - // println("[$debugString] WRAP_WRITE: $wrapDestination") output.writeFully(wrapDestination) output.flush() } - // println("[$debugString] WRAP_WRITE_AFTER: $wrapDestination") return result } suspend fun close(cause: Throwable?): SSLEngineResult = wrapLock.withLock { - // println("[$debugString] CLOSE: $cause") val temp = bufferAllocator.allocateApplication(0) var result: SSLEngineResult do { From ade156a454b22771bff6aee2ab6b516b9c242ad8 Mon Sep 17 00:00:00 2001 From: olme04 Date: Fri, 8 Apr 2022 18:04:19 +0300 Subject: [PATCH 6/9] add TLS support to native linux via openssl --- .../ktor-network-tls/build.gradle.kts | 27 ++- .../network/tls/TLSConfigBuilderDarwin.kt | 50 +++++ .../io/ktor/network/tls/TLSConfigDarwin.kt | 17 ++ .../src/io/ktor/network/tls/TLSDarwin.kt | 15 ++ .../jvm/src/io/ktor/network/tls/TLS.kt | 42 ++--- .../ktor/network/tls/TLSClientSessionJvm.kt | 2 +- .../io/ktor/network/tls/TLSConfigBuilder.kt | 79 ++++++-- .../src/io/ktor/network/tls/TLSConfigJvm.kt | 18 +- .../jvm/test/io/ktor/network/tls/configure.kt | 12 ++ .../ktor/network/tls/tests/ConnectionTest.kt | 59 ++---- .../io/ktor/network/tls/TLSClientSession.kt | 17 -- .../src/io/ktor/network/tls/TLSCommon.kt | 44 ++--- .../src/io/ktor/network/tls/TLSConfig.kt | 14 +- .../network/tls/TLSConfigBuilderCommon.kt | 24 ++- .../jvmAndNix/test-resources/commonkey.p12 | Bin 0 -> 5669 bytes .../network/tls/TlsClientServerSocketTest.kt | 66 +++++++ .../ktor/network/tls/TlsClientSocketTest.kt | 38 ++++ .../linuxX64/interop/openssl.def | 5 + .../src/io/ktor/network/tls/BIOPipe.kt | 86 +++++++++ .../src/io/ktor/network/tls/OPENSSLInit.kt | 19 ++ .../src/io/ktor/network/tls/PKCS12.kt | 54 ++++++ .../src/io/ktor/network/tls/SSLError.kt | 30 +++ .../src/io/ktor/network/tls/SSLOperations.kt | 102 ++++++++++ .../src/io/ktor/network/tls/SSLSocket.kt | 175 ++++++++++++++++++ .../network/tls/TLSConfigBuilderLinuxX64.kt | 50 +++++ .../io/ktor/network/tls/TLSConfigLinuxX64.kt | 70 +++++++ .../src/io/ktor/network/tls/TlsLinuxX64.kt | 29 +++ .../io/ktor/network/tls/PKCS12Certificate.kt | 10 + .../network/tls/TLSClientSessionNative.kt | 19 -- .../network/tls/TLSConfigBuilderNative.kt | 27 --- .../io/ktor/network/tls/TLSConfigNative.kt | 8 - .../nix/src/io/ktor/network/tls/TLSNative.kt | 34 ---- .../nix/test/io/ktor/network/tls/configure.kt | 21 +++ .../ktor/server/cio/HttpServerSettingsJvm.kt | 51 ++--- .../src/io/ktor/server/cio/HttpServer.kt | 3 +- 35 files changed, 1047 insertions(+), 270 deletions(-) create mode 100644 ktor-network/ktor-network-tls/darwin/src/io/ktor/network/tls/TLSConfigBuilderDarwin.kt create mode 100644 ktor-network/ktor-network-tls/darwin/src/io/ktor/network/tls/TLSConfigDarwin.kt create mode 100644 ktor-network/ktor-network-tls/darwin/src/io/ktor/network/tls/TLSDarwin.kt create mode 100644 ktor-network/ktor-network-tls/jvm/test/io/ktor/network/tls/configure.kt delete mode 100644 ktor-network/ktor-network-tls/jvmAndNix/src/io/ktor/network/tls/TLSClientSession.kt create mode 100644 ktor-network/ktor-network-tls/jvmAndNix/test-resources/commonkey.p12 create mode 100644 ktor-network/ktor-network-tls/jvmAndNix/test/io/ktor/network/tls/TlsClientServerSocketTest.kt create mode 100644 ktor-network/ktor-network-tls/jvmAndNix/test/io/ktor/network/tls/TlsClientSocketTest.kt create mode 100644 ktor-network/ktor-network-tls/linuxX64/interop/openssl.def create mode 100644 ktor-network/ktor-network-tls/linuxX64/src/io/ktor/network/tls/BIOPipe.kt create mode 100644 ktor-network/ktor-network-tls/linuxX64/src/io/ktor/network/tls/OPENSSLInit.kt create mode 100644 ktor-network/ktor-network-tls/linuxX64/src/io/ktor/network/tls/PKCS12.kt create mode 100644 ktor-network/ktor-network-tls/linuxX64/src/io/ktor/network/tls/SSLError.kt create mode 100644 ktor-network/ktor-network-tls/linuxX64/src/io/ktor/network/tls/SSLOperations.kt create mode 100644 ktor-network/ktor-network-tls/linuxX64/src/io/ktor/network/tls/SSLSocket.kt create mode 100644 ktor-network/ktor-network-tls/linuxX64/src/io/ktor/network/tls/TLSConfigBuilderLinuxX64.kt create mode 100644 ktor-network/ktor-network-tls/linuxX64/src/io/ktor/network/tls/TLSConfigLinuxX64.kt create mode 100644 ktor-network/ktor-network-tls/linuxX64/src/io/ktor/network/tls/TlsLinuxX64.kt create mode 100644 ktor-network/ktor-network-tls/nix/src/io/ktor/network/tls/PKCS12Certificate.kt delete mode 100644 ktor-network/ktor-network-tls/nix/src/io/ktor/network/tls/TLSClientSessionNative.kt delete mode 100644 ktor-network/ktor-network-tls/nix/src/io/ktor/network/tls/TLSConfigBuilderNative.kt delete mode 100644 ktor-network/ktor-network-tls/nix/src/io/ktor/network/tls/TLSConfigNative.kt delete mode 100644 ktor-network/ktor-network-tls/nix/src/io/ktor/network/tls/TLSNative.kt create mode 100644 ktor-network/ktor-network-tls/nix/test/io/ktor/network/tls/configure.kt diff --git a/ktor-network/ktor-network-tls/build.gradle.kts b/ktor-network/ktor-network-tls/build.gradle.kts index 2b02c50abba..9030fb5f268 100644 --- a/ktor-network/ktor-network-tls/build.gradle.kts +++ b/ktor-network/ktor-network-tls/build.gradle.kts @@ -1,14 +1,23 @@ -kotlin.sourceSets { - jvmAndNixMain { - dependencies { - api(project(":ktor-network")) - api(project(":ktor-utils")) +kotlin { + linuxX64 { + val main by compilations.getting { + val openssl by cinterops.creating { + defFile(project.file("linuxX64/interop/openssl.def")) + } } } - jvmTest { - dependencies { - api(project(":ktor-network:ktor-network-tls:ktor-network-tls-certificates")) - api(libs.netty.handler) + sourceSets { + jvmAndNixMain { + dependencies { + api(project(":ktor-network")) + api(project(":ktor-utils")) + } + } + jvmTest { + dependencies { + api(project(":ktor-network:ktor-network-tls:ktor-network-tls-certificates")) + api(libs.netty.handler) + } } } } diff --git a/ktor-network/ktor-network-tls/darwin/src/io/ktor/network/tls/TLSConfigBuilderDarwin.kt b/ktor-network/ktor-network-tls/darwin/src/io/ktor/network/tls/TLSConfigBuilderDarwin.kt new file mode 100644 index 00000000000..b57d452a8f5 --- /dev/null +++ b/ktor-network/ktor-network-tls/darwin/src/io/ktor/network/tls/TLSConfigBuilderDarwin.kt @@ -0,0 +1,50 @@ +/* + * Copyright 2014-2019 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package io.ktor.network.tls + +/** + * [TLSConfig] builder. + */ +public actual class TLSConfigBuilder actual constructor(private val isClient: Boolean) { + private var authenticationBuilder: TLSAuthenticationConfigBuilder? = null + + /** + * Custom server name for TLS server name extension. + * See also: https://en.wikipedia.org/wiki/Server_Name_Indication + */ + public actual var serverName: String? = null + + public actual fun authentication( + privateKeyPassword: () -> CharArray, + block: TLSAuthenticationConfigBuilder.() -> Unit + ) { + authenticationBuilder = TLSAuthenticationConfigBuilder(privateKeyPassword).apply(block) + } + + public actual fun takeFrom(other: TLSConfigBuilder) { + serverName = other.serverName + authenticationBuilder = other.authenticationBuilder + } + + /** + * Create [TLSConfig]. + */ + public actual fun build(): TLSConfig = TLSConfig(isClient, serverName, authenticationBuilder?.build()) +} + +public actual class TLSAuthenticationConfigBuilder actual constructor( + private val privateKeyPassword: () -> CharArray +) { + private var certificate: PKCS12Certificate? = null + + public actual fun pkcs12Certificate(certificatePath: String, certificatePassword: (() -> CharArray)?) { + certificate = PKCS12Certificate(certificatePath, certificatePassword) + } + + public actual fun build(): TLSAuthenticationConfig = TLSAuthenticationConfig( + certificate, + privateKeyPassword + ) +} diff --git a/ktor-network/ktor-network-tls/darwin/src/io/ktor/network/tls/TLSConfigDarwin.kt b/ktor-network/ktor-network-tls/darwin/src/io/ktor/network/tls/TLSConfigDarwin.kt new file mode 100644 index 00000000000..5846afce0a0 --- /dev/null +++ b/ktor-network/ktor-network-tls/darwin/src/io/ktor/network/tls/TLSConfigDarwin.kt @@ -0,0 +1,17 @@ +// ktlint-disable filename +/* + * Copyright 2014-2019 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package io.ktor.network.tls + +public actual class TLSConfig( + public actual val isClient: Boolean, + public actual val serverName: String?, + public actual val authentication: TLSAuthenticationConfig? +) + +public actual class TLSAuthenticationConfig( + public val certificate: PKCS12Certificate?, + public val privateKeyPassword: () -> CharArray +) diff --git a/ktor-network/ktor-network-tls/darwin/src/io/ktor/network/tls/TLSDarwin.kt b/ktor-network/ktor-network-tls/darwin/src/io/ktor/network/tls/TLSDarwin.kt new file mode 100644 index 00000000000..6e47e8c94fe --- /dev/null +++ b/ktor-network/ktor-network-tls/darwin/src/io/ktor/network/tls/TLSDarwin.kt @@ -0,0 +1,15 @@ +/* + * Copyright 2014-2022 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package io.ktor.network.tls + +import io.ktor.network.sockets.* +import kotlin.coroutines.* + +public actual suspend fun Connection.tls( + coroutineContext: CoroutineContext, + config: TLSConfig +): Socket { + error("TLS is not supported on Darwin platform.") +} diff --git a/ktor-network/ktor-network-tls/jvm/src/io/ktor/network/tls/TLS.kt b/ktor-network/ktor-network-tls/jvm/src/io/ktor/network/tls/TLS.kt index 8db26c85590..a0ec2710aea 100644 --- a/ktor-network/ktor-network-tls/jvm/src/io/ktor/network/tls/TLS.kt +++ b/ktor-network/ktor-network-tls/jvm/src/io/ktor/network/tls/TLS.kt @@ -13,21 +13,32 @@ import kotlin.coroutines.* /** * Make [Socket] connection secure with TLS using [TLSConfig]. */ -public actual suspend fun Socket.tls( +public actual suspend fun Connection.tls( coroutineContext: CoroutineContext, config: TLSConfig -): Socket { - val reader = openReadChannel() - val writer = openWriteChannel() - - return try { - openTLSSession(this, reader, writer, config, coroutineContext) +): Socket = when (config.isClient && config.authentication == null) { + true -> try { + openTLSSession(socket, input, output, config, coroutineContext) } catch (cause: Throwable) { - reader.cancel(cause) - writer.close(cause) - close() + input.cancel(cause) + output.close(cause) + socket.close() throw cause } + false -> { + val engine = when (val address = socket.remoteAddress) { + is UnixSocketAddress -> config.sslContext.createSSLEngine() + is InetSocketAddress -> config.sslContext.createSSLEngine( + config.serverName ?: address.hostname, + address.port + ) + } + + //TODO: we can also set cipherSuites here + engine.useClientMode = config.isClient + + SSLEngineSocket(coroutineContext, engine, this) + } } /** @@ -45,14 +56,3 @@ public suspend fun Socket.tls( this.cipherSuites = cipherSuites this.serverName = serverName } - -/** - * Make [Socket] connection secure with TLS configured with [block]. - */ -public actual suspend fun Socket.tls(coroutineContext: CoroutineContext, block: TLSConfigBuilder.() -> Unit): Socket = - tls(coroutineContext, TLSConfigBuilder().apply(block).build()) - -@InternalAPI -public fun Socket.tls(coroutineContext: CoroutineContext, engine: SSLEngine): Socket { - return SSLEngineSocket(coroutineContext, engine, connection()) -} diff --git a/ktor-network/ktor-network-tls/jvm/src/io/ktor/network/tls/TLSClientSessionJvm.kt b/ktor-network/ktor-network-tls/jvm/src/io/ktor/network/tls/TLSClientSessionJvm.kt index c8184355f0b..6503e2a5b93 100644 --- a/ktor-network/ktor-network-tls/jvm/src/io/ktor/network/tls/TLSClientSessionJvm.kt +++ b/ktor-network/ktor-network-tls/jvm/src/io/ktor/network/tls/TLSClientSessionJvm.kt @@ -14,7 +14,7 @@ import kotlinx.coroutines.channels.* import java.nio.* import kotlin.coroutines.* -internal actual suspend fun openTLSSession( +internal suspend fun openTLSSession( socket: Socket, input: ByteReadChannel, output: ByteWriteChannel, diff --git a/ktor-network/ktor-network-tls/jvm/src/io/ktor/network/tls/TLSConfigBuilder.kt b/ktor-network/ktor-network-tls/jvm/src/io/ktor/network/tls/TLSConfigBuilder.kt index 24ec81f329a..724381a3c2d 100644 --- a/ktor-network/ktor-network-tls/jvm/src/io/ktor/network/tls/TLSConfigBuilder.kt +++ b/ktor-network/ktor-network-tls/jvm/src/io/ktor/network/tls/TLSConfigBuilder.kt @@ -5,6 +5,7 @@ package io.ktor.network.tls import kotlinx.coroutines.* +import java.io.* import java.security.* import java.security.cert.* import java.security.cert.Certificate @@ -13,7 +14,9 @@ import javax.net.ssl.* /** * [TLSConfig] builder. */ -public actual class TLSConfigBuilder { +public actual class TLSConfigBuilder actual constructor(private val isClient: Boolean) { + private var authenticationBuilder: TLSAuthenticationConfigBuilder? = null + /** * List of client certificate chains with private keys. */ @@ -51,27 +54,73 @@ public actual class TLSConfigBuilder { */ public actual var serverName: String? = null + public actual fun authentication( + privateKeyPassword: () -> CharArray, + block: TLSAuthenticationConfigBuilder.() -> Unit + ) { + authenticationBuilder = TLSAuthenticationConfigBuilder(privateKeyPassword).apply(block) + } + + /** + * Append config from [other] builder. + */ + public actual fun takeFrom(other: TLSConfigBuilder) { + certificates += other.certificates + random = other.random + cipherSuites = other.cipherSuites + trustManager = other.trustManager + serverName = other.serverName + authenticationBuilder = other.authenticationBuilder + } + /** * Create [TLSConfig]. */ public actual fun build(): TLSConfig = TLSConfig( - random ?: SecureRandom(), - certificates, - trustManager as? X509TrustManager ?: findTrustManager(), - cipherSuites, - serverName + random = random ?: SecureRandom(), + certificates = certificates, + trustManager = trustManager as? X509TrustManager ?: findTrustManager(), + cipherSuites = cipherSuites, + isClient = isClient, + serverName = serverName, + authentication = authenticationBuilder?.build() ) } -/** - * Append config from [other] builder. - */ -public actual fun TLSConfigBuilder.takeFrom(other: TLSConfigBuilder) { - certificates += other.certificates - random = other.random - cipherSuites = other.cipherSuites - serverName = other.serverName - trustManager = other.trustManager +public actual class TLSAuthenticationConfigBuilder actual constructor( + private val privateKeyPassword: () -> CharArray +) { + private var keyStore: KeyStore? = null + + public actual fun pkcs12Certificate( + certificatePath: String, + certificatePassword: (() -> CharArray)? + ) { + pkcs12Certificate(File(certificatePath), certificatePassword) + } + + public fun pkcs12Certificate( + certificatePath: File, + certificatePassword: (() -> CharArray)? = null + ) { + keyStore = KeyStore.getInstance("PKCS12").apply { + val password = certificatePassword?.invoke() + load(certificatePath.inputStream(), password) + password?.fill('\u0000') + } + } + + public fun keyStore(keyStore: KeyStore) { + this.keyStore = keyStore + } + + public actual fun build(): TLSAuthenticationConfig = TLSAuthenticationConfig( + KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()).apply { + val password = privateKeyPassword() + init(keyStore, password) + password.fill('\u0000') + } + ) } /** diff --git a/ktor-network/ktor-network-tls/jvm/src/io/ktor/network/tls/TLSConfigJvm.kt b/ktor-network/ktor-network-tls/jvm/src/io/ktor/network/tls/TLSConfigJvm.kt index acf7a615538..81ab4e30bbc 100644 --- a/ktor-network/ktor-network-tls/jvm/src/io/ktor/network/tls/TLSConfigJvm.kt +++ b/ktor-network/ktor-network-tls/jvm/src/io/ktor/network/tls/TLSConfigJvm.kt @@ -21,7 +21,23 @@ public actual class TLSConfig( public val certificates: List, public val trustManager: X509TrustManager, public val cipherSuites: List, - public val serverName: String? + public actual val isClient: Boolean, + public actual val serverName: String?, + public actual val authentication: TLSAuthenticationConfig? +) { + public val sslContext: SSLContext by lazy { + SSLContext.getInstance("TLS").apply { + init( + authentication?.keyManagerFactory?.keyManagers, + arrayOf(trustManager), + random + ) + } + } +} + +public actual class TLSAuthenticationConfig( + public val keyManagerFactory: KeyManagerFactory ) /** diff --git a/ktor-network/ktor-network-tls/jvm/test/io/ktor/network/tls/configure.kt b/ktor-network/ktor-network-tls/jvm/test/io/ktor/network/tls/configure.kt new file mode 100644 index 00000000000..be4dc1420af --- /dev/null +++ b/ktor-network/ktor-network-tls/jvm/test/io/ktor/network/tls/configure.kt @@ -0,0 +1,12 @@ +/* + * Copyright 2014-2022 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package io.ktor.network.tls + +import kotlinx.coroutines.* +import java.io.* + +actual val Dispatchers.IOBridge: CoroutineDispatcher get() = IO + +actual val testResourcesFolder: String get() = File("jvmAndNix/test-resources").absolutePath diff --git a/ktor-network/ktor-network-tls/jvm/test/io/ktor/network/tls/tests/ConnectionTest.kt b/ktor-network/ktor-network-tls/jvm/test/io/ktor/network/tls/tests/ConnectionTest.kt index 6466770aeb7..cb74e75d707 100644 --- a/ktor-network/ktor-network-tls/jvm/test/io/ktor/network/tls/tests/ConnectionTest.kt +++ b/ktor-network/ktor-network-tls/jvm/test/io/ktor/network/tls/tests/ConnectionTest.kt @@ -58,60 +58,20 @@ class ConnectionTest { assertTrue(socket.openReadChannel().readRemaining().isNotEmpty) } - @OptIn(InternalAPI::class) - @Test - fun tlsEngineWithoutCloseTest(): Unit = runBlocking { - val context = SSLContext.getInstance("TLS").apply { - val trustManager = object : X509TrustManager { - override fun checkClientTrusted(chain: Array?, authType: String?) { - } - - override fun checkServerTrusted(chain: Array?, authType: String?) { - } - - override fun getAcceptedIssuers(): Array = arrayOf() - } - init(null, arrayOf(trustManager), null) - } - val engine = context.createSSLEngine().apply { - useClientMode = true - } - val selectorManager = ActorSelectorManager(Dispatchers.IO) - val socket = aSocket(selectorManager) - .tcp() - .connect("www.google.com", port = 443) - .tls(Dispatchers.Default, engine) - - val channel = socket.openWriteChannel() - channel.apply { - writeStringUtf8("GET / HTTP/1.1\r\n") - writeStringUtf8("Host: www.google.com\r\n") - writeStringUtf8("Connection: close\r\n\r\n") - flush() - } - assertTrue(socket.openReadChannel().readRemaining().isNotEmpty) - } - - @OptIn(InternalAPI::class) @Test fun testClientServer() = runBlocking { - val context = SSLContext.getInstance("TLS").apply { - val keyStore = generateCertificate() - val trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()).apply { - init(keyStore) - } - val keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()).apply { - init(keyStore, "changeit".toCharArray()) - } - init(keyManagerFactory.keyManagers, trustManagerFactory.trustManagers, null) - } + val keyStore = generateCertificate() SelectorManager(Dispatchers.IO).use { selector -> val tcp = aSocket(selector).tcp() tcp.bind().use { serverSocket -> val serverJob = GlobalScope.launch { while (true) serverSocket.accept() - .tls(Dispatchers.Default, context.createSSLEngine().apply { useClientMode = false }) + .tls(Dispatchers.Default, isClient = false) { + authentication({ "changeit".toCharArray() }) { + keyStore(keyStore) + } + } .use { socket -> val reader = socket.openReadChannel() val writer = socket.openWriteChannel() @@ -126,7 +86,12 @@ class ConnectionTest { } tcp.connect(serverSocket.localAddress) - .tls(Dispatchers.Default, context.createSSLEngine().apply { useClientMode = true }) + .tls(Dispatchers.Default, isClient = true) { + trustManager = + TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()).apply { + init(keyStore) + }.trustManagers.first() + } .use { socket -> socket.openWriteChannel().apply { writeStringUtf8("GET / HTTP/1.1\r\n") diff --git a/ktor-network/ktor-network-tls/jvmAndNix/src/io/ktor/network/tls/TLSClientSession.kt b/ktor-network/ktor-network-tls/jvmAndNix/src/io/ktor/network/tls/TLSClientSession.kt deleted file mode 100644 index 766791b048b..00000000000 --- a/ktor-network/ktor-network-tls/jvmAndNix/src/io/ktor/network/tls/TLSClientSession.kt +++ /dev/null @@ -1,17 +0,0 @@ -/* -* Copyright 2014-2021 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. -*/ - -package io.ktor.network.tls - -import io.ktor.network.sockets.* -import io.ktor.utils.io.* -import kotlin.coroutines.* - -internal expect suspend fun openTLSSession( - socket: Socket, - input: ByteReadChannel, - output: ByteWriteChannel, - config: TLSConfig, - context: CoroutineContext -): Socket diff --git a/ktor-network/ktor-network-tls/jvmAndNix/src/io/ktor/network/tls/TLSCommon.kt b/ktor-network/ktor-network-tls/jvmAndNix/src/io/ktor/network/tls/TLSCommon.kt index b383b03616f..a6ca1f356e1 100644 --- a/ktor-network/ktor-network-tls/jvmAndNix/src/io/ktor/network/tls/TLSCommon.kt +++ b/ktor-network/ktor-network-tls/jvmAndNix/src/io/ktor/network/tls/TLSCommon.kt @@ -10,47 +10,33 @@ import kotlin.coroutines.* /** * Make [Socket] connection secure with TLS using [TLSConfig]. */ -public expect suspend fun Socket.tls( +public suspend fun Socket.tls( coroutineContext: CoroutineContext, config: TLSConfig -): Socket - -/** - * Make [Socket] connection secure with TLS. - * - * TODO: report YT issue - */ -public suspend fun Socket.tls(coroutineContext: CoroutineContext): Socket = tls(coroutineContext) {} +): Socket = connection().tls(coroutineContext, config) /** * Make [Socket] connection secure with TLS configured with [block]. */ -public expect suspend fun Socket.tls(coroutineContext: CoroutineContext, block: TLSConfigBuilder.() -> Unit): Socket +public suspend fun Socket.tls( + coroutineContext: CoroutineContext, + isClient: Boolean = true, + block: TLSConfigBuilder.() -> Unit = {} +): Socket = tls(coroutineContext, TLSConfig(isClient, block)) /** * Make [Socket] connection secure with TLS using [TLSConfig]. */ -public suspend fun Connection.tls( +public expect suspend fun Connection.tls( coroutineContext: CoroutineContext, config: TLSConfig -): Socket { - return try { - openTLSSession(socket, input, output, config, coroutineContext) - } catch (cause: Throwable) { - input.cancel(cause) - output.close(cause) - socket.close() - throw cause - } -} +): Socket /** - * Make [Socket] connection secure with TLS. + * Make [Socket] connection secure with TLS configured with [block]. */ -public suspend fun Connection.tls(coroutineContext: CoroutineContext): Socket = tls(coroutineContext) {} - -/** -* Make [Socket] connection secure with TLS configured with [block]. -*/ -public suspend fun Connection.tls(coroutineContext: CoroutineContext, block: TLSConfigBuilder.() -> Unit): Socket = - tls(coroutineContext, TLSConfigBuilder().apply(block).build()) +public suspend fun Connection.tls( + coroutineContext: CoroutineContext, + isClient: Boolean = true, + block: TLSConfigBuilder.() -> Unit +): Socket = tls(coroutineContext, TLSConfig(isClient, block)) diff --git a/ktor-network/ktor-network-tls/jvmAndNix/src/io/ktor/network/tls/TLSConfig.kt b/ktor-network/ktor-network-tls/jvmAndNix/src/io/ktor/network/tls/TLSConfig.kt index ca84d859e16..c7b4372b28b 100644 --- a/ktor-network/ktor-network-tls/jvmAndNix/src/io/ktor/network/tls/TLSConfig.kt +++ b/ktor-network/ktor-network-tls/jvmAndNix/src/io/ktor/network/tls/TLSConfig.kt @@ -4,4 +4,16 @@ package io.ktor.network.tls -public expect class TLSConfig +//TODO: make it closeable +public expect class TLSConfig { + public val isClient: Boolean + public val serverName: String? + public val authentication: TLSAuthenticationConfig? +} + +public expect class TLSAuthenticationConfig + +public fun TLSConfig( + isClient: Boolean = true, + block: TLSConfigBuilder.() -> Unit +): TLSConfig = TLSConfigBuilder(isClient).apply(block).build() diff --git a/ktor-network/ktor-network-tls/jvmAndNix/src/io/ktor/network/tls/TLSConfigBuilderCommon.kt b/ktor-network/ktor-network-tls/jvmAndNix/src/io/ktor/network/tls/TLSConfigBuilderCommon.kt index a9e1e369789..2ececc71398 100644 --- a/ktor-network/ktor-network-tls/jvmAndNix/src/io/ktor/network/tls/TLSConfigBuilderCommon.kt +++ b/ktor-network/ktor-network-tls/jvmAndNix/src/io/ktor/network/tls/TLSConfigBuilderCommon.kt @@ -7,20 +7,34 @@ package io.ktor.network.tls /** * [TLSConfig] builder. */ -public expect class TLSConfigBuilder() { +//TODO: add verification - alternative to JVM trust manager +public expect class TLSConfigBuilder(isClient: Boolean = true) { /** * Custom server name for TLS server name extension. * See also: https://en.wikipedia.org/wiki/Server_Name_Indication */ + //TODO: rename to peerName? public var serverName: String? + //if used, on JVM, SSLEngine will be used, instead of handwritten implementation + //supported on Linux native + //`certificates` on JVM should be empty + public fun authentication(privateKeyPassword: () -> CharArray, block: TLSAuthenticationConfigBuilder.() -> Unit) + + /** + * Append config from [other] builder. + */ + public fun takeFrom(other: TLSConfigBuilder) + /** * Create [TLSConfig]. */ public fun build(): TLSConfig } -/** - * Append config from [other] builder. - */ -public expect fun TLSConfigBuilder.takeFrom(other: TLSConfigBuilder) +public expect class TLSAuthenticationConfigBuilder( + privateKeyPassword: () -> CharArray +) { + public fun pkcs12Certificate(certificatePath: String, certificatePassword: (() -> CharArray)? = null) + public fun build(): TLSAuthenticationConfig +} diff --git a/ktor-network/ktor-network-tls/jvmAndNix/test-resources/commonkey.p12 b/ktor-network/ktor-network-tls/jvmAndNix/test-resources/commonkey.p12 new file mode 100644 index 0000000000000000000000000000000000000000..b9dce45cce84c113190cd84d0ff15a3321dc6766 GIT binary patch literal 5669 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 literal 0 HcmV?d00001 diff --git a/ktor-network/ktor-network-tls/jvmAndNix/test/io/ktor/network/tls/TlsClientServerSocketTest.kt b/ktor-network/ktor-network-tls/jvmAndNix/test/io/ktor/network/tls/TlsClientServerSocketTest.kt new file mode 100644 index 00000000000..bc03ee5b73d --- /dev/null +++ b/ktor-network/ktor-network-tls/jvmAndNix/test/io/ktor/network/tls/TlsClientServerSocketTest.kt @@ -0,0 +1,66 @@ +/* + * Copyright 2014-2022 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package io.ktor.network.tls + +import io.ktor.network.selector.* +import io.ktor.network.sockets.* +import io.ktor.utils.io.* +import io.ktor.utils.io.core.* +import kotlinx.coroutines.* +import kotlin.test.* + +expect val testResourcesFolder: String + +class TlsClientServerSocketTest { + + //this test show how to configure authentication fully common - will work both on native and JVM with the same configuration + @Test + fun testClientServer() = runBlocking { + SelectorManager(Dispatchers.IOBridge).use { selector -> + val tcp = aSocket(selector).tcp() + + tcp.bind().use { serverSocket -> + val serverJob = GlobalScope.launch { + while (true) serverSocket.accept() + .tls(Dispatchers.Default + CoroutineName("SERVER"), isClient = false) { + authentication(privateKeyPassword = { "changeit".toCharArray() }) { + pkcs12Certificate("$testResourcesFolder/commonkey.p12") { "changeit".toCharArray() } + } + } + .use { socket -> + val reader = socket.openReadChannel() + val writer = socket.openWriteChannel() + repeat(3) { + val line = assertNotNull(reader.readUTF8Line()) + println("SSS: $line") + writer.writeStringUtf8("$line\r\n") + writer.flush() + } + delay(2000) //await reading from client socket + } + } + + tcp.connect(serverSocket.localAddress) + .tls(Dispatchers.Default + CoroutineName("CLIENT"), isClient = true) { + authentication({ "".toCharArray() }) {} //forces using of SSLEngine + //TODO: ser verification here + } + .use { socket -> + socket.openWriteChannel().apply { + writeStringUtf8("GET / HTTP/1.1\r\n") + writeStringUtf8("Host: www.google.com\r\n") + writeStringUtf8("Connection: close\r\n") + flush() + } + val reader = socket.openReadChannel() + repeat(3) { + println("CCC: ${assertNotNull(reader.readUTF8Line())}") + } + } + serverJob.cancelAndJoin() + } + } + } +} diff --git a/ktor-network/ktor-network-tls/jvmAndNix/test/io/ktor/network/tls/TlsClientSocketTest.kt b/ktor-network/ktor-network-tls/jvmAndNix/test/io/ktor/network/tls/TlsClientSocketTest.kt new file mode 100644 index 00000000000..c6e604730bc --- /dev/null +++ b/ktor-network/ktor-network-tls/jvmAndNix/test/io/ktor/network/tls/TlsClientSocketTest.kt @@ -0,0 +1,38 @@ +/* + * Copyright 2014-2022 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package io.ktor.network.tls + +import io.ktor.network.selector.* +import io.ktor.network.sockets.* +import io.ktor.utils.io.* +import io.ktor.utils.io.core.* +import kotlinx.coroutines.* +import kotlin.test.* + +expect val Dispatchers.IOBridge: CoroutineDispatcher + +class TlsClientSocketTest { + + @Test + fun testGoogleWithoutClose(): Unit = runBlocking { + SelectorManager(Dispatchers.IOBridge).use { selector -> + aSocket(selector) + .tcp() + .connect(hostname = "www.google.com", port = 443) + .tls(Dispatchers.IOBridge) { + authentication({ "".toCharArray() }) {} //forces using of SSLEngine + } + .use { socket -> + socket.openWriteChannel().run { + writeStringUtf8("GET / HTTP/1.1\r\n") + writeStringUtf8("Host: www.google.com\r\n") + writeStringUtf8("Connection: close\r\n\r\n") + flush() + } + println(socket.openReadChannel().readRemaining().readText()) + } + } + } +} diff --git a/ktor-network/ktor-network-tls/linuxX64/interop/openssl.def b/ktor-network/ktor-network-tls/linuxX64/interop/openssl.def new file mode 100644 index 00000000000..9e0c7832b82 --- /dev/null +++ b/ktor-network/ktor-network-tls/linuxX64/interop/openssl.def @@ -0,0 +1,5 @@ +package = io.ktor.network.tls.internal.openssl +headers = openssl/bio.h openssl/ssl.h openssl/err.h openssl/pkcs12.h openssl/pem.h openssl/x509.h openssl/evp.h +headerFilter = openssl/* +linkerOpts.linux = -L/home/linuxbrew/.linuxbrew/lib -lssl -lcrypto +compilerOpts.linux = -I/home/linuxbrew/.linuxbrew/include diff --git a/ktor-network/ktor-network-tls/linuxX64/src/io/ktor/network/tls/BIOPipe.kt b/ktor-network/ktor-network-tls/linuxX64/src/io/ktor/network/tls/BIOPipe.kt new file mode 100644 index 00000000000..16f158f89e5 --- /dev/null +++ b/ktor-network/ktor-network-tls/linuxX64/src/io/ktor/network/tls/BIOPipe.kt @@ -0,0 +1,86 @@ +/* + * Copyright 2014-2022 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package io.ktor.network.tls + +import io.ktor.network.sockets.* +import io.ktor.network.tls.internal.openssl.* +import io.ktor.utils.io.* +import kotlinx.atomicfu.locks.* +import kotlinx.cinterop.* +import kotlinx.coroutines.* +import kotlinx.coroutines.sync.* +import kotlin.coroutines.* + +//TODO handle BIO_eof +//TODO handle not fully write +//TODO handle partiol read +internal class BIOPipe( + ssl: CPointer, + connection: Connection, + private val debugString: String +) { + private val readerLock = Mutex() + private var readerCont: CancellableContinuation? = null + private var readerContLock = SynchronizedObject() + + private val writerLock = Mutex() + + private val sslOutput = BIO_new(BIO_s_mem())!! + private val sslInput = BIO_new(BIO_s_mem())!! + private val connectionInput = connection.input + private val connectionOutput = connection.output + + init { + SSL_set_bio(s = ssl, rbio = sslInput, wbio = sslOutput) + } + + //TODO: revisit + suspend fun readAvailable(): Int { + // if read is in progress, await it result + if (!readerLock.tryLock()) return suspendCancellableCoroutine { + synchronized(readerContLock) { + readerCont = it + } + } + + try { + val result = connectionInput.read { source, start, endExclusive -> + val bytesWritten = BIO_write(sslInput, source.pointer + start, (endExclusive - start).toInt()) + //println("[$debugString] PIPE.READ: $bytesWritten") + if (bytesWritten == -2) TODO("BIO operation isn't implemented") + else if (bytesWritten < 0) 0 + else bytesWritten + } + synchronized(readerContLock) { + readerCont?.resume(result) + readerCont = null + } + + return result + } catch (cause: Throwable) { + synchronized(readerContLock) { + readerCont?.resumeWithException(cause) + readerCont = null + } + throw cause + } finally { + readerLock.unlock() + } + } + + suspend fun writeFully(): Int = writerLock.withLock { + //println("[$debugString] PIPE.WRITE.START") + val result = connectionOutput.write { freeSpace, startOffset, endExclusive -> + val bytesRead = BIO_read(sslOutput, freeSpace.pointer + startOffset, (endExclusive - startOffset).toInt()) + //println("[$debugString] PIPE.WRITE: $bytesRead") + if (bytesRead == -2) TODO("BIO operation isn't implemented") + else if (bytesRead < 0) 0 + else bytesRead + } + connectionOutput.flush() + //println("[$debugString] PIPE.WRITE.FLUSHED") + return result + } +} diff --git a/ktor-network/ktor-network-tls/linuxX64/src/io/ktor/network/tls/OPENSSLInit.kt b/ktor-network/ktor-network-tls/linuxX64/src/io/ktor/network/tls/OPENSSLInit.kt new file mode 100644 index 00000000000..a643f807f26 --- /dev/null +++ b/ktor-network/ktor-network-tls/linuxX64/src/io/ktor/network/tls/OPENSSLInit.kt @@ -0,0 +1,19 @@ +/* + * Copyright 2014-2022 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package io.ktor.network.tls + +import io.ktor.network.tls.internal.openssl.* +import kotlinx.cinterop.* + +@Suppress("DEPRECATION") +@OptIn(ExperimentalStdlibApi::class) +@EagerInitialization +internal val opensslInitReturnCode = opensslInitBridge() //TODO + +private fun opensslInitBridge() { + OPENSSL_init_crypto(OPENSSL_INIT_ADD_ALL_CIPHERS.convert(), null) + OPENSSL_init_crypto(OPENSSL_INIT_ADD_ALL_DIGESTS.convert(), null) + OPENSSL_init_ssl(0.convert(), null) +} diff --git a/ktor-network/ktor-network-tls/linuxX64/src/io/ktor/network/tls/PKCS12.kt b/ktor-network/ktor-network-tls/linuxX64/src/io/ktor/network/tls/PKCS12.kt new file mode 100644 index 00000000000..273437cf858 --- /dev/null +++ b/ktor-network/ktor-network-tls/linuxX64/src/io/ktor/network/tls/PKCS12.kt @@ -0,0 +1,54 @@ +/* + * Copyright 2014-2022 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package io.ktor.network.tls + +import kotlinx.cinterop.* +import io.ktor.network.tls.internal.openssl.* +import platform.posix.* + +internal inline fun PKCS12Certificate.parse( + block: ( + privateKey: CPointer, + x509Certificate: CPointer, + ) -> Unit +) { + val pkcs12 = useFile(path, "rb") { file -> + requireNotNull(d2i_PKCS12_fp(file, null)) { "Failed to read PKCS12 file" } + } + try { + val password = password?.invoke() + + val pkey = nativeHeap.allocPointerTo() + val cert = nativeHeap.allocPointerTo() + + try { + //TODO reading CA certificates + require(PKCS12_parse(pkcs12, password?.concatToString(), pkey.ptr, cert.ptr, null) == 1) { + "Failed to parse PKCS12 file" + } + block(pkey.value!!, cert.value!!) + } catch (cause: Throwable) { + EVP_PKEY_free(pkey.value) + X509_free(cert.value) + password?.fill('\u0000') + throw cause + } + } finally { + PKCS12_free(pkcs12) + } +} + +private fun useFile( + path: String, + modes: String?, + block: (CPointer) -> T +): T { + val file = requireNotNull(fopen(path, modes)) { "Failed to open file: $path" } + try { + return block(file) + } finally { + fclose(file) + } +} diff --git a/ktor-network/ktor-network-tls/linuxX64/src/io/ktor/network/tls/SSLError.kt b/ktor-network/ktor-network-tls/linuxX64/src/io/ktor/network/tls/SSLError.kt new file mode 100644 index 00000000000..5906c7c457c --- /dev/null +++ b/ktor-network/ktor-network-tls/linuxX64/src/io/ktor/network/tls/SSLError.kt @@ -0,0 +1,30 @@ +/* + * Copyright 2014-2022 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package io.ktor.network.tls + +import io.ktor.network.tls.internal.openssl.* + +internal enum class SSLError(private val code: Int) { + WantRead(SSL_ERROR_WANT_READ), + WantWrite(SSL_ERROR_WANT_WRITE), + WANT_ASYNC(SSL_ERROR_WANT_ASYNC), + WANT_ASYNC_JOB(SSL_ERROR_WANT_ASYNC_JOB), + WANT_CLIENT_HELLO_CB(SSL_ERROR_WANT_CLIENT_HELLO_CB), + WANT_X509_LOOKUP(SSL_ERROR_WANT_X509_LOOKUP), + Closed(SSL_ERROR_ZERO_RETURN); + + companion object { + private val values: Array + + init { + val enums = values() + val maxCode = enums.maxOf { it.code } + values = arrayOfNulls(maxCode + 1) + enums.forEach { values[it.code] = it } + } + + operator fun invoke(code: Int): SSLError = values[code] ?: error("Unknown code: $code") + } +} diff --git a/ktor-network/ktor-network-tls/linuxX64/src/io/ktor/network/tls/SSLOperations.kt b/ktor-network/ktor-network-tls/linuxX64/src/io/ktor/network/tls/SSLOperations.kt new file mode 100644 index 00000000000..40d4a63ca7a --- /dev/null +++ b/ktor-network/ktor-network-tls/linuxX64/src/io/ktor/network/tls/SSLOperations.kt @@ -0,0 +1,102 @@ +/* + * Copyright 2014-2022 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +/* + * Copyright 2014-2022 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package io.ktor.network.tls + +import io.ktor.network.tls.internal.openssl.* +import io.ktor.utils.io.errors.* +import kotlinx.atomicfu.locks.* +import kotlinx.cinterop.* +import platform.posix.* + +internal class SSLOperations( + private val ssl: CPointer, + val debugString: String +) { + private val operationsLock = SynchronizedObject() + + inline fun write( + source: CPointer, sourceOffset: Int, sourceLength: Int, + onSuccess: (consumed: Int) -> T, + onError: (error: SSLError) -> T, + onClose: () -> T + ): T { + //println("[$debugString] ENGINE.WRITE.START") + return ssl.operation({ + //println("[$debugString] ENGINE.WRITE.SUCCESS: $it") + onSuccess(it) + }, { + //println("[$debugString] ENGINE.WRITE.ERROR: $it") + onError(it) + }, { + //println("[$debugString] ENGINE.WRITE.CLOSE") + onClose() + }) { + SSL_write(this, source + sourceOffset, sourceLength - sourceOffset) + } + } + + internal inline fun read( + destination: CPointer, destinationOffset: Int, destinationLength: Int, + onSuccess: (produced: Int) -> T, + onError: (error: SSLError) -> T, + onClose: () -> T + ): T { + //println("[$debugString] ENGINE.READ.START") + return ssl.operation({ + //println("[$debugString] ENGINE.READ.SUCCESS: $it") + onSuccess(it) + }, { + //println("[$debugString] ENGINE.READ.ERROR: $it") + onError(it) + }, { + //println("[$debugString] ENGINE.READ.CLOSE") + onClose() + }) { + SSL_read(this, destination + destinationOffset, destinationLength - destinationOffset) + } + } + + private inline fun CPointer.operation( + onSuccess: (result: Int) -> T, + onError: (error: SSLError) -> T, + onClose: () -> T, + block: CPointer.() -> Int + ): T = operationsLock.withLock { + ERR_clear_error() + val result = block(this) + + if (result > 0) return@withLock onSuccess(result) + if (result == 0) return@withLock onClose() + + onError( + when (val code = SSL_get_error(this, result)) { + SSL_ERROR_NONE -> TODO("never") + SSL_ERROR_SSL -> { + val message = getSslErrorMessage() + error("SSL error: $message") + } + SSL_ERROR_SYSCALL -> { + if (errno != 0) throw PosixException.forErrno() + val message = getSslErrorMessage() + error("SSL SYSCALL error: $message") + } + else -> SSLError(code) + } + ) + } + + private fun getSslErrorMessage(): String? = when (val errorCode = ERR_get_error()) { + 0UL -> null + else -> memScoped { + val pointer = allocArray(256) + ERR_error_string(errorCode, pointer) + pointer.toKString() + } + } +} diff --git a/ktor-network/ktor-network-tls/linuxX64/src/io/ktor/network/tls/SSLSocket.kt b/ktor-network/ktor-network-tls/linuxX64/src/io/ktor/network/tls/SSLSocket.kt new file mode 100644 index 00000000000..20dab8e46c7 --- /dev/null +++ b/ktor-network/ktor-network-tls/linuxX64/src/io/ktor/network/tls/SSLSocket.kt @@ -0,0 +1,175 @@ +/* + * Copyright 2014-2022 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +/* + * Copyright 2014-2022 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package io.ktor.network.tls + +import io.ktor.network.sockets.* +import io.ktor.utils.io.* +import io.ktor.utils.io.core.* +import kotlinx.atomicfu.* +import kotlinx.cinterop.* +import kotlinx.coroutines.* +import kotlinx.coroutines.sync.* +import io.ktor.network.tls.internal.openssl.* +import kotlin.coroutines.* + +internal class SSLSocket( + override val coroutineContext: CoroutineContext, + ssl: CPointer, + connection: Connection +) : Socket, CoroutineScope { + private val socket = connection.socket + + override val socketContext: Job get() = socket.socketContext + override val remoteAddress: SocketAddress get() = socket.remoteAddress + override val localAddress: SocketAddress get() = socket.localAddress + + private val debugString = coroutineContext[CoroutineName]?.name ?: "DEBUG" + + private val lock = Mutex() + private val closed = atomic(false) + + private val operations = SSLOperations(ssl, debugString) + private val pipe = BIOPipe(ssl, connection, debugString) + + init { + socketContext.job.invokeOnCompletion { + SSL_free(ssl) //TODO: when to call SSL_free(ssl) + } + } + + override fun attachForReading(channel: ByteChannel): WriterJob = + writer(CoroutineName("network-secure-input"), channel) { + var error: Throwable? = null + try { + do { + val result = this.channel.write { destination, startOffset, endExclusive -> + val destinationLength = (endExclusive - startOffset).toInt() + //println("[$debugString] READING START: size=${destination.size} | offset=$startOffset | length=$destinationLength") + if (destinationLength <= 0) { + //println("[$debugString] READING STOP: NO MORE TO WRITE") + return@write 0 + } + val initialOffset = startOffset.toInt() + var destinationOffset = initialOffset + + while (true) { + //println("[$debugString] READING STEP: size=${destination.size} | offset=$destinationOffset | length=$destinationLength") + val sslError = operations.read( + destination.pointer, destinationOffset, destinationLength, + onSuccess = { + destinationOffset += it //TODO: caps at 1378 + null + }, + onError = { it }, + onClose = { SSLError.Closed } + ) + pipe.writeFully() //TODO? + + when (sslError) { + null -> {} + SSLError.Closed -> break //TODO handle properly + SSLError.WantRead -> { + if (destinationOffset != initialOffset) break //we read some data already + if (pipe.readAvailable() == 0) { + //println("[$debugString] READING STOP: NO MORE TO READ FROM SOCKET") + return@write 0 + } + } + else -> TODO("READ_ERROR: $sslError") + } + } + //println("[$debugString] READING STOP: size=${destination.size} | offset=$destinationOffset | length=$destinationLength") + destinationOffset - initialOffset + } + this.channel.flush() + //println("[$debugString] READING STEP RESULT: $result") + } while (result > 0) + } catch (cause: Throwable) { + error = cause + throw cause + } finally { + //println(error) +// unwrapper.cancel(error) +// engine.closeOutbound() +// doClose(error) + } + } + + @Suppress("BlockingMethodInNonBlockingContext") + override fun attachForWriting(channel: ByteChannel): ReaderJob = + reader(CoroutineName("network-secure-output"), channel) { + var error: Throwable? = null + try { + do { + val result = this@reader.channel.read { source, startOffset, endExclusive -> + val sourceLength = (endExclusive - startOffset).toInt() + //println("[$debugString] WRITING START: size=${source.size} | offset=$startOffset | length=$sourceLength") + if (sourceLength <= 0) { + //println("[$debugString] WRITING STOP: NO MORE TO READ") + return@read 0 + } + var sourceOffset = startOffset.toInt() + + while (sourceLength - sourceOffset > 0) { + //println("[$debugString] WRITING STEP: size=${source.size} | offset=$sourceOffset | length=$sourceLength") + val sslError = operations.write( + source.pointer, sourceOffset, sourceLength, + onSuccess = { + sourceOffset += it + null + }, + onError = { it }, + onClose = { SSLError.Closed } + ) + pipe.writeFully() //TODO? + + when (sslError) { + null -> {} + SSLError.Closed -> break //TODO handle properly + SSLError.WantRead -> if (pipe.readAvailable() == 0) { + //println("[$debugString] WRITING STOP: NO MORE TO READ FROM SOCKET") + return@read 0 + } + else -> TODO("WRITE_ERROR: $sslError") + } + } + //println("[$debugString] WRITING STOP: size=${source.size} | offset=$sourceOffset | length=$sourceLength") + sourceOffset - startOffset.toInt() + } + //println("[$debugString] WRITING STEP RESULT: $result") + } while (result > 0) + } catch (cause: Throwable) { + error = cause + throw cause + } finally { + //println(error) +// engine.closeInbound() //TODO: when this should be called??? +// doClose(error) + } + } + + //TODO proper close implementation? + override fun close() { + socket.close() +// engine.closeOutbound() +// if (isActive) launch { +// doClose(null) +// } else { +// if (closed.compareAndSet(expect = false, update = true)) socket.close() +// } + } + + private suspend fun doClose(cause: Throwable?) = lock.withLock { + if (closed.compareAndSet(expect = false, update = true)) { + socket.use { +// wrapper.close(cause) + } + } + } +} diff --git a/ktor-network/ktor-network-tls/linuxX64/src/io/ktor/network/tls/TLSConfigBuilderLinuxX64.kt b/ktor-network/ktor-network-tls/linuxX64/src/io/ktor/network/tls/TLSConfigBuilderLinuxX64.kt new file mode 100644 index 00000000000..b57d452a8f5 --- /dev/null +++ b/ktor-network/ktor-network-tls/linuxX64/src/io/ktor/network/tls/TLSConfigBuilderLinuxX64.kt @@ -0,0 +1,50 @@ +/* + * Copyright 2014-2019 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package io.ktor.network.tls + +/** + * [TLSConfig] builder. + */ +public actual class TLSConfigBuilder actual constructor(private val isClient: Boolean) { + private var authenticationBuilder: TLSAuthenticationConfigBuilder? = null + + /** + * Custom server name for TLS server name extension. + * See also: https://en.wikipedia.org/wiki/Server_Name_Indication + */ + public actual var serverName: String? = null + + public actual fun authentication( + privateKeyPassword: () -> CharArray, + block: TLSAuthenticationConfigBuilder.() -> Unit + ) { + authenticationBuilder = TLSAuthenticationConfigBuilder(privateKeyPassword).apply(block) + } + + public actual fun takeFrom(other: TLSConfigBuilder) { + serverName = other.serverName + authenticationBuilder = other.authenticationBuilder + } + + /** + * Create [TLSConfig]. + */ + public actual fun build(): TLSConfig = TLSConfig(isClient, serverName, authenticationBuilder?.build()) +} + +public actual class TLSAuthenticationConfigBuilder actual constructor( + private val privateKeyPassword: () -> CharArray +) { + private var certificate: PKCS12Certificate? = null + + public actual fun pkcs12Certificate(certificatePath: String, certificatePassword: (() -> CharArray)?) { + certificate = PKCS12Certificate(certificatePath, certificatePassword) + } + + public actual fun build(): TLSAuthenticationConfig = TLSAuthenticationConfig( + certificate, + privateKeyPassword + ) +} diff --git a/ktor-network/ktor-network-tls/linuxX64/src/io/ktor/network/tls/TLSConfigLinuxX64.kt b/ktor-network/ktor-network-tls/linuxX64/src/io/ktor/network/tls/TLSConfigLinuxX64.kt new file mode 100644 index 00000000000..b9eeca7b6c1 --- /dev/null +++ b/ktor-network/ktor-network-tls/linuxX64/src/io/ktor/network/tls/TLSConfigLinuxX64.kt @@ -0,0 +1,70 @@ +// ktlint-disable filename +/* + * Copyright 2014-2019 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package io.ktor.network.tls + +import io.ktor.network.tls.internal.openssl.* +import io.ktor.utils.io.core.* +import kotlinx.cinterop.* +import platform.posix.* + +public actual class TLSConfig( + public actual val isClient: Boolean, + public actual val serverName: String?, + public actual val authentication: TLSAuthenticationConfig? +) : Closeable { + private var privateKey: CPointer? = null + private var x509Certificate: CPointer? = null + private val _sslContext = lazy { + val context = SSL_CTX_new(TLS_method())!! + + //TODO: set peer + SSL_CTX_set_verify(context, SSL_VERIFY_NONE, null) + + if (authentication?.certificate != null) { + val privateKeyPassword = authentication.privateKeyPassword.invoke() + val passwordRef = StableRef.create(privateKeyPassword.concatToString()) + privateKeyPassword.fill('\u0000') + try { + SSL_CTX_set_default_passwd_cb_userdata(context, passwordRef.asCPointer()) + SSL_CTX_set_default_passwd_cb(context, passwordCheckFunction) + + authentication.certificate.parse { privateKey, x509Certificate -> + check(SSL_CTX_use_certificate(context, x509Certificate) == 1) { "Failed to set X509 certificate" } + check(SSL_CTX_use_PrivateKey(context, privateKey) == 1) { "Failed to set EVP private key" } + + this.privateKey = privateKey + this.x509Certificate = x509Certificate + } + } finally { + passwordRef.dispose() + } + } + context + } + + public val sslContext: CPointer by _sslContext + + override fun close() { + if (_sslContext.isInitialized()) { + EVP_PKEY_free(privateKey) + X509_free(x509Certificate) + SSL_CTX_free(_sslContext.value) + } + } +} + +public actual class TLSAuthenticationConfig( + public val certificate: PKCS12Certificate?, + public val privateKeyPassword: () -> CharArray +) + +private val passwordCheckFunction: CPointer = staticCFunction { buf, size, rwFlag, u -> + val stringPassword = u?.asStableRef()?.get() ?: return@staticCFunction 0 + stringPassword.encodeToByteArray().usePinned { + memcpy(buf, it.addressOf(0), stringPassword.length.convert()) + } + stringPassword.length +} diff --git a/ktor-network/ktor-network-tls/linuxX64/src/io/ktor/network/tls/TlsLinuxX64.kt b/ktor-network/ktor-network-tls/linuxX64/src/io/ktor/network/tls/TlsLinuxX64.kt new file mode 100644 index 00000000000..f5d10fa1761 --- /dev/null +++ b/ktor-network/ktor-network-tls/linuxX64/src/io/ktor/network/tls/TlsLinuxX64.kt @@ -0,0 +1,29 @@ +/* + * Copyright 2014-2022 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package io.ktor.network.tls + +import io.ktor.network.sockets.* +import io.ktor.network.tls.internal.openssl.* +import kotlin.coroutines.* + +public actual suspend fun Connection.tls( + coroutineContext: CoroutineContext, + config: TLSConfig +): Socket { + val ssl = SSL_new(config.sslContext)!! + when (val address = socket.remoteAddress) { + is UnixSocketAddress -> {} + is InetSocketAddress -> SSL_set1_host(ssl, "${address.hostname}:${address.port}") + } + + //TODO: support setting serverName + + when (config.isClient) { + true -> SSL_set_connect_state(ssl) + false -> SSL_set_accept_state(ssl) + } + + return SSLSocket(coroutineContext, ssl, this) +} diff --git a/ktor-network/ktor-network-tls/nix/src/io/ktor/network/tls/PKCS12Certificate.kt b/ktor-network/ktor-network-tls/nix/src/io/ktor/network/tls/PKCS12Certificate.kt new file mode 100644 index 00000000000..4d75ad34ad1 --- /dev/null +++ b/ktor-network/ktor-network-tls/nix/src/io/ktor/network/tls/PKCS12Certificate.kt @@ -0,0 +1,10 @@ +/* + * Copyright 2014-2022 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package io.ktor.network.tls + +public class PKCS12Certificate( + public val path: String, + public val password: (() -> CharArray)? +) diff --git a/ktor-network/ktor-network-tls/nix/src/io/ktor/network/tls/TLSClientSessionNative.kt b/ktor-network/ktor-network-tls/nix/src/io/ktor/network/tls/TLSClientSessionNative.kt deleted file mode 100644 index 8cae10bce20..00000000000 --- a/ktor-network/ktor-network-tls/nix/src/io/ktor/network/tls/TLSClientSessionNative.kt +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright 2014-2019 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. - */ - -package io.ktor.network.tls - -import io.ktor.network.sockets.* -import io.ktor.utils.io.* -import kotlin.coroutines.* - -internal actual suspend fun openTLSSession( - socket: Socket, - input: ByteReadChannel, - output: ByteWriteChannel, - config: TLSConfig, - context: CoroutineContext -): Socket { - error("TLS sessions are not supported on Native platform.") -} diff --git a/ktor-network/ktor-network-tls/nix/src/io/ktor/network/tls/TLSConfigBuilderNative.kt b/ktor-network/ktor-network-tls/nix/src/io/ktor/network/tls/TLSConfigBuilderNative.kt deleted file mode 100644 index c793008af39..00000000000 --- a/ktor-network/ktor-network-tls/nix/src/io/ktor/network/tls/TLSConfigBuilderNative.kt +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright 2014-2019 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. - */ - -package io.ktor.network.tls - -/** - * [TLSConfig] builder. - */ -public actual class TLSConfigBuilder { - /** - * Custom server name for TLS server name extension. - * See also: https://en.wikipedia.org/wiki/Server_Name_Indication - */ - public actual var serverName: String? = null - - /** - * Create [TLSConfig]. - */ - public actual fun build(): TLSConfig = TLSConfig() -} - -/** - * Append config from [other] builder. - */ -public actual fun TLSConfigBuilder.takeFrom(other: TLSConfigBuilder) { -} diff --git a/ktor-network/ktor-network-tls/nix/src/io/ktor/network/tls/TLSConfigNative.kt b/ktor-network/ktor-network-tls/nix/src/io/ktor/network/tls/TLSConfigNative.kt deleted file mode 100644 index c81687af065..00000000000 --- a/ktor-network/ktor-network-tls/nix/src/io/ktor/network/tls/TLSConfigNative.kt +++ /dev/null @@ -1,8 +0,0 @@ -// ktlint-disable filename -/* - * Copyright 2014-2019 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. - */ - -package io.ktor.network.tls - -public actual class TLSConfig() diff --git a/ktor-network/ktor-network-tls/nix/src/io/ktor/network/tls/TLSNative.kt b/ktor-network/ktor-network-tls/nix/src/io/ktor/network/tls/TLSNative.kt deleted file mode 100644 index 960fb50d752..00000000000 --- a/ktor-network/ktor-network-tls/nix/src/io/ktor/network/tls/TLSNative.kt +++ /dev/null @@ -1,34 +0,0 @@ -/* -* Copyright 2014-2021 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. -*/ - -package io.ktor.network.tls - -import io.ktor.network.sockets.* -import kotlin.coroutines.* - -/** - * Make [Socket] connection secure with TLS using [TLSConfig]. - */ -public actual suspend fun Socket.tls( - coroutineContext: CoroutineContext, - config: TLSConfig -): Socket { - val reader = openReadChannel() - val writer = openWriteChannel() - - return try { - openTLSSession(this, reader, writer, config, coroutineContext) - } catch (cause: Throwable) { - reader.cancel(cause) - writer.close(cause) - close() - throw cause - } -} - -/** - * Make [Socket] connection secure with TLS configured with [block]. - */ -public actual suspend fun Socket.tls(coroutineContext: CoroutineContext, block: TLSConfigBuilder.() -> Unit): Socket = - tls(coroutineContext, TLSConfigBuilder().apply(block).build()) diff --git a/ktor-network/ktor-network-tls/nix/test/io/ktor/network/tls/configure.kt b/ktor-network/ktor-network-tls/nix/test/io/ktor/network/tls/configure.kt new file mode 100644 index 00000000000..b48d8ae19e2 --- /dev/null +++ b/ktor-network/ktor-network-tls/nix/test/io/ktor/network/tls/configure.kt @@ -0,0 +1,21 @@ +/* + * Copyright 2014-2022 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package io.ktor.network.tls + +import kotlinx.cinterop.* +import kotlinx.coroutines.* +import platform.posix.* + +actual val Dispatchers.IOBridge: CoroutineDispatcher get() = Default + +actual val testResourcesFolder: String + get() { + val root = memScoped { + val result = allocArray(512) + getcwd(result, 512) + result.toKString() + } + return "$root/jvmAndNix/test-resources" + } diff --git a/ktor-server/ktor-server-cio/jvm/src/io/ktor/server/cio/HttpServerSettingsJvm.kt b/ktor-server/ktor-server-cio/jvm/src/io/ktor/server/cio/HttpServerSettingsJvm.kt index c0ba5146633..646e4dad56c 100644 --- a/ktor-server/ktor-server-cio/jvm/src/io/ktor/server/cio/HttpServerSettingsJvm.kt +++ b/ktor-server/ktor-server-cio/jvm/src/io/ktor/server/cio/HttpServerSettingsJvm.kt @@ -7,37 +7,24 @@ package io.ktor.server.cio import io.ktor.network.sockets.* import io.ktor.network.tls.* import io.ktor.server.engine.* -import io.ktor.util.* import java.io.* import java.security.* import javax.net.ssl.* import kotlin.coroutines.* -@OptIn(InternalAPI::class) internal actual fun HttpServerSettings( connectionIdleTimeoutSeconds: Long, connectorConfig: EngineConnectorConfig ): HttpServerSettings { - val interceptor: (Socket, CoroutineContext) -> Socket = when (connectorConfig) { + val interceptor: suspend (Socket, CoroutineContext) -> Socket = when (connectorConfig) { is EngineSSLConnectorConfig -> { - val sslContext = SSLContext.getInstance("TLS").apply { - init( - connectorConfig.keyManagerFactory().keyManagers, - connectorConfig.trustManagerFactory()?.trustManagers, - null - ) - }; - - { socket, context -> - val engine = sslContext.createSSLEngine().apply { - useClientMode = false - if (connectorConfig.hasTrustStore()) { - needClientAuth = true - } + val config = TLSConfig(isClient = false) { + trustManager = connectorConfig.trustManagerFactory()?.trustManagers?.firstOrNull() + authentication(connectorConfig.privateKeyPassword) { + keyStore(connectorConfig.resolveKeyStore()) } - - socket.tls(context, engine) - } + }; + { socket, context -> socket.tls(context, config) } } else -> { socket, _ -> socket } } @@ -63,18 +50,12 @@ private fun EngineSSLConnectorConfig.trustManagerFactory(): TrustManagerFactory? } } -private fun EngineSSLConnectorConfig.keyManagerFactory(): KeyManagerFactory { - val keyStore = keyStorePath?.let { file -> - FileInputStream(file).use { fis -> - val password = keyStorePassword() - val instance = KeyStore.getInstance("JKS").also { it.load(fis, password) } - password.fill('\u0000') - instance - } - } ?: keyStore - val kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()) - val password = privateKeyPassword() - kmf.init(keyStore, password) - password.fill('\u0000') - return kmf -} +//TODO: make it public and move to core +private fun EngineSSLConnectorConfig.resolveKeyStore(): KeyStore = keyStorePath?.let { file -> + FileInputStream(file).use { fis -> + val password = keyStorePassword() + val instance = KeyStore.getInstance("JKS").also { it.load(fis, password) } + password.fill('\u0000') + instance + } +} ?: keyStore diff --git a/ktor-server/ktor-server-cio/jvmAndNix/src/io/ktor/server/cio/HttpServer.kt b/ktor-server/ktor-server-cio/jvmAndNix/src/io/ktor/server/cio/HttpServer.kt index 67c59424e20..4ae0b6dc9dd 100644 --- a/ktor-server/ktor-server-cio/jvmAndNix/src/io/ktor/server/cio/HttpServer.kt +++ b/ktor-server/ktor-server-cio/jvmAndNix/src/io/ktor/server/cio/HttpServer.kt @@ -34,7 +34,8 @@ public data class HttpServerSettings( val host: String = "0.0.0.0", val port: Int = 8080, val connectionIdleTimeoutSeconds: Long = 45, - val interceptor: (socket: Socket, context: CoroutineContext) -> Socket = { socket, _ -> socket } + //TODO: pass TLSConfig here + val interceptor: suspend (socket: Socket, context: CoroutineContext) -> Socket = { socket, _ -> socket } ) /** From b7425fcd6008f797580a6d48223887b1438bf495 Mon Sep 17 00:00:00 2001 From: olme04 Date: Sat, 9 Apr 2022 11:21:49 +0300 Subject: [PATCH 7/9] add TLS verification support --- .../io/ktor/network/tls/PKCS12Certificate.kt | 0 .../network/tls/TLSConfigBuilderDarwin.kt | 26 ++++- .../io/ktor/network/tls/TLSConfigDarwin.kt | 15 ++- .../jvm/src/io/ktor/network/tls/TLS.kt | 42 ++++--- .../io/ktor/network/tls/TLSConfigBuilder.kt | 72 ++++++++++-- .../src/io/ktor/network/tls/TLSConfigJvm.kt | 17 ++- .../ktor/network/tls/tests/ConnectionTest.kt | 7 +- .../src/io/ktor/network/tls/TLSCommon.kt | 15 ++- .../src/io/ktor/network/tls/TLSConfig.kt | 7 +- .../network/tls/TLSConfigBuilderCommon.kt | 10 +- .../jvmAndNix/test-resources/client.p12 | Bin 0 -> 5597 bytes .../jvmAndNix/test-resources/commonkey.p12 | Bin 5669 -> 0 bytes .../jvmAndNix/test-resources/server.p12 | Bin 0 -> 5677 bytes .../network/tls/TlsClientServerSocketTest.kt | 68 +++++++++++- .../ktor/network/tls/TlsClientSocketTest.kt | 2 + .../linuxX64/interop/openssl.def | 6 +- .../src/io/ktor/network/tls/PKCS12.kt | 54 --------- .../io/ktor/network/tls/PKCS12Certificate.kt | 62 +++++++++++ ...erLinuxX64.kt => TLSConfigBuilderLinux.kt} | 26 ++++- .../src/io/ktor/network/tls/TLSConfigLinux.kt | 103 ++++++++++++++++++ .../io/ktor/network/tls/TLSConfigLinuxX64.kt | 70 ------------ .../tls/{TlsLinuxX64.kt => TLSLinux.kt} | 19 +++- .../common/src/io/ktor/util/PlatformUtils.kt | 2 + .../src/io/ktor/util/PlatformUtilsDarwin.kt} | 2 + .../js/src/io/ktor/util/PlatformUtilsJs.kt | 2 + .../jvm/src/io/ktor/util/PlatformUtilsJvm.kt | 2 + .../src/io/ktor/util/PlatformUtilsLinux.kt | 20 ++++ .../src/io/ktor/util/PlatformUtilsMingw.kt | 20 ++++ 28 files changed, 499 insertions(+), 170 deletions(-) rename ktor-network/ktor-network-tls/{nix => darwin}/src/io/ktor/network/tls/PKCS12Certificate.kt (100%) create mode 100644 ktor-network/ktor-network-tls/jvmAndNix/test-resources/client.p12 delete mode 100644 ktor-network/ktor-network-tls/jvmAndNix/test-resources/commonkey.p12 create mode 100644 ktor-network/ktor-network-tls/jvmAndNix/test-resources/server.p12 delete mode 100644 ktor-network/ktor-network-tls/linuxX64/src/io/ktor/network/tls/PKCS12.kt create mode 100644 ktor-network/ktor-network-tls/linuxX64/src/io/ktor/network/tls/PKCS12Certificate.kt rename ktor-network/ktor-network-tls/linuxX64/src/io/ktor/network/tls/{TLSConfigBuilderLinuxX64.kt => TLSConfigBuilderLinux.kt} (63%) create mode 100644 ktor-network/ktor-network-tls/linuxX64/src/io/ktor/network/tls/TLSConfigLinux.kt delete mode 100644 ktor-network/ktor-network-tls/linuxX64/src/io/ktor/network/tls/TLSConfigLinuxX64.kt rename ktor-network/ktor-network-tls/linuxX64/src/io/ktor/network/tls/{TlsLinuxX64.kt => TLSLinux.kt} (54%) rename ktor-utils/{posix/src/io/ktor/util/PlatformUtilsNative.kt => darwin/src/io/ktor/util/PlatformUtilsDarwin.kt} (86%) create mode 100644 ktor-utils/linuxX64/src/io/ktor/util/PlatformUtilsLinux.kt create mode 100644 ktor-utils/mingwX64/src/io/ktor/util/PlatformUtilsMingw.kt diff --git a/ktor-network/ktor-network-tls/nix/src/io/ktor/network/tls/PKCS12Certificate.kt b/ktor-network/ktor-network-tls/darwin/src/io/ktor/network/tls/PKCS12Certificate.kt similarity index 100% rename from ktor-network/ktor-network-tls/nix/src/io/ktor/network/tls/PKCS12Certificate.kt rename to ktor-network/ktor-network-tls/darwin/src/io/ktor/network/tls/PKCS12Certificate.kt diff --git a/ktor-network/ktor-network-tls/darwin/src/io/ktor/network/tls/TLSConfigBuilderDarwin.kt b/ktor-network/ktor-network-tls/darwin/src/io/ktor/network/tls/TLSConfigBuilderDarwin.kt index b57d452a8f5..d289d852595 100644 --- a/ktor-network/ktor-network-tls/darwin/src/io/ktor/network/tls/TLSConfigBuilderDarwin.kt +++ b/ktor-network/ktor-network-tls/darwin/src/io/ktor/network/tls/TLSConfigBuilderDarwin.kt @@ -9,6 +9,7 @@ package io.ktor.network.tls */ public actual class TLSConfigBuilder actual constructor(private val isClient: Boolean) { private var authenticationBuilder: TLSAuthenticationConfigBuilder? = null + private var verificationBuilder: TLSVerificationConfigBuilder? = null /** * Custom server name for TLS server name extension. @@ -23,6 +24,12 @@ public actual class TLSConfigBuilder actual constructor(private val isClient: Bo authenticationBuilder = TLSAuthenticationConfigBuilder(privateKeyPassword).apply(block) } + public actual fun verification( + block: TLSVerificationConfigBuilder.() -> Unit + ) { + verificationBuilder = TLSVerificationConfigBuilder().apply(block) + } + public actual fun takeFrom(other: TLSConfigBuilder) { serverName = other.serverName authenticationBuilder = other.authenticationBuilder @@ -31,7 +38,12 @@ public actual class TLSConfigBuilder actual constructor(private val isClient: Bo /** * Create [TLSConfig]. */ - public actual fun build(): TLSConfig = TLSConfig(isClient, serverName, authenticationBuilder?.build()) + public actual fun build(): TLSConfig = TLSConfig( + isClient = isClient, + serverName = serverName, + authentication = authenticationBuilder?.build(), + verification = verificationBuilder?.build() + ) } public actual class TLSAuthenticationConfigBuilder actual constructor( @@ -48,3 +60,15 @@ public actual class TLSAuthenticationConfigBuilder actual constructor( privateKeyPassword ) } + +public actual class TLSVerificationConfigBuilder { + private var certificate: PKCS12Certificate? = null + + public actual fun pkcs12Certificate(certificatePath: String, certificatePassword: (() -> CharArray)?) { + certificate = PKCS12Certificate(certificatePath, certificatePassword) + } + + public actual fun build(): TLSVerificationConfig = TLSVerificationConfig( + certificate + ) +} diff --git a/ktor-network/ktor-network-tls/darwin/src/io/ktor/network/tls/TLSConfigDarwin.kt b/ktor-network/ktor-network-tls/darwin/src/io/ktor/network/tls/TLSConfigDarwin.kt index 5846afce0a0..92c14b121dd 100644 --- a/ktor-network/ktor-network-tls/darwin/src/io/ktor/network/tls/TLSConfigDarwin.kt +++ b/ktor-network/ktor-network-tls/darwin/src/io/ktor/network/tls/TLSConfigDarwin.kt @@ -5,13 +5,24 @@ package io.ktor.network.tls +import io.ktor.utils.io.core.* + public actual class TLSConfig( public actual val isClient: Boolean, public actual val serverName: String?, - public actual val authentication: TLSAuthenticationConfig? -) + public actual val authentication: TLSAuthenticationConfig?, + public actual val verification: TLSVerificationConfig?, +) : Closeable { + override fun close() { + //NOOP + } +} public actual class TLSAuthenticationConfig( public val certificate: PKCS12Certificate?, public val privateKeyPassword: () -> CharArray ) + +public actual class TLSVerificationConfig( + public val certificate: PKCS12Certificate?, +) diff --git a/ktor-network/ktor-network-tls/jvm/src/io/ktor/network/tls/TLS.kt b/ktor-network/ktor-network-tls/jvm/src/io/ktor/network/tls/TLS.kt index a0ec2710aea..a3518f48dbf 100644 --- a/ktor-network/ktor-network-tls/jvm/src/io/ktor/network/tls/TLS.kt +++ b/ktor-network/ktor-network-tls/jvm/src/io/ktor/network/tls/TLS.kt @@ -16,29 +16,41 @@ import kotlin.coroutines.* public actual suspend fun Connection.tls( coroutineContext: CoroutineContext, config: TLSConfig -): Socket = when (config.isClient && config.authentication == null) { - true -> try { - openTLSSession(socket, input, output, config, coroutineContext) - } catch (cause: Throwable) { - input.cancel(cause) - output.close(cause) - socket.close() - throw cause +): Socket { + + //using old implementation for now if new configuration is not used + if (config.isClient && config.authentication == null && config.verification == null) { + return try { + openTLSSession(socket, input, output, config, coroutineContext) + } catch (cause: Throwable) { + input.cancel(cause) + output.close(cause) + socket.close() + throw cause + } } - false -> { - val engine = when (val address = socket.remoteAddress) { - is UnixSocketAddress -> config.sslContext.createSSLEngine() - is InetSocketAddress -> config.sslContext.createSSLEngine( + + //TODO: servername is not validated + //TODO: client auth doesn't check if certificate is specific to server or client + + val engine = when (val address = socket.remoteAddress) { + is UnixSocketAddress -> config.sslContext.createSSLEngine() + is InetSocketAddress -> { + config.sslContext.createSSLEngine( config.serverName ?: address.hostname, address.port ) } + } - //TODO: we can also set cipherSuites here - engine.useClientMode = config.isClient + //TODO: we can also set cipherSuites here + engine.useClientMode = config.isClient - SSLEngineSocket(coroutineContext, engine, this) + if (!config.isClient) { + engine.needClientAuth = config.verification != null } + + return SSLEngineSocket(coroutineContext, engine, this) } /** diff --git a/ktor-network/ktor-network-tls/jvm/src/io/ktor/network/tls/TLSConfigBuilder.kt b/ktor-network/ktor-network-tls/jvm/src/io/ktor/network/tls/TLSConfigBuilder.kt index 724381a3c2d..4b1491d9c7e 100644 --- a/ktor-network/ktor-network-tls/jvm/src/io/ktor/network/tls/TLSConfigBuilder.kt +++ b/ktor-network/ktor-network-tls/jvm/src/io/ktor/network/tls/TLSConfigBuilder.kt @@ -16,6 +16,7 @@ import javax.net.ssl.* */ public actual class TLSConfigBuilder actual constructor(private val isClient: Boolean) { private var authenticationBuilder: TLSAuthenticationConfigBuilder? = null + private var verificationBuilder: TLSVerificationConfigBuilder? = null /** * List of client certificate chains with private keys. @@ -61,6 +62,12 @@ public actual class TLSConfigBuilder actual constructor(private val isClient: Bo authenticationBuilder = TLSAuthenticationConfigBuilder(privateKeyPassword).apply(block) } + public actual fun verification( + block: TLSVerificationConfigBuilder.() -> Unit + ) { + verificationBuilder = TLSVerificationConfigBuilder().apply(block) + } + /** * Append config from [other] builder. */ @@ -83,7 +90,8 @@ public actual class TLSConfigBuilder actual constructor(private val isClient: Bo cipherSuites = cipherSuites, isClient = isClient, serverName = serverName, - authentication = authenticationBuilder?.build() + authentication = authenticationBuilder?.build(), + verification = verificationBuilder?.build() ) } @@ -114,13 +122,63 @@ public actual class TLSAuthenticationConfigBuilder actual constructor( this.keyStore = keyStore } - public actual fun build(): TLSAuthenticationConfig = TLSAuthenticationConfig( - KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()).apply { - val password = privateKeyPassword() - init(keyStore, password) - password.fill('\u0000') + public actual fun build(): TLSAuthenticationConfig { + //TODO: what is the best place to put this as SSLContext doesn't check it + keyStore?.apply { + aliases().toList().forEach { + (getCertificate(it) as? X509Certificate)?.checkValidity() + } } - ) + + return TLSAuthenticationConfig( + KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()).apply { + val password = privateKeyPassword() + init(keyStore, password) + password.fill('\u0000') + } + ) + } +} + +public actual class TLSVerificationConfigBuilder { + private var keyStore: KeyStore? = null + + public actual fun pkcs12Certificate( + certificatePath: String, + certificatePassword: (() -> CharArray)? + ) { + pkcs12Certificate(File(certificatePath), certificatePassword) + } + + public fun pkcs12Certificate( + certificatePath: File, + certificatePassword: (() -> CharArray)? = null + ) { + keyStore = KeyStore.getInstance("PKCS12").apply { + val password = certificatePassword?.invoke() + load(certificatePath.inputStream(), password) + password?.fill('\u0000') + } + } + + public fun trustStore(keyStore: KeyStore) { + this.keyStore = keyStore + } + + public actual fun build(): TLSVerificationConfig { + //TODO: what is the best place to put this as SSLContext doesn't check it + keyStore?.apply { + aliases().toList().forEach { + (getCertificate(it) as? X509Certificate)?.checkValidity() + } + } + + return TLSVerificationConfig( + TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()).apply { + init(keyStore) + } + ) + } } /** diff --git a/ktor-network/ktor-network-tls/jvm/src/io/ktor/network/tls/TLSConfigJvm.kt b/ktor-network/ktor-network-tls/jvm/src/io/ktor/network/tls/TLSConfigJvm.kt index 81ab4e30bbc..d1c3aaf3b09 100644 --- a/ktor-network/ktor-network-tls/jvm/src/io/ktor/network/tls/TLSConfigJvm.kt +++ b/ktor-network/ktor-network-tls/jvm/src/io/ktor/network/tls/TLSConfigJvm.kt @@ -4,6 +4,7 @@ package io.ktor.network.tls +import io.ktor.utils.io.core.* import java.security.* import java.security.cert.* import javax.net.ssl.* @@ -23,23 +24,33 @@ public actual class TLSConfig( public val cipherSuites: List, public actual val isClient: Boolean, public actual val serverName: String?, - public actual val authentication: TLSAuthenticationConfig? -) { + public actual val authentication: TLSAuthenticationConfig?, + public actual val verification: TLSVerificationConfig?, +) : Closeable { public val sslContext: SSLContext by lazy { SSLContext.getInstance("TLS").apply { init( authentication?.keyManagerFactory?.keyManagers, - arrayOf(trustManager), + verification?.trustManagerFactory?.trustManagers ?: arrayOf(trustManager), random ) } } + + override fun close() { + //NOOP + } } +//TODO: migrate to just trustManager and keyManager? public actual class TLSAuthenticationConfig( public val keyManagerFactory: KeyManagerFactory ) +public actual class TLSVerificationConfig( + public val trustManagerFactory: TrustManagerFactory +) + /** * Client certificate chain with private key. * @property certificateChain: client certificate chain. diff --git a/ktor-network/ktor-network-tls/jvm/test/io/ktor/network/tls/tests/ConnectionTest.kt b/ktor-network/ktor-network-tls/jvm/test/io/ktor/network/tls/tests/ConnectionTest.kt index cb74e75d707..7434c59276c 100644 --- a/ktor-network/ktor-network-tls/jvm/test/io/ktor/network/tls/tests/ConnectionTest.kt +++ b/ktor-network/ktor-network-tls/jvm/test/io/ktor/network/tls/tests/ConnectionTest.kt @@ -87,10 +87,9 @@ class ConnectionTest { tcp.connect(serverSocket.localAddress) .tls(Dispatchers.Default, isClient = true) { - trustManager = - TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()).apply { - init(keyStore) - }.trustManagers.first() + verification { + trustStore(keyStore) + } } .use { socket -> socket.openWriteChannel().apply { diff --git a/ktor-network/ktor-network-tls/jvmAndNix/src/io/ktor/network/tls/TLSCommon.kt b/ktor-network/ktor-network-tls/jvmAndNix/src/io/ktor/network/tls/TLSCommon.kt index a6ca1f356e1..94348567796 100644 --- a/ktor-network/ktor-network-tls/jvmAndNix/src/io/ktor/network/tls/TLSCommon.kt +++ b/ktor-network/ktor-network-tls/jvmAndNix/src/io/ktor/network/tls/TLSCommon.kt @@ -22,7 +22,7 @@ public suspend fun Socket.tls( coroutineContext: CoroutineContext, isClient: Boolean = true, block: TLSConfigBuilder.() -> Unit = {} -): Socket = tls(coroutineContext, TLSConfig(isClient, block)) +): Socket = tls(coroutineContext, createTLSConfig(isClient, block)) /** * Make [Socket] connection secure with TLS using [TLSConfig]. @@ -39,4 +39,15 @@ public suspend fun Connection.tls( coroutineContext: CoroutineContext, isClient: Boolean = true, block: TLSConfigBuilder.() -> Unit -): Socket = tls(coroutineContext, TLSConfig(isClient, block)) +): Socket = tls(coroutineContext, socket.createTLSConfig(isClient, block)) + +private fun Socket.createTLSConfig( + isClient: Boolean = true, + block: TLSConfigBuilder.() -> Unit +): TLSConfig { + val config = TLSConfig(isClient, block) + socketContext.invokeOnCompletion { + config.close() + } + return config +} diff --git a/ktor-network/ktor-network-tls/jvmAndNix/src/io/ktor/network/tls/TLSConfig.kt b/ktor-network/ktor-network-tls/jvmAndNix/src/io/ktor/network/tls/TLSConfig.kt index c7b4372b28b..2cacb975576 100644 --- a/ktor-network/ktor-network-tls/jvmAndNix/src/io/ktor/network/tls/TLSConfig.kt +++ b/ktor-network/ktor-network-tls/jvmAndNix/src/io/ktor/network/tls/TLSConfig.kt @@ -4,15 +4,20 @@ package io.ktor.network.tls +import io.ktor.utils.io.core.* + //TODO: make it closeable -public expect class TLSConfig { +public expect class TLSConfig: Closeable { public val isClient: Boolean public val serverName: String? public val authentication: TLSAuthenticationConfig? + public val verification: TLSVerificationConfig? } public expect class TLSAuthenticationConfig +public expect class TLSVerificationConfig + public fun TLSConfig( isClient: Boolean = true, block: TLSConfigBuilder.() -> Unit diff --git a/ktor-network/ktor-network-tls/jvmAndNix/src/io/ktor/network/tls/TLSConfigBuilderCommon.kt b/ktor-network/ktor-network-tls/jvmAndNix/src/io/ktor/network/tls/TLSConfigBuilderCommon.kt index 2ececc71398..1aa92e59e91 100644 --- a/ktor-network/ktor-network-tls/jvmAndNix/src/io/ktor/network/tls/TLSConfigBuilderCommon.kt +++ b/ktor-network/ktor-network-tls/jvmAndNix/src/io/ktor/network/tls/TLSConfigBuilderCommon.kt @@ -7,13 +7,12 @@ package io.ktor.network.tls /** * [TLSConfig] builder. */ -//TODO: add verification - alternative to JVM trust manager public expect class TLSConfigBuilder(isClient: Boolean = true) { /** * Custom server name for TLS server name extension. * See also: https://en.wikipedia.org/wiki/Server_Name_Indication */ - //TODO: rename to peerName? + //TODO public var serverName: String? //if used, on JVM, SSLEngine will be used, instead of handwritten implementation @@ -21,6 +20,8 @@ public expect class TLSConfigBuilder(isClient: Boolean = true) { //`certificates` on JVM should be empty public fun authentication(privateKeyPassword: () -> CharArray, block: TLSAuthenticationConfigBuilder.() -> Unit) + public fun verification(block: TLSVerificationConfigBuilder.() -> Unit) + /** * Append config from [other] builder. */ @@ -38,3 +39,8 @@ public expect class TLSAuthenticationConfigBuilder( public fun pkcs12Certificate(certificatePath: String, certificatePassword: (() -> CharArray)? = null) public fun build(): TLSAuthenticationConfig } + +public expect class TLSVerificationConfigBuilder { + public fun pkcs12Certificate(certificatePath: String, certificatePassword: (() -> CharArray)? = null) + public fun build(): TLSVerificationConfig +} diff --git a/ktor-network/ktor-network-tls/jvmAndNix/test-resources/client.p12 b/ktor-network/ktor-network-tls/jvmAndNix/test-resources/client.p12 new file mode 100644 index 0000000000000000000000000000000000000000..eed1708e8e7929b5c7fdc86b2b54341de839ec83 GIT binary patch literal 5597 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 diff --git a/ktor-network/ktor-network-tls/jvmAndNix/test-resources/server.p12 b/ktor-network/ktor-network-tls/jvmAndNix/test-resources/server.p12 new file mode 100644 index 0000000000000000000000000000000000000000..cee37ac9393f409618ed4fde9c294c9be35b38d6 GIT binary patch literal 5677 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

WrKDLBG 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 + val tcp = aSocket(selector).tcp() + + tcp.bind().use { serverSocket -> + val serverJob = GlobalScope.launch { + while (true) serverSocket.accept() + .tls(Dispatchers.Default, isClient = false) { + authentication(privateKeyPassword = { "changeit".toCharArray() }) { + pkcs12Certificate("$testResourcesFolder/server.p12") { "changeit".toCharArray() } + } + } + .use { socket -> + val reader = socket.openReadChannel() + val writer = socket.openWriteChannel() + repeat(3) { + val line = assertNotNull(reader.readUTF8Line()) + println("SSS: $line") + writer.writeStringUtf8("$line\r\n") + writer.flush() + } + delay(2000) //await reading from client socket + } + } + + tcp.connect(serverSocket.localAddress) + .tls(Dispatchers.Default, isClient = true) { + serverName = "localhost" + verification { + pkcs12Certificate("$testResourcesFolder/server.p12") { "changeit".toCharArray() } + } + } + .use { socket -> + socket.openWriteChannel().apply { + writeStringUtf8("GET / HTTP/1.1\r\n") + writeStringUtf8("Host: www.google.com\r\n") + writeStringUtf8("Connection: close\r\n") + flush() + } + val reader = socket.openReadChannel() + repeat(3) { + println("CCC: ${assertNotNull(reader.readUTF8Line())}") + } + } + serverJob.cancelAndJoin() + } + } + } + + @Test + fun testClientServerWithClientAuth() = runBlocking { + if(PlatformUtils.IS_DARWIN) return@runBlocking SelectorManager(Dispatchers.IOBridge).use { selector -> val tcp = aSocket(selector).tcp() @@ -25,8 +78,12 @@ class TlsClientServerSocketTest { val serverJob = GlobalScope.launch { while (true) serverSocket.accept() .tls(Dispatchers.Default + CoroutineName("SERVER"), isClient = false) { + serverName = "localhost" //TODO openssl validate it if client auth is enabled authentication(privateKeyPassword = { "changeit".toCharArray() }) { - pkcs12Certificate("$testResourcesFolder/commonkey.p12") { "changeit".toCharArray() } + pkcs12Certificate("$testResourcesFolder/server.p12") { "changeit".toCharArray() } + } + verification { + pkcs12Certificate("$testResourcesFolder/client.p12") { "changeit".toCharArray() } } } .use { socket -> @@ -44,8 +101,13 @@ class TlsClientServerSocketTest { tcp.connect(serverSocket.localAddress) .tls(Dispatchers.Default + CoroutineName("CLIENT"), isClient = true) { - authentication({ "".toCharArray() }) {} //forces using of SSLEngine - //TODO: ser verification here + serverName = "localhost" + verification { + pkcs12Certificate("$testResourcesFolder/server.p12") { "changeit".toCharArray() } + } + authentication(privateKeyPassword = { "changeit".toCharArray() }) { + pkcs12Certificate("$testResourcesFolder/client.p12") { "changeit".toCharArray() } + } } .use { socket -> socket.openWriteChannel().apply { diff --git a/ktor-network/ktor-network-tls/jvmAndNix/test/io/ktor/network/tls/TlsClientSocketTest.kt b/ktor-network/ktor-network-tls/jvmAndNix/test/io/ktor/network/tls/TlsClientSocketTest.kt index c6e604730bc..2ab7840f264 100644 --- a/ktor-network/ktor-network-tls/jvmAndNix/test/io/ktor/network/tls/TlsClientSocketTest.kt +++ b/ktor-network/ktor-network-tls/jvmAndNix/test/io/ktor/network/tls/TlsClientSocketTest.kt @@ -6,6 +6,7 @@ package io.ktor.network.tls import io.ktor.network.selector.* import io.ktor.network.sockets.* +import io.ktor.util.* import io.ktor.utils.io.* import io.ktor.utils.io.core.* import kotlinx.coroutines.* @@ -17,6 +18,7 @@ class TlsClientSocketTest { @Test fun testGoogleWithoutClose(): Unit = runBlocking { + if (PlatformUtils.IS_DARWIN) return@runBlocking SelectorManager(Dispatchers.IOBridge).use { selector -> aSocket(selector) .tcp() diff --git a/ktor-network/ktor-network-tls/linuxX64/interop/openssl.def b/ktor-network/ktor-network-tls/linuxX64/interop/openssl.def index 9e0c7832b82..09e97253558 100644 --- a/ktor-network/ktor-network-tls/linuxX64/interop/openssl.def +++ b/ktor-network/ktor-network-tls/linuxX64/interop/openssl.def @@ -1,5 +1,5 @@ package = io.ktor.network.tls.internal.openssl -headers = openssl/bio.h openssl/ssl.h openssl/err.h openssl/pkcs12.h openssl/pem.h openssl/x509.h openssl/evp.h +headers = openssl/bio.h openssl/ssl.h openssl/err.h openssl/pkcs12.h openssl/pem.h openssl/x509.h openssl/evp.h openssl/tls1.h headerFilter = openssl/* -linkerOpts.linux = -L/home/linuxbrew/.linuxbrew/lib -lssl -lcrypto -compilerOpts.linux = -I/home/linuxbrew/.linuxbrew/include +linkerOpts = -L/home/linuxbrew/.linuxbrew/lib -lssl -lcrypto +compilerOpts = -I/home/linuxbrew/.linuxbrew/include diff --git a/ktor-network/ktor-network-tls/linuxX64/src/io/ktor/network/tls/PKCS12.kt b/ktor-network/ktor-network-tls/linuxX64/src/io/ktor/network/tls/PKCS12.kt deleted file mode 100644 index 273437cf858..00000000000 --- a/ktor-network/ktor-network-tls/linuxX64/src/io/ktor/network/tls/PKCS12.kt +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2014-2022 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. - */ - -package io.ktor.network.tls - -import kotlinx.cinterop.* -import io.ktor.network.tls.internal.openssl.* -import platform.posix.* - -internal inline fun PKCS12Certificate.parse( - block: ( - privateKey: CPointer, - x509Certificate: CPointer, - ) -> Unit -) { - val pkcs12 = useFile(path, "rb") { file -> - requireNotNull(d2i_PKCS12_fp(file, null)) { "Failed to read PKCS12 file" } - } - try { - val password = password?.invoke() - - val pkey = nativeHeap.allocPointerTo() - val cert = nativeHeap.allocPointerTo() - - try { - //TODO reading CA certificates - require(PKCS12_parse(pkcs12, password?.concatToString(), pkey.ptr, cert.ptr, null) == 1) { - "Failed to parse PKCS12 file" - } - block(pkey.value!!, cert.value!!) - } catch (cause: Throwable) { - EVP_PKEY_free(pkey.value) - X509_free(cert.value) - password?.fill('\u0000') - throw cause - } - } finally { - PKCS12_free(pkcs12) - } -} - -private fun useFile( - path: String, - modes: String?, - block: (CPointer) -> T -): T { - val file = requireNotNull(fopen(path, modes)) { "Failed to open file: $path" } - try { - return block(file) - } finally { - fclose(file) - } -} diff --git a/ktor-network/ktor-network-tls/linuxX64/src/io/ktor/network/tls/PKCS12Certificate.kt b/ktor-network/ktor-network-tls/linuxX64/src/io/ktor/network/tls/PKCS12Certificate.kt new file mode 100644 index 00000000000..93a3b01e5a5 --- /dev/null +++ b/ktor-network/ktor-network-tls/linuxX64/src/io/ktor/network/tls/PKCS12Certificate.kt @@ -0,0 +1,62 @@ +/* + * Copyright 2014-2022 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package io.ktor.network.tls + +import io.ktor.network.tls.internal.openssl.* +import io.ktor.utils.io.core.* +import kotlinx.cinterop.* +import platform.posix.* + +public class PKCS12Certificate( + public val privateKey: CPointer, + public val x509Certificate: CPointer +) : Closeable { + + override fun close() { + EVP_PKEY_free(privateKey) + X509_free(x509Certificate) + } +} + +internal fun PKCS12Certificate(certificatePath: String, certificatePassword: (() -> CharArray)?): PKCS12Certificate { + val pkcs12 = useFile(certificatePath, "rb") { file -> + requireNotNull(d2i_PKCS12_fp(file, null)) { "Failed to read PKCS12 file" } + } + try { + val charPassword = certificatePassword?.invoke() + val password = charPassword?.concatToString() ?: "" //empty password for openssl is absence of password + charPassword?.fill('\u0000') + + val privateKey = nativeHeap.allocPointerTo() + val x509Certificate = nativeHeap.allocPointerTo() + + try { + //TODO reading CA certificates + require(PKCS12_parse(pkcs12, password, privateKey.ptr, x509Certificate.ptr, null) == 1) { + "Failed to parse PKCS12 file" + } + return PKCS12Certificate(privateKey.value!!, x509Certificate.value!!) + } catch (cause: Throwable) { + EVP_PKEY_free(privateKey.value) + X509_free(x509Certificate.value) + throw cause + } + } finally { + PKCS12_free(pkcs12) + } +} + +private fun useFile( + path: String, + modes: String?, + block: (CPointer) -> T +): T { + val file = requireNotNull(fopen(path, modes)) { "Failed to open file: $path" } + try { + return block(file) + } finally { + fclose(file) + } +} diff --git a/ktor-network/ktor-network-tls/linuxX64/src/io/ktor/network/tls/TLSConfigBuilderLinuxX64.kt b/ktor-network/ktor-network-tls/linuxX64/src/io/ktor/network/tls/TLSConfigBuilderLinux.kt similarity index 63% rename from ktor-network/ktor-network-tls/linuxX64/src/io/ktor/network/tls/TLSConfigBuilderLinuxX64.kt rename to ktor-network/ktor-network-tls/linuxX64/src/io/ktor/network/tls/TLSConfigBuilderLinux.kt index b57d452a8f5..d289d852595 100644 --- a/ktor-network/ktor-network-tls/linuxX64/src/io/ktor/network/tls/TLSConfigBuilderLinuxX64.kt +++ b/ktor-network/ktor-network-tls/linuxX64/src/io/ktor/network/tls/TLSConfigBuilderLinux.kt @@ -9,6 +9,7 @@ package io.ktor.network.tls */ public actual class TLSConfigBuilder actual constructor(private val isClient: Boolean) { private var authenticationBuilder: TLSAuthenticationConfigBuilder? = null + private var verificationBuilder: TLSVerificationConfigBuilder? = null /** * Custom server name for TLS server name extension. @@ -23,6 +24,12 @@ public actual class TLSConfigBuilder actual constructor(private val isClient: Bo authenticationBuilder = TLSAuthenticationConfigBuilder(privateKeyPassword).apply(block) } + public actual fun verification( + block: TLSVerificationConfigBuilder.() -> Unit + ) { + verificationBuilder = TLSVerificationConfigBuilder().apply(block) + } + public actual fun takeFrom(other: TLSConfigBuilder) { serverName = other.serverName authenticationBuilder = other.authenticationBuilder @@ -31,7 +38,12 @@ public actual class TLSConfigBuilder actual constructor(private val isClient: Bo /** * Create [TLSConfig]. */ - public actual fun build(): TLSConfig = TLSConfig(isClient, serverName, authenticationBuilder?.build()) + public actual fun build(): TLSConfig = TLSConfig( + isClient = isClient, + serverName = serverName, + authentication = authenticationBuilder?.build(), + verification = verificationBuilder?.build() + ) } public actual class TLSAuthenticationConfigBuilder actual constructor( @@ -48,3 +60,15 @@ public actual class TLSAuthenticationConfigBuilder actual constructor( privateKeyPassword ) } + +public actual class TLSVerificationConfigBuilder { + private var certificate: PKCS12Certificate? = null + + public actual fun pkcs12Certificate(certificatePath: String, certificatePassword: (() -> CharArray)?) { + certificate = PKCS12Certificate(certificatePath, certificatePassword) + } + + public actual fun build(): TLSVerificationConfig = TLSVerificationConfig( + certificate + ) +} diff --git a/ktor-network/ktor-network-tls/linuxX64/src/io/ktor/network/tls/TLSConfigLinux.kt b/ktor-network/ktor-network-tls/linuxX64/src/io/ktor/network/tls/TLSConfigLinux.kt new file mode 100644 index 00000000000..36506b0e06b --- /dev/null +++ b/ktor-network/ktor-network-tls/linuxX64/src/io/ktor/network/tls/TLSConfigLinux.kt @@ -0,0 +1,103 @@ +// ktlint-disable filename +/* + * Copyright 2014-2019 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package io.ktor.network.tls + +import io.ktor.network.tls.internal.openssl.* +import io.ktor.utils.io.core.* +import kotlinx.cinterop.* +import platform.posix.* + +public actual class TLSConfig( + public actual val isClient: Boolean, + public actual val serverName: String?, + public actual val authentication: TLSAuthenticationConfig?, + public actual val verification: TLSVerificationConfig?, +) : Closeable { + private val _sslContext = lazy { + val context = SSL_CTX_new(TLS_method())!! + + authentication?.configure(context) + verification?.configure(context) + + context + } + + public val sslContext: CPointer by _sslContext + + override fun close() { + authentication?.close() + verification?.close() + if (_sslContext.isInitialized()) { + SSL_CTX_free(_sslContext.value) + } + } +} + +public actual class TLSAuthenticationConfig( + public val certificate: PKCS12Certificate?, + public val privateKeyPassword: () -> CharArray +) : Closeable { + override fun close() { + certificate?.close() + } + + internal fun configure(context: CPointer) { + if (certificate == null) return + + val charPassword = privateKeyPassword.invoke() + val password = charPassword.concatToString() + charPassword.fill('\u0000') + + SSL_CTX_set_default_passwd_cb_userdata(context, password.refTo(0)) + SSL_CTX_set_default_passwd_cb(context, passwordCheckFunction) + check( + SSL_CTX_use_certificate(context, certificate.x509Certificate) == 1 + ) { "Failed to set X509 certificate for authentication" } + check( + SSL_CTX_use_PrivateKey(context, certificate.privateKey) == 1 + ) { "Failed to set EVP private key for authentication" } + } +} + +public actual class TLSVerificationConfig( + public val certificate: PKCS12Certificate?, +) : Closeable { + override fun close() { + certificate?.close() + } + + internal fun configure(context: CPointer) { + if (certificate == null) { + SSL_CTX_set_verify(context, SSL_VERIFY_NONE, null) + return + } + + SSL_CTX_set_verify(context, SSL_VERIFY_PEER or SSL_VERIFY_FAIL_IF_NO_PEER_CERT, verificationFunction) + + //store will be automatically freed when context is freed - TODO: how to check it? + val store = X509_STORE_new()!! + + check( + X509_STORE_add_cert(store, certificate.x509Certificate) == 1 + ) { "Failed to set X509 certificate for verification" } + + check( + SSL_CTX_ctrl(context, SSL_CTRL_SET_VERIFY_CERT_STORE, 1, store) == 1L + ) { "Failed to set X509 certificate store for verification" } + } +} + +private val passwordCheckFunction: CPointer = staticCFunction { buf, size, rwFlag, u -> + val stringPassword = u?.asStableRef()?.get() ?: return@staticCFunction 0 + memcpy(buf, stringPassword.encodeToByteArray().refTo(0), stringPassword.length.convert()) + stringPassword.length +} + +private val verificationFunction: SSL_verify_cb = staticCFunction { preverifyOk, store -> + //TODO: somehow pass this error outside + println(X509_verify_cert_error_string(X509_STORE_CTX_get_error(store).toLong())?.toKString()) + preverifyOk +} diff --git a/ktor-network/ktor-network-tls/linuxX64/src/io/ktor/network/tls/TLSConfigLinuxX64.kt b/ktor-network/ktor-network-tls/linuxX64/src/io/ktor/network/tls/TLSConfigLinuxX64.kt deleted file mode 100644 index b9eeca7b6c1..00000000000 --- a/ktor-network/ktor-network-tls/linuxX64/src/io/ktor/network/tls/TLSConfigLinuxX64.kt +++ /dev/null @@ -1,70 +0,0 @@ -// ktlint-disable filename -/* - * Copyright 2014-2019 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. - */ - -package io.ktor.network.tls - -import io.ktor.network.tls.internal.openssl.* -import io.ktor.utils.io.core.* -import kotlinx.cinterop.* -import platform.posix.* - -public actual class TLSConfig( - public actual val isClient: Boolean, - public actual val serverName: String?, - public actual val authentication: TLSAuthenticationConfig? -) : Closeable { - private var privateKey: CPointer? = null - private var x509Certificate: CPointer? = null - private val _sslContext = lazy { - val context = SSL_CTX_new(TLS_method())!! - - //TODO: set peer - SSL_CTX_set_verify(context, SSL_VERIFY_NONE, null) - - if (authentication?.certificate != null) { - val privateKeyPassword = authentication.privateKeyPassword.invoke() - val passwordRef = StableRef.create(privateKeyPassword.concatToString()) - privateKeyPassword.fill('\u0000') - try { - SSL_CTX_set_default_passwd_cb_userdata(context, passwordRef.asCPointer()) - SSL_CTX_set_default_passwd_cb(context, passwordCheckFunction) - - authentication.certificate.parse { privateKey, x509Certificate -> - check(SSL_CTX_use_certificate(context, x509Certificate) == 1) { "Failed to set X509 certificate" } - check(SSL_CTX_use_PrivateKey(context, privateKey) == 1) { "Failed to set EVP private key" } - - this.privateKey = privateKey - this.x509Certificate = x509Certificate - } - } finally { - passwordRef.dispose() - } - } - context - } - - public val sslContext: CPointer by _sslContext - - override fun close() { - if (_sslContext.isInitialized()) { - EVP_PKEY_free(privateKey) - X509_free(x509Certificate) - SSL_CTX_free(_sslContext.value) - } - } -} - -public actual class TLSAuthenticationConfig( - public val certificate: PKCS12Certificate?, - public val privateKeyPassword: () -> CharArray -) - -private val passwordCheckFunction: CPointer = staticCFunction { buf, size, rwFlag, u -> - val stringPassword = u?.asStableRef()?.get() ?: return@staticCFunction 0 - stringPassword.encodeToByteArray().usePinned { - memcpy(buf, it.addressOf(0), stringPassword.length.convert()) - } - stringPassword.length -} diff --git a/ktor-network/ktor-network-tls/linuxX64/src/io/ktor/network/tls/TlsLinuxX64.kt b/ktor-network/ktor-network-tls/linuxX64/src/io/ktor/network/tls/TLSLinux.kt similarity index 54% rename from ktor-network/ktor-network-tls/linuxX64/src/io/ktor/network/tls/TlsLinuxX64.kt rename to ktor-network/ktor-network-tls/linuxX64/src/io/ktor/network/tls/TLSLinux.kt index f5d10fa1761..492e1afab25 100644 --- a/ktor-network/ktor-network-tls/linuxX64/src/io/ktor/network/tls/TlsLinuxX64.kt +++ b/ktor-network/ktor-network-tls/linuxX64/src/io/ktor/network/tls/TLSLinux.kt @@ -6,6 +6,7 @@ package io.ktor.network.tls import io.ktor.network.sockets.* import io.ktor.network.tls.internal.openssl.* +import kotlinx.cinterop.* import kotlin.coroutines.* public actual suspend fun Connection.tls( @@ -13,12 +14,26 @@ public actual suspend fun Connection.tls( config: TLSConfig ): Socket { val ssl = SSL_new(config.sslContext)!! + when (val address = socket.remoteAddress) { is UnixSocketAddress -> {} - is InetSocketAddress -> SSL_set1_host(ssl, "${address.hostname}:${address.port}") + is InetSocketAddress -> check( + SSL_set1_host(ssl, config.serverName ?: "${address.hostname}:${address.port}") == 1 + ) { "Failed to set host name" } } - //TODO: support setting serverName + //TODO: is it correct?? + if (config.serverName != null) { + //TODO: extract macros to def file + check( + SSL_ctrl( + ssl, + SSL_CTRL_SET_TLSEXT_HOSTNAME, + TLSEXT_NAMETYPE_host_name.toLong(), + config.serverName.refTo(0) + ) == 1L + ) { "Failed to set server name" } + } when (config.isClient) { true -> SSL_set_connect_state(ssl) diff --git a/ktor-utils/common/src/io/ktor/util/PlatformUtils.kt b/ktor-utils/common/src/io/ktor/util/PlatformUtils.kt index e88e577d2fa..2b7dbfad413 100644 --- a/ktor-utils/common/src/io/ktor/util/PlatformUtils.kt +++ b/ktor-utils/common/src/io/ktor/util/PlatformUtils.kt @@ -9,6 +9,8 @@ public expect object PlatformUtils { public val IS_NODE: Boolean public val IS_JVM: Boolean public val IS_NATIVE: Boolean + public val IS_NIX: Boolean + public val IS_DARWIN: Boolean public val IS_DEVELOPMENT_MODE: Boolean diff --git a/ktor-utils/posix/src/io/ktor/util/PlatformUtilsNative.kt b/ktor-utils/darwin/src/io/ktor/util/PlatformUtilsDarwin.kt similarity index 86% rename from ktor-utils/posix/src/io/ktor/util/PlatformUtilsNative.kt rename to ktor-utils/darwin/src/io/ktor/util/PlatformUtilsDarwin.kt index 8a9c8352234..cc207d4f293 100644 --- a/ktor-utils/posix/src/io/ktor/util/PlatformUtilsNative.kt +++ b/ktor-utils/darwin/src/io/ktor/util/PlatformUtilsDarwin.kt @@ -10,6 +10,8 @@ public actual object PlatformUtils { public actual val IS_NODE: Boolean = false public actual val IS_JVM: Boolean = false public actual val IS_NATIVE: Boolean = true + public actual val IS_NIX: Boolean = true + public actual val IS_DARWIN: Boolean = true public actual val IS_DEVELOPMENT_MODE: Boolean = false diff --git a/ktor-utils/js/src/io/ktor/util/PlatformUtilsJs.kt b/ktor-utils/js/src/io/ktor/util/PlatformUtilsJs.kt index 7b05245be1c..a3f0f2b574e 100644 --- a/ktor-utils/js/src/io/ktor/util/PlatformUtilsJs.kt +++ b/ktor-utils/js/src/io/ktor/util/PlatformUtilsJs.kt @@ -16,6 +16,8 @@ public actual object PlatformUtils { public actual val IS_JVM: Boolean = false public actual val IS_NATIVE: Boolean = false + public actual val IS_NIX: Boolean = false + public actual val IS_DARWIN: Boolean = false public actual val IS_DEVELOPMENT_MODE: Boolean = false public actual val IS_NEW_MM_ENABLED: Boolean = true } diff --git a/ktor-utils/jvm/src/io/ktor/util/PlatformUtilsJvm.kt b/ktor-utils/jvm/src/io/ktor/util/PlatformUtilsJvm.kt index 5d0821a5daa..ae9ebfaaedf 100644 --- a/ktor-utils/jvm/src/io/ktor/util/PlatformUtilsJvm.kt +++ b/ktor-utils/jvm/src/io/ktor/util/PlatformUtilsJvm.kt @@ -11,6 +11,8 @@ public actual object PlatformUtils { public actual val IS_NODE: Boolean = false public actual val IS_JVM: Boolean = true public actual val IS_NATIVE: Boolean = false + public actual val IS_NIX: Boolean = false + public actual val IS_DARWIN: Boolean = false public actual val IS_DEVELOPMENT_MODE: Boolean = System.getProperty(DEVELOPMENT_MODE_KEY)?.toBoolean() == true diff --git a/ktor-utils/linuxX64/src/io/ktor/util/PlatformUtilsLinux.kt b/ktor-utils/linuxX64/src/io/ktor/util/PlatformUtilsLinux.kt new file mode 100644 index 00000000000..e3d35e93806 --- /dev/null +++ b/ktor-utils/linuxX64/src/io/ktor/util/PlatformUtilsLinux.kt @@ -0,0 +1,20 @@ +// ktlint-disable filename +/* +* Copyright 2014-2021 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. +*/ + +package io.ktor.util + +public actual object PlatformUtils { + public actual val IS_BROWSER: Boolean = false + public actual val IS_NODE: Boolean = false + public actual val IS_JVM: Boolean = false + public actual val IS_NATIVE: Boolean = true + public actual val IS_NIX: Boolean = true + public actual val IS_DARWIN: Boolean = false + + public actual val IS_DEVELOPMENT_MODE: Boolean = false + + @OptIn(ExperimentalStdlibApi::class) + public actual val IS_NEW_MM_ENABLED: Boolean = isExperimentalMM() +} diff --git a/ktor-utils/mingwX64/src/io/ktor/util/PlatformUtilsMingw.kt b/ktor-utils/mingwX64/src/io/ktor/util/PlatformUtilsMingw.kt new file mode 100644 index 00000000000..225ff11b4e6 --- /dev/null +++ b/ktor-utils/mingwX64/src/io/ktor/util/PlatformUtilsMingw.kt @@ -0,0 +1,20 @@ +// ktlint-disable filename +/* +* Copyright 2014-2021 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. +*/ + +package io.ktor.util + +public actual object PlatformUtils { + public actual val IS_BROWSER: Boolean = false + public actual val IS_NODE: Boolean = false + public actual val IS_JVM: Boolean = false + public actual val IS_NATIVE: Boolean = true + public actual val IS_NIX: Boolean = false + public actual val IS_DARWIN: Boolean = false + + public actual val IS_DEVELOPMENT_MODE: Boolean = false + + @OptIn(ExperimentalStdlibApi::class) + public actual val IS_NEW_MM_ENABLED: Boolean = isExperimentalMM() +} From 3772f17d6c939427aff10c413b5e06f78de62987 Mon Sep 17 00:00:00 2001 From: olme04 Date: Sat, 9 Apr 2022 11:38:44 +0300 Subject: [PATCH 8/9] update CIO server TLS usage --- .../src/io/ktor/client/engine/cio/Endpoint.kt | 1 + ktor-server/ktor-server-cio/build.gradle.kts | 4 -- .../ktor/server/cio/HttpServerSettingsJvm.kt | 57 ++++++++----------- .../src/io/ktor/server/cio/HttpServer.kt | 10 +++- .../io/ktor/server/cio/backend/HttpServer.kt | 11 +++- 5 files changed, 40 insertions(+), 43 deletions(-) diff --git a/ktor-client/ktor-client-cio/jvmAndNix/src/io/ktor/client/engine/cio/Endpoint.kt b/ktor-client/ktor-client-cio/jvmAndNix/src/io/ktor/client/engine/cio/Endpoint.kt index febef41b0d0..80b7e59633f 100644 --- a/ktor-client/ktor-client-cio/jvmAndNix/src/io/ktor/client/engine/cio/Endpoint.kt +++ b/ktor-client/ktor-client-cio/jvmAndNix/src/io/ktor/client/engine/cio/Endpoint.kt @@ -204,6 +204,7 @@ internal class Endpoint( if (proxy?.type == ProxyType.HTTP) { startTunnel(requestData, connection.output, connection.input) } + //TODO: cache TLS config to support session reuse, when using SSLEngine val tlsSocket = connection.tls(coroutineContext) { takeFrom(config.https) serverName = serverName ?: address.hostname diff --git a/ktor-server/ktor-server-cio/build.gradle.kts b/ktor-server/ktor-server-cio/build.gradle.kts index 856bb29fd6b..9f823c94712 100644 --- a/ktor-server/ktor-server-cio/build.gradle.kts +++ b/ktor-server/ktor-server-cio/build.gradle.kts @@ -6,10 +6,6 @@ kotlin.sourceSets { api(project(":ktor-server:ktor-server-host-common")) api(project(":ktor-http:ktor-http-cio")) api(project(":ktor-network")) - } - } - jvmMain { - dependencies { api(project(":ktor-network:ktor-network-tls")) } } diff --git a/ktor-server/ktor-server-cio/jvm/src/io/ktor/server/cio/HttpServerSettingsJvm.kt b/ktor-server/ktor-server-cio/jvm/src/io/ktor/server/cio/HttpServerSettingsJvm.kt index 646e4dad56c..51935547fb3 100644 --- a/ktor-server/ktor-server-cio/jvm/src/io/ktor/server/cio/HttpServerSettingsJvm.kt +++ b/ktor-server/ktor-server-cio/jvm/src/io/ktor/server/cio/HttpServerSettingsJvm.kt @@ -4,51 +4,33 @@ package io.ktor.server.cio -import io.ktor.network.sockets.* import io.ktor.network.tls.* import io.ktor.server.engine.* import java.io.* import java.security.* -import javax.net.ssl.* -import kotlin.coroutines.* internal actual fun HttpServerSettings( connectionIdleTimeoutSeconds: Long, connectorConfig: EngineConnectorConfig -): HttpServerSettings { - val interceptor: suspend (Socket, CoroutineContext) -> Socket = when (connectorConfig) { - is EngineSSLConnectorConfig -> { - val config = TLSConfig(isClient = false) { - trustManager = connectorConfig.trustManagerFactory()?.trustManagers?.firstOrNull() - authentication(connectorConfig.privateKeyPassword) { - keyStore(connectorConfig.resolveKeyStore()) +): HttpServerSettings = HttpServerSettings( + host = connectorConfig.host, + port = connectorConfig.port, + connectionIdleTimeoutSeconds = connectionIdleTimeoutSeconds, + tlsConfig = when (connectorConfig) { + is EngineSSLConnectorConfig -> TLSConfig(isClient = false) { +// serverName = connectorConfig.host //TODO? + authentication(connectorConfig.privateKeyPassword) { + keyStore(connectorConfig.resolveKeyStore()) + } + connectorConfig.resolveTrustStore()?.let { + verification { + trustStore(it) } - }; - { socket, context -> socket.tls(context, config) } + } } - else -> { socket, _ -> socket } + else -> null } - - return HttpServerSettings( - host = connectorConfig.host, - port = connectorConfig.port, - connectionIdleTimeoutSeconds = connectionIdleTimeoutSeconds, - interceptor = interceptor - ) -} - -private fun EngineSSLConnectorConfig.hasTrustStore() = trustStore != null || trustStorePath != null - -private fun EngineSSLConnectorConfig.trustManagerFactory(): TrustManagerFactory? { - val trustStore = trustStore ?: trustStorePath?.let { file -> - FileInputStream(file).use { fis -> - KeyStore.getInstance("JKS").also { it.load(fis, null) } - } - } - return trustStore?.let { store -> - TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()).also { it.init(store) } - } -} +) //TODO: make it public and move to core private fun EngineSSLConnectorConfig.resolveKeyStore(): KeyStore = keyStorePath?.let { file -> @@ -59,3 +41,10 @@ private fun EngineSSLConnectorConfig.resolveKeyStore(): KeyStore = keyStorePath? instance } } ?: keyStore + +//TODO: make it public and move to core +private fun EngineSSLConnectorConfig.resolveTrustStore(): KeyStore? = trustStore ?: trustStorePath?.let { file -> + FileInputStream(file).use { fis -> + KeyStore.getInstance("JKS").also { it.load(fis, null) } + } +} diff --git a/ktor-server/ktor-server-cio/jvmAndNix/src/io/ktor/server/cio/HttpServer.kt b/ktor-server/ktor-server-cio/jvmAndNix/src/io/ktor/server/cio/HttpServer.kt index 4ae0b6dc9dd..57a4fb24895 100644 --- a/ktor-server/ktor-server-cio/jvmAndNix/src/io/ktor/server/cio/HttpServer.kt +++ b/ktor-server/ktor-server-cio/jvmAndNix/src/io/ktor/server/cio/HttpServer.kt @@ -6,6 +6,7 @@ package io.ktor.server.cio import io.ktor.http.cio.* import io.ktor.network.sockets.* +import io.ktor.network.tls.* import io.ktor.server.cio.backend.* import io.ktor.utils.io.* import kotlinx.coroutines.* @@ -34,9 +35,12 @@ public data class HttpServerSettings( val host: String = "0.0.0.0", val port: Int = 8080, val connectionIdleTimeoutSeconds: Long = 45, - //TODO: pass TLSConfig here - val interceptor: suspend (socket: Socket, context: CoroutineContext) -> Socket = { socket, _ -> socket } -) + val tlsConfig: TLSConfig? = null +) { + init { + require(tlsConfig == null || !tlsConfig.isClient) { "TLSConfig should be configured for server" } + } +} /** * Start an http server with [settings] invoking [handler] for every request diff --git a/ktor-server/ktor-server-cio/jvmAndNix/src/io/ktor/server/cio/backend/HttpServer.kt b/ktor-server/ktor-server-cio/jvmAndNix/src/io/ktor/server/cio/backend/HttpServer.kt index 69b667cf1fc..93a1a8a7b4e 100644 --- a/ktor-server/ktor-server-cio/jvmAndNix/src/io/ktor/server/cio/backend/HttpServer.kt +++ b/ktor-server/ktor-server-cio/jvmAndNix/src/io/ktor/server/cio/backend/HttpServer.kt @@ -6,8 +6,9 @@ package io.ktor.server.cio.backend import io.ktor.network.selector.* import io.ktor.network.sockets.* +import io.ktor.network.tls.* import io.ktor.server.cio.* -import io.ktor.server.cio.internal.WeakTimeoutQueue +import io.ktor.server.cio.internal.* import io.ktor.server.engine.* import io.ktor.server.engine.internal.* import io.ktor.util.* @@ -60,7 +61,13 @@ public fun CoroutineScope.httpServer( try { while (true) { - val client: Socket = settings.interceptor(server.accept(), connectionScope.coroutineContext) + val client: Socket = server.accept().let { socket -> + if (settings.tlsConfig != null) { + socket.tls(connectionScope.coroutineContext, settings.tlsConfig) + } else { + socket + } + } val connection = ServerIncomingConnection( client.openReadChannel(), From 433a829e5d11b4e7261fcfc78ef1e22da06b7b0a Mon Sep 17 00:00:00 2001 From: olme04 Date: Sat, 9 Apr 2022 11:57:12 +0300 Subject: [PATCH 9/9] apidump --- .../ktor-network-tls/api/ktor-network-tls.api | 63 ++++++++++++++++--- .../ktor-server-cio/api/ktor-server-cio.api | 12 ++-- ktor-utils/api/ktor-utils.api | 2 + 3 files changed, 61 insertions(+), 16 deletions(-) diff --git a/ktor-network/ktor-network-tls/api/ktor-network-tls.api b/ktor-network/ktor-network-tls/api/ktor-network-tls.api index 108652ac4c8..6166accfdb0 100644 --- a/ktor-network/ktor-network-tls/api/ktor-network-tls.api +++ b/ktor-network/ktor-network-tls/api/ktor-network-tls.api @@ -180,24 +180,48 @@ public final class io/ktor/network/tls/TLSAlertType$Companion { public final fun byCode (I)Lio/ktor/network/tls/TLSAlertType; } +public final class io/ktor/network/tls/TLSAuthenticationConfig { + public fun (Ljavax/net/ssl/KeyManagerFactory;)V + public final fun getKeyManagerFactory ()Ljavax/net/ssl/KeyManagerFactory; +} + +public final class io/ktor/network/tls/TLSAuthenticationConfigBuilder { + public fun (Lkotlin/jvm/functions/Function0;)V + public final fun build ()Lio/ktor/network/tls/TLSAuthenticationConfig; + public final fun keyStore (Ljava/security/KeyStore;)V + public final fun pkcs12Certificate (Ljava/io/File;Lkotlin/jvm/functions/Function0;)V + public final fun pkcs12Certificate (Ljava/lang/String;Lkotlin/jvm/functions/Function0;)V + public static synthetic fun pkcs12Certificate$default (Lio/ktor/network/tls/TLSAuthenticationConfigBuilder;Ljava/io/File;Lkotlin/jvm/functions/Function0;ILjava/lang/Object;)V + public static synthetic fun pkcs12Certificate$default (Lio/ktor/network/tls/TLSAuthenticationConfigBuilder;Ljava/lang/String;Lkotlin/jvm/functions/Function0;ILjava/lang/Object;)V +} + public final class io/ktor/network/tls/TLSCommonKt { - public static final fun tls (Lio/ktor/network/sockets/Connection;Lkotlin/coroutines/CoroutineContext;Lio/ktor/network/tls/TLSConfig;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public static final fun tls (Lio/ktor/network/sockets/Connection;Lkotlin/coroutines/CoroutineContext;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public static final fun tls (Lio/ktor/network/sockets/Connection;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public static final fun tls (Lio/ktor/network/sockets/Socket;Lkotlin/coroutines/CoroutineContext;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final fun tls (Lio/ktor/network/sockets/Connection;Lkotlin/coroutines/CoroutineContext;ZLkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final fun tls (Lio/ktor/network/sockets/Socket;Lkotlin/coroutines/CoroutineContext;Lio/ktor/network/tls/TLSConfig;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final fun tls (Lio/ktor/network/sockets/Socket;Lkotlin/coroutines/CoroutineContext;ZLkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun tls$default (Lio/ktor/network/sockets/Connection;Lkotlin/coroutines/CoroutineContext;ZLkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static synthetic fun tls$default (Lio/ktor/network/sockets/Socket;Lkotlin/coroutines/CoroutineContext;ZLkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; } -public final class io/ktor/network/tls/TLSConfig { - public fun (Ljava/security/SecureRandom;Ljava/util/List;Ljavax/net/ssl/X509TrustManager;Ljava/util/List;Ljava/lang/String;)V +public final class io/ktor/network/tls/TLSConfig : java/io/Closeable { + public fun (Ljava/security/SecureRandom;Ljava/util/List;Ljavax/net/ssl/X509TrustManager;Ljava/util/List;ZLjava/lang/String;Lio/ktor/network/tls/TLSAuthenticationConfig;Lio/ktor/network/tls/TLSVerificationConfig;)V + public fun close ()V + public final fun getAuthentication ()Lio/ktor/network/tls/TLSAuthenticationConfig; public final fun getCertificates ()Ljava/util/List; public final fun getCipherSuites ()Ljava/util/List; public final fun getRandom ()Ljava/security/SecureRandom; public final fun getServerName ()Ljava/lang/String; + public final fun getSslContext ()Ljavax/net/ssl/SSLContext; public final fun getTrustManager ()Ljavax/net/ssl/X509TrustManager; + public final fun getVerification ()Lio/ktor/network/tls/TLSVerificationConfig; + public final fun isClient ()Z } public final class io/ktor/network/tls/TLSConfigBuilder { public fun ()V + public fun (Z)V + public synthetic fun (ZILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun authentication (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;)V public final fun build ()Lio/ktor/network/tls/TLSConfig; public final fun getCertificates ()Ljava/util/List; public final fun getCipherSuites ()Ljava/util/List; @@ -208,13 +232,19 @@ public final class io/ktor/network/tls/TLSConfigBuilder { public final fun setRandom (Ljava/security/SecureRandom;)V public final fun setServerName (Ljava/lang/String;)V public final fun setTrustManager (Ljavax/net/ssl/TrustManager;)V + public final fun takeFrom (Lio/ktor/network/tls/TLSConfigBuilder;)V + public final fun verification (Lkotlin/jvm/functions/Function1;)V } public final class io/ktor/network/tls/TLSConfigBuilderKt { public static final fun addCertificateChain (Lio/ktor/network/tls/TLSConfigBuilder;[Ljava/security/cert/X509Certificate;Ljava/security/PrivateKey;)V public static final fun addKeyStoreNullablePassword (Lio/ktor/network/tls/TLSConfigBuilder;Ljava/security/KeyStore;[CLjava/lang/String;)V public static synthetic fun addKeyStoreNullablePassword$default (Lio/ktor/network/tls/TLSConfigBuilder;Ljava/security/KeyStore;[CLjava/lang/String;ILjava/lang/Object;)V - public static final fun takeFrom (Lio/ktor/network/tls/TLSConfigBuilder;Lio/ktor/network/tls/TLSConfigBuilder;)V +} + +public final class io/ktor/network/tls/TLSConfigKt { + public static final fun TLSConfig (ZLkotlin/jvm/functions/Function1;)Lio/ktor/network/tls/TLSConfig; + public static synthetic fun TLSConfig$default (ZLkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lio/ktor/network/tls/TLSConfig; } public final class io/ktor/network/tls/TLSHandshakeType : java/lang/Enum { @@ -239,10 +269,8 @@ public final class io/ktor/network/tls/TLSHandshakeType$Companion { } public final class io/ktor/network/tls/TLSKt { - public static final fun tls (Lio/ktor/network/sockets/Socket;Lkotlin/coroutines/CoroutineContext;Lio/ktor/network/tls/TLSConfig;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public static final fun tls (Lio/ktor/network/sockets/Socket;Lkotlin/coroutines/CoroutineContext;Ljavax/net/ssl/SSLEngine;)Lio/ktor/network/sockets/Socket; + public static final fun tls (Lio/ktor/network/sockets/Connection;Lkotlin/coroutines/CoroutineContext;Lio/ktor/network/tls/TLSConfig;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final fun tls (Lio/ktor/network/sockets/Socket;Lkotlin/coroutines/CoroutineContext;Ljavax/net/ssl/X509TrustManager;Ljava/lang/String;Ljava/util/List;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public static final fun tls (Lio/ktor/network/sockets/Socket;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun tls$default (Lio/ktor/network/sockets/Socket;Lkotlin/coroutines/CoroutineContext;Ljavax/net/ssl/X509TrustManager;Ljava/lang/String;Ljava/util/List;Ljava/lang/String;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; } @@ -261,6 +289,21 @@ public final class io/ktor/network/tls/TLSRecordType$Companion { public final fun byCode (I)Lio/ktor/network/tls/TLSRecordType; } +public final class io/ktor/network/tls/TLSVerificationConfig { + public fun (Ljavax/net/ssl/TrustManagerFactory;)V + public final fun getTrustManagerFactory ()Ljavax/net/ssl/TrustManagerFactory; +} + +public final class io/ktor/network/tls/TLSVerificationConfigBuilder { + public fun ()V + public final fun build ()Lio/ktor/network/tls/TLSVerificationConfig; + public final fun pkcs12Certificate (Ljava/io/File;Lkotlin/jvm/functions/Function0;)V + public final fun pkcs12Certificate (Ljava/lang/String;Lkotlin/jvm/functions/Function0;)V + public static synthetic fun pkcs12Certificate$default (Lio/ktor/network/tls/TLSVerificationConfigBuilder;Ljava/io/File;Lkotlin/jvm/functions/Function0;ILjava/lang/Object;)V + public static synthetic fun pkcs12Certificate$default (Lio/ktor/network/tls/TLSVerificationConfigBuilder;Ljava/lang/String;Lkotlin/jvm/functions/Function0;ILjava/lang/Object;)V + public final fun trustStore (Ljava/security/KeyStore;)V +} + public final class io/ktor/network/tls/TLSVersion : java/lang/Enum { public static final field Companion Lio/ktor/network/tls/TLSVersion$Companion; public static final field SSL3 Lio/ktor/network/tls/TLSVersion; diff --git a/ktor-server/ktor-server-cio/api/ktor-server-cio.api b/ktor-server/ktor-server-cio/api/ktor-server-cio.api index 3ace6a9508a..5dd2c34f713 100644 --- a/ktor-server/ktor-server-cio/api/ktor-server-cio.api +++ b/ktor-server/ktor-server-cio/api/ktor-server-cio.api @@ -34,19 +34,19 @@ public final class io/ktor/server/cio/HttpServerKt { public final class io/ktor/server/cio/HttpServerSettings { public fun ()V - public fun (Ljava/lang/String;IJLkotlin/jvm/functions/Function2;)V - public synthetic fun (Ljava/lang/String;IJLkotlin/jvm/functions/Function2;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Ljava/lang/String;IJLio/ktor/network/tls/TLSConfig;)V + public synthetic fun (Ljava/lang/String;IJLio/ktor/network/tls/TLSConfig;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Ljava/lang/String; public final fun component2 ()I public final fun component3 ()J - public final fun component4 ()Lkotlin/jvm/functions/Function2; - public final fun copy (Ljava/lang/String;IJLkotlin/jvm/functions/Function2;)Lio/ktor/server/cio/HttpServerSettings; - public static synthetic fun copy$default (Lio/ktor/server/cio/HttpServerSettings;Ljava/lang/String;IJLkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lio/ktor/server/cio/HttpServerSettings; + public final fun component4 ()Lio/ktor/network/tls/TLSConfig; + public final fun copy (Ljava/lang/String;IJLio/ktor/network/tls/TLSConfig;)Lio/ktor/server/cio/HttpServerSettings; + public static synthetic fun copy$default (Lio/ktor/server/cio/HttpServerSettings;Ljava/lang/String;IJLio/ktor/network/tls/TLSConfig;ILjava/lang/Object;)Lio/ktor/server/cio/HttpServerSettings; public fun equals (Ljava/lang/Object;)Z public final fun getConnectionIdleTimeoutSeconds ()J public final fun getHost ()Ljava/lang/String; - public final fun getInterceptor ()Lkotlin/jvm/functions/Function2; public final fun getPort ()I + public final fun getTlsConfig ()Lio/ktor/network/tls/TLSConfig; public fun hashCode ()I public fun toString ()Ljava/lang/String; } diff --git a/ktor-utils/api/ktor-utils.api b/ktor-utils/api/ktor-utils.api index 037805fe351..59521a62be4 100644 --- a/ktor-utils/api/ktor-utils.api +++ b/ktor-utils/api/ktor-utils.api @@ -233,10 +233,12 @@ public final class io/ktor/util/PathKt { public final class io/ktor/util/PlatformUtils { public static final field INSTANCE Lio/ktor/util/PlatformUtils; public final fun getIS_BROWSER ()Z + public final fun getIS_DARWIN ()Z public final fun getIS_DEVELOPMENT_MODE ()Z public final fun getIS_JVM ()Z public final fun getIS_NATIVE ()Z public final fun getIS_NEW_MM_ENABLED ()Z + public final fun getIS_NIX ()Z public final fun getIS_NODE ()Z }