Scala+Akka-http自定义信任库与自签名证书SSL握手异常排查
背景与环境配置
我开发了一个兼具客户端与服务端功能的Scala+Akka-http应用,单个「服务端+客户端」组合称为instance(实例)。
每个instance通过以下Scala代码生成自签名证书与密钥库:
val genKeyCommand = s"openssl genrsa -out $SSL_KEY_PATH 4096" Seq("bash", "-c", genKeyCommand) !! val sslCommand = s"openssl req -new -x509 -sha256 -days 36500 -addext 'subjectAltName = IP:$SERVER_IP' " + s"-subj '/C=UK/ST=London/L=London/O=Dis/CN=hydra-${OS.getOS.toString.toLowerCase}-test.com' -key $SSL_KEY_PATH " + s"-out $SSL_CERTIFICATE_PATH" Seq("bash", "-c", sslCommand) !! // load the key into keystore and create val keyStoreCommand = s"openssl pkcs12 -export -out $KEY_STORE_PATH -in $SSL_CERTIFICATE_PATH -inkey $SSL_KEY_PATH " + s"-passout pass:" + KY_STORE_PASS Seq("bash", "-c", keyStoreCommand) !!
当前测试环境包含2个实例:hydra-macos-test和运行在VirtualBox虚拟机Debian 12系统中的hydra-linux-test。
我实现了一个基于HTTP的跨实例握手流程,当实例感知到另一个实例时触发,核心步骤如下:
instance 1创建客户端Actor,通过HTTP向instance 2请求认证令牌instance 1加密自身自签名证书,携带认证令牌发送给instance 2instance 2解密证书并存储到自定义信任库instance 2返回自身加密后的自签名证书instance 1解密证书并存入自身自定义信任库- 理论上此时可进行HTTPS通信
该流程执行正常,双方证书均已成功添加至对应信任库。
我通过以下代码设置系统密钥库与信任库:
// set system keystore and truststore to our custom ones System.setProperty("javax.net.ssl.trustStore", TRUST_STORE_PATH) System.setProperty("javax.net.ssl.trustStorePassword", DatabaseUtil.hashString(SERVER_ID)) System.setProperty("javax.net.ssl.keyStore", KEY_STORE_PATH) System.setProperty("javax.net.ssl.keyStorePassword", DatabaseUtil.hashString(SERVER_ID))
实例内为服务端和客户端创建SSLContext的代码如下:
def createClientSSLContext: SSLContext = { val sslContext = SSLContext.getInstance("TLS") val keyStore = loadKeyStore(TRUST_STORE_PATH, KEY_STORE_PASS) val keyManagerFactory: KeyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm) keyManagerFactory.init(keyStore, KEY_STORE_PASS) val tmf: TrustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm) tmf.init(keyStore) sslContext.init(keyManagerFactory.getKeyManagers, tmf.getTrustManagers, new SecureRandom) sslContext } def createServerSSLContext: SSLContext = { val sslContext = SSLContext.getInstance("TLS") val keyStore = loadKeyStore(KEY_STORE_PATH, KEY_STORE_PASS) val entry = keyStore.getEntry(ALIAS, new KeyStore.PasswordProtection(KEY_PASS)) val keyManagerFactory: KeyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm) keyManagerFactory.init(keyStore, KEY_STORE_PASS) val tmf: TrustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm) tmf.init(keyStore) sslContext.init(keyManagerFactory.getKeyManagers, tmf.getTrustManagers, new SecureRandom) sslContext }
服务端使用SSLContext的代码:
val sslContext = SSLManager.createServerSSLContext val https: HttpsConnectionContext = ConnectionContext.httpsServer(sslContext) // create https Engine ConnectionContext.httpsServer(() => { val engine = sslContext.createSSLEngine() engine.setUseClientMode(false) engine.setNeedClientAuth(true) engine }) Http().newServerAt("localhost", 8443).enableHttps(https).bind(new HydraRoute(HttpsRoutes(clientManager)).masterRoute) .onComplete { case Success(binding) => val address = binding.localAddress system.log.info(s"HTTPS Server is listening on ${address.getHostString}:${address.getPort}") case Failure(ex) => system.log.error("HTTPS Server could not be started", ex) stop() }
客户端使用SSLContext的代码:
val connectionContext = ConnectionContext.httpsClient(SSLManager.createClientSSLContext) http.singleRequest(request, connectionContext).pipeTo(self)
问题描述
当instance 1向instance 2发送HTTPS POST请求时,出现如下异常:
javax.net.ssl.SSLHandshakeException: (certificate_unknown) PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
完整带ssl:debug的输出已留存,若需更多调试信息可提供。
内容的提问来源于stack exchange,提问作者Kris Rice
相关产品推荐
相关产品推荐

