I'm trying to add a custom logic to accept tls connection to the server using Ktor server and Netty, I didn't find a way to modify trustManger
or sslContext
.
val environment = applicationEngineEnvironment {
connector {
port = 8080
}
sslConnector(
keyStore = keystore,
keyAlias = "selfsigned",
keyStorePassword = { "".toCharArray() },
privateKeyPassword = { "changeit".toCharArray() }
) {
port = 8443
trustStore = clientkeystore
}
module {
routing {
get("/204") {
call.response.status(HttpStatusCode.NoContent)
}
}
}
}
embeddedServer(Netty, environment).start(true)
this is for mtls using keystore, but i want to control the logic of accepting the client certificates like ignore the validity of the client certificate, this is why I need to provide trustManager
.
I have search the source code, it seem that sslContext
and trustManger
is setup in NettyChannelInitializer
public class NettyChannelInitializer(
private val enginePipeline: EnginePipeline,
private val environment: ApplicationEngineEnvironment,
private val callEventGroup: EventExecutorGroup,
private val engineContext: CoroutineContext,
private val userContext: CoroutineContext,
private val connector: EngineConnectorConfig,
private val requestQueueLimit: Int,
private val runningLimit: Int,
private val responseWriteTimeout: Int,
private val requestReadTimeout: Int,
private val httpServerCodec: () -> HttpServerCodec,
private val channelPipelineConfig: ChannelPipeline.() -> Unit
) : ChannelInitializer<SocketChannel>() {
private var sslContext: SslContext? = null
init {
if (connector is EngineSSLConnectorConfig) {
// It is better but netty-openssl doesn't support it
// val kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm())
// kmf.init(ktorConnector.keyStore, password)
// password.fill('\u0000')
@Suppress("UNCHECKED_CAST")
val chain1 = connector.keyStore.getCertificateChain(connector.keyAlias).toList() as List<X509Certificate>
val certs = chain1.toList().toTypedArray()
val password = connector.privateKeyPassword()
val pk = connector.keyStore.getKey(connector.keyAlias, password) as PrivateKey
password.fill('\u0000')
sslContext = SslContextBuilder.forServer(pk, *certs).apply {
if (alpnProvider != null) {
sslProvider(alpnProvider)
ciphers(Http2SecurityUtil.CIPHERS, SupportedCipherSuiteFilter.INSTANCE)
applicationProtocolConfig(
ApplicationProtocolConfig(
ApplicationProtocolConfig.Protocol.ALPN,
ApplicationProtocolConfig.SelectorFailureBehavior.NO_ADVERTISE,
ApplicationProtocolConfig.SelectedListenerFailureBehavior.ACCEPT,
ApplicationProtocolNames.HTTP_2,
ApplicationProtocolNames.HTTP_1_1
)
)
}
connector.trustManagerFactory()?.let { this.trustManager(it) }
}
.build()
}
}
is there a way to configure the logic using channelPipelineConfig
or configureBootstrap
? or I need to create custom netty engine?
I have tried to use childHandler
but it never been called, maybe I have missed something.
embeddedServer(Netty, port = 8443, configure = {
configureBootstrap = {
val certsChain = keystore2.getCertificateChain("selfsigned").toList() as List<X509Certificate>
val certs = certsChain.toTypedArray()
val password = "changeit".toCharArray()
val privateKey = keystore2.getKey("selfsigned", password) as PrivateKey
val trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
.also { it.init(keystore2) }
childHandler(object : ChannelInitializer<SocketChannel>() {
override fun initChannel(ch: SocketChannel) {
println("initChannel")
val sslContext = SslContextBuilder.forServer(privateKey, *certs)
.trustManager(trustManagerFactory)
.build()
val sslEngine = sslContext.newEngine(ch.alloc()).apply {
useClientMode = false
needClientAuth = false
}
ch.pipeline().addLast(SslHandler(sslEngine))
}
})
}
}, module = {
routing {
get("/204") {
call.response.status(HttpStatusCode.NoContent)
}
}
}).start(wait = true)
I have managed to add sslContext to Netty using channelPipelineConfig
and SslHandler
embeddedServer(Netty, port = 8443, configure = {
val certsChain = keystore2.getCertificateChain("selfsigned").toList() as List<X509Certificate>
val certs = certsChain.toTypedArray()
val password = "changeit".toCharArray()
val privateKey = keystore2.getKey("selfsigned", password) as PrivateKey
val trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
.also { it.init(keystore2) }
channelPipelineConfig = {
val sslContext = SslContextBuilder.forServer(privateKey, *certs)
.trustManager(trustManagerFactory)
.build()
val sslEngine = sslContext.newEngine(channel().alloc()).apply {
useClientMode = false
needClientAuth = true
}
addFirst("ssl", SslHandler(sslEngine))
}
}, module = {
routing {
get("/204") {
call.response.status(HttpStatusCode.NoContent)
}
}
}).start(wait = true)