ktor sslConnector 从 jar p12 pkcs12 jks 密钥库和 mtls 相互 ssl 连接中服务/读取

Posted

技术标签:

【中文标题】ktor sslConnector 从 jar p12 pkcs12 jks 密钥库和 mtls 相互 ssl 连接中服务/读取【英文标题】:ktor sslConnector serve/read from jar p12 pkcs12 jks keystore and mtls mutual ssl connection 【发布时间】:2021-10-21 06:29:51 【问题描述】:

我已经弄清楚如何使用 EmbeddedServer Jetty 启动 ktor 并使用我自己的签名证书(由我自己的自签名 rootCA 证书签名)提供 https/ssl/tls。

现在 ktor sslConnector 需要将 keyStorePath 设置为 File,但我更愿意从最终 fat jar 中的 /a 文件中提供密钥库(主要是为了能够在 kubernetes 集群中运行它)

有没有办法告诉 ktor 获取/读取嵌入在其 jar 文件中的资源作为 keyStore?

    // openssl pkcs12 -export -nodes -passout pass:$keystorePW -in "domain.cert" -inkey "domain.key" -certfile "intermediateAndRootCAchain.ca" -name "aliasName" -out "webserverKeystore.p12"
    val keystore: KeyStore = KeyStore.getInstance(keystoreFile, keystorePW.toCharArray())

    @Suppress("UNUSED_PARAMETER")
    fun doIt(args: Array<String>) 
        val server = embeddedServer(Jetty, applicationEngineEnvironment 
            module 
                configureRouting()
                configureHTTP(sslPort)
                configureSerialization()
            

            connector 
                this.host = host // redirected to https
                this.port = port // redirected to sslPort
            

            sslConnector(keystore,
                keyAlias = certAlias, // alias name inside keystore: keytool -v -list -keystore certs/keystore.jks
                keyStorePassword =  keystorePW.toCharArray() ,
                privateKeyPassword =  keystorePW.toCharArray()  // somehow this is the same as keystorePW if using openssl pkcs12 -export from above
            ) 
                this.port = sslPort
                keyStorePath = keystoreFile
            
        )

        server.start(wait = true)
    

有没有办法告诉 ktor 获取/读取嵌入在其 jar 文件中的资源作为 keyStore?

(也想知道为什么sslConnector 仍然需要作为File 的密钥库,如果它已经将整个密钥库作为第一个参数,但这可能与正在使用的实际 Web 容器无关)

第二个问题:是否可以启用 mutual tls,以便在客户端无法提供有效证书时 ktor 服务器拒绝连接?如果是,我将如何配置?

【问题讨论】:

我不知道 ktor,但在 Java 中,您可以(仍然)使用 pre-9 方式从 jar 资源加载 KeyStore:x = KeyStore.getInstance("PKCS12"); x.load( whatever.getResourceAsStream("/blah"), pw_chararray)(并关闭逻辑要整洁)。同样在 Java 中,SSLSocket/SSLEngine.setNeedClientAuth(true).setSSLParameters(containing_same) 中止,如果没有或错误的客户端证书,但我不知道 ktor。 PS:openssl pkcs12 -export 忽略了-nodes——这仅适用于相反的“解析”模式。 【参考方案1】:

对于NettyJetty 引擎,实际上使用了作为sslConnector 的第一个参数传递的keystorekeyStorePath 属性仅用于 Tomcat 引擎。

不幸的是,无法在 Ktor 中配置双向 TLS 身份验证,但作为一种解决方法,您可以通过将连接器添加到底层 Jetty 服务器来手动完成。 This article 可能对您有用。这是一个不完整的例子:

embeddedServer(
    Jetty, 
    applicationEngineEnvironment 
        module 
            // ...
        
    
) 
    configureServer = 
        val factory = SslConnectionFactory(
            SslContextFactory.Server().apply 
                // keyStore = ...
                // setKeyManagerPassword(...)
                // setKeyStorePassword(...)
                needClientAuth = true
            ,
            HttpVersion.HTTP_1_1.asString()
        )

        val connector = ServerConnector(this, factory).apply 
            // host = ...
            // port = ...
        

        addConnector(connector)
    

【讨论】:

【参考方案2】:

这是我最终的使用代码:

        val (theTrustStore, theKeyStore) = createTrustStoreAndKeyStore()

        val server = embeddedServer(Jetty, applicationEngineEnvironment 
            module 
                configureRouting()
                configureHTTP(theSslPort)
                configureSerialization()
            

            connector 
                this.host = theBindHost // redirected to https
                this.port = thePort // redirected to sslPort
            

            // // without mTLS (m = mutual)
            // sslConnector(theKeyStore,
            //     keyAlias = theHost, // alias name inside keystore: keytool -v -list -keystore certs/keystore.jks
            //     keyStorePassword =  keystorePW.toCharArray() ,
            //     privateKeyPassword =  privateKeyPW.toCharArray()  // somehow this is the same as keystorePW if using openssl pkcs12 -export from above
            // ) 
            //     this.host = theBindHost
            //     this.port = theSslPort
            //     keyStorePath = null // only used by tomcat engine
            // 
        ) 
            configureServer = 
                val factory = SslConnectionFactory(
                    SslContextFactory.Server().apply 
                        keyStore = theKeyStore
                        trustStore = theTrustStore
                        // setKeyManagerPassword(...)
                        // setKeyStorePassword(...)
                        needClientAuth = true
                    ,
                    HttpVersion.HTTP_1_1.asString()
                )

                val httpConfig = HttpConfiguration()
                httpConfig.secureScheme = "https"
                httpConfig.securePort = theSslPort
                // SSL HTTP Configuration
                val httpsConfig = HttpConfiguration(httpConfig)
                httpsConfig.addCustomizer(SecureRequestCustomizer()) // so that servlets can see the encryption details


                val connector = ServerConnector(
                    this,
                    factory,
                    HttpConnectionFactory(httpsConfig)
                ).apply 
                    host = theBindHost
                    port = theSslPort
                

                addConnector(connector)
            
        

        server.start(wait = true)
    

【讨论】:

感谢这个例子,你能分享一下你是如何实现 createTrustStoreAndKeyStore() 的吗? gist.github.com/HoffiMuc/bb183db2e9d54cf1d94a71a3705117f4

以上是关于ktor sslConnector 从 jar p12 pkcs12 jks 密钥库和 mtls 相互 ssl 连接中服务/读取的主要内容,如果未能解决你的问题,请参考以下文章

运行 fat jar 的 ktor 抛出 java.lang.UnsupportedOperationException::Kotlin 反射中尚不支持包和文件外观

Ktor App 部署到 AppEngine 时未调用 Main 方法

Ktor 动态文件路径

Kotlin:Ktor 如何将文本响应为 html

如何在 Ktor (Kotlin) 中管道的各个部分之间传递数据

从 ktor 提供 kotlin 多平台 javascript