跳到主要内容
ecdsa.com
Java (JSSE, SunX509 trust manager)Java

错误信息

PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target

也可能输出为

  • javax.net.ssl.SSLHandshakeException: (certificate_unknown) PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
  • javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
  • unable to find valid certification path to requested target

JDK 21 和 25 会在前面加上 TLS 告警:“javax.net.ssl.SSLHandshakeException: (certificate_unknown) PKIX path building failed: …”。较旧 JDK 的日志显示为“javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: …”。框架和构建工具(Spring、Maven、Gradle、IDE)会把它包装进自己的消息中,但在异常原因(cause)中保留 PKIX 文本。

Java (JSSE, SunX509 trust manager) — javax.net.ssl.SSLHandshakeException caused by sun.security.validator.ValidatorException

含义

Java 的 PKIX 证书路径构建器无法从服务器证书出发,构建出一条通往 JVM 当前所用信任库中任何一个信任锚的链。“Requested target”指的就是服务器证书。Java 只信任它自己的 cacerts 文件(或用 javax.net.ssl.trustStore 指定的信任库),而不信任操作系统的信任库,因此由公司 CA 签发的证书、TLS 检查代理出示的证书,或者来自未发送中间证书的服务器的证书,验证都会在这里失败。

原因

修复方法

  1. 1.

    查看证书链和正在使用的信任库

    openssl s_client 会显示服务器发送了什么。javax.net.debug=ssl:handshake:verbose 会让 Java 打印它加载的信任库路径,以及收到的每一张证书。

    bash
    openssl s_client -connect api.example.com:443 -servername api.example.com -showcerts </dev/null 2>/dev/null \
      | grep -E '^ *[0-9]+ s:|^ *i:'
    # Or from Java itself: prints the chain and the trust store in use
    java -Djavax.net.debug=ssl:handshake:verbose -jar app.jar 2>&1 | grep -E 'trustStore is|Subject:|Issuer:'
  2. 2.

    使用包含你的根证书的应用信任库

    复制 JDK 的 cacerts(这样公共 CA 仍然可用),用 keytool 导入公司根证书或代理根证书,然后通过 javax.net.ssl.trustStore 和 trustStorePassword 让应用指向它(对于不是由你直接启动的工具,使用 JAVA_TOOL_OPTIONS)。

    bash
    # A copy of the JDK's cacerts plus your root: public sites keep working
    cp "$JAVA_HOME/lib/security/cacerts" app-truststore.p12
    keytool -importcert -noprompt -alias corp-root -file corp-root-ca.pem \
      -keystore app-truststore.p12 -storepass changeit
    java -Djavax.net.ssl.trustStore=app-truststore.p12 -Djavax.net.ssl.trustStorePassword=changeit -jar app.jar
  3. 3.

    把根证书导入 JDK 的 cacerts

    keytool -cacerts 操作的是运行 keytool 的那个 JDK 的默认信任库(默认密码为“changeit”)。该 JDK 上的所有应用都会信任这个根证书;更换 JDK 后,这项修改就会丢失。

    bash
    # Add the root to this JDK's default trust store (applies to every app on this JDK;
    # repeat after each JDK upgrade)
    sudo keytool -importcert -noprompt -cacerts -alias corp-root -file corp-root-ca.pem -storepass changeit
  4. 4.

    容器:让镜像导入你的 CA

    设置了 USE_SYSTEM_CA_CERTS 时,Eclipse Temurin 镜像的入口点会在启动时把挂载在 /certificates 下的 *.crt 证书文件添加到 JVM 的 cacerts 中(只有使用镜像默认入口点时才会执行)。其他镜像需要在 Dockerfile 中加入 keytool 步骤。

    bash
    # Eclipse Temurin images: mount your CA(s) as *.crt and let the entrypoint add them to cacerts
    docker run -e USE_SYSTEM_CA_CERTS=1 \
      -v "$PWD/certs:/certificates/" eclipse-temurin:25.0.4.1_1-jdk-noble java -jar /app/app.jar
  5. 5.

    为单个客户端配置独立的信任库

    先用你的信任库初始化 TrustManagerFactory,再用它构建 SSLContext,然后传给 HttpClient(或 OkHttp、Apache HttpClient、Netty)。切勿安装接受所有证书的信任管理器。

    java
    // One client with its own trust store (verification stays on)
    KeyStore ts = KeyStore.getInstance("PKCS12");
    try (InputStream in = Files.newInputStream(Path.of("app-truststore.p12"))) { ts.load(in, "changeit".toCharArray()); }
    TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
    tmf.init(ts);
    SSLContext ssl = SSLContext.getInstance("TLS");
    ssl.init(null, tmf.getTrustManagers(), null);
    HttpClient client = HttpClient.newBuilder().sslContext(ssl).build();

相关错误

浏览完整的签名错误库