本文へスキップ
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 で指定されたストア)だけで、OS のストアは使いません。そのため、社内 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 で社内 CA またはプロキシのルートをインポートして、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();

関連するエラー

署名エラーデータベースをすべて見る