エラーメッセージ
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 インスペクションを行うプロキシの証明書、中間証明書を送信しないサーバーの証明書は、ここで失敗します。
発生する理由
サーバーの証明書がプライベート CA から発行されている
よくある社内の証明書を使う内部サービス、Kubernetes の Ingress、Nexus や Artifactory のリポジトリ、データベースは、JDK の cacerts に含まれていないルートにチェーンがつながっています。
TLS インスペクションを行うプロキシが接続を再署名している
よくある社内ネットワークでは、Maven Central、Gradle のプラグインポータル、公開 API に対して、プロキシのルートから発行された証明書が提示されます。そのルートを Windows や macOS にインストールしても、Java には反映されません。
サーバーが中間証明書を送信していない
よくあるJava はデフォルトでは不足している中間証明書を取得しません(com.sun.security.enableAIAcaIssuers がオフ)。そのため、リーフ証明書だけを設定したサーバーは、ブラウザーでは受け入れられても Java では失敗します。
JVM が想定とは別のトラストストアを使っている
ときどきIDE やビルドツールは独自の JDK を同梱しており、アプリケーションサーバーは javax.net.ssl.trustStore に少数の CA しか含まないストアを設定し、コンテナーは cacerts が一度も更新されていない JRE を使っていることがあります。ある JDK の cacerts にインポートしたルートは、別の JDK には効果がありません。
非常に古い JDK に新しいパブリック CA のルートが含まれていない
まれCA のルートが cacerts に追加される前の、パッチ未適用の Java 8 ビルドは、そのルートを使うサイトで失敗します。現在のアップデートにはそのルートが含まれています。
修正方法
- 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.
必要なルートを含むアプリケーション専用のトラストストアを使う
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.
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.
コンテナー:イメージに 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.
特定のクライアントに専用のトラストストアを持たせる
使用するトラストストアで初期化した 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();
関連するエラー
PKIX path validation failed: java.security.cert.CertPathValidatorException: val…Java (JSSE)java.security.InvalidAlgorithmParameterException: the trustAnchors parameter mu…Java (JSSE / PKIX)No subject alternative DNS name matching api.example.com found.Java (JSSE HostnameChecker)curl: (60) SSL certificate problem: unable to get local issuer certificatecurl (OpenSSL backend)x509: certificate signed by unknown authorityGo crypto/x509