Перейти к содержимому
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

Что это означает

Механизм построения путей сертификации PKIX в Java не смог выстроить цепочку от сертификата сервера ни до одного якоря доверия из хранилища доверенных сертификатов, которое использует 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.

    Используйте для приложения хранилище доверенных сертификатов с вашим корневым сертификатом

    Скопируйте cacerts из JDK (чтобы публичные 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.

    Импортируйте корневой сертификат в cacerts JDK

    keytool -cacerts работает с хранилищем по умолчанию того JDK, из которого запущен keytool (пароль по умолчанию — «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, entrypoint образов Eclipse Temurin при запуске добавляет файлы *.crt, смонтированные в /certificates, в cacerts JVM (это срабатывает только со стандартным entrypoint образа). Для других образов нужен шаг с keytool в Dockerfile.

    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.

    Дайте отдельному клиенту собственное хранилище доверенных сертификатов

    Создайте SSLContext на основе TrustManagerFactory, инициализированной вашим хранилищем доверенных сертификатов, и передайте его в HttpClient (или OkHttp, Apache HttpClient, Netty). Никогда не устанавливайте TrustManager, который принимает любой сертификат.

    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();

Связанные ошибки

Открыть всю базу ошибок подписей