You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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 2
  • instance 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 12:47:20