Mensaje de error
PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
También se muestra como
- 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 y 25 anteponen la alerta TLS: «javax.net.ssl.SSLHandshakeException: (certificate_unknown) PKIX path building failed: …». Los registros de JDK más antiguos muestran «javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: …». Los frameworks y las herramientas de compilación (Spring, Maven, Gradle, los IDE) lo envuelven en sus propios mensajes, pero conservan el texto PKIX en la causa.
Java (JSSE, SunX509 trust manager) — javax.net.ssl.SSLHandshakeException caused by sun.security.validator.ValidatorException
Qué significa
El constructor de rutas de certificación PKIX de Java no pudo construir una cadena desde el certificado del servidor hasta ninguna ancla de confianza del almacén de confianza que usa la JVM. «Requested target» es el certificado del servidor. Java solo confía en su propio archivo cacerts (o en el almacén indicado con javax.net.ssl.trustStore), no en el almacén del sistema operativo, así que aquí falla un certificado de una CA de empresa, de un proxy que inspecciona TLS o de un servidor que no envía su certificado intermedio.
Por qué ocurre
El certificado del servidor procede de una CA privada
frecuenteLos servicios internos, los ingress de Kubernetes, los repositorios Nexus o Artifactory y las bases de datos con certificados de la empresa tienen cadenas que terminan en una raíz que no está en el cacerts del JDK.
Un proxy que inspecciona TLS vuelve a firmar la conexión
frecuenteEn las redes de empresa, Maven Central, los portales de plugins de Gradle y las API públicas se presentan con certificados de la raíz del proxy. Instalar esa raíz en Windows o macOS no tiene ningún efecto en Java.
El servidor no envía su certificado intermedio
frecuenteJava no descarga por defecto los intermedios que faltan (com.sun.security.enableAIAcaIssuers está desactivado), así que un servidor configurado solo con su certificado hoja falla aunque los navegadores lo acepten.
La JVM usa un almacén de confianza distinto del que crees
ocasionalLos IDE y las herramientas de compilación incluyen su propio JDK, los servidores de aplicaciones apuntan javax.net.ssl.trustStore a un almacén con solo unas pocas CA, y los contenedores pueden usar un JRE cuyo cacerts nunca se actualizó. Una raíz importada en el cacerts de un JDK no sirve para otro JDK.
A un JDK muy antiguo le faltan raíces públicas más recientes
raraLas compilaciones de Java 8 sin parches, anteriores a que la raíz de una CA se añadiera a cacerts, fallan en los sitios que usan esa raíz; las actualizaciones actuales la incluyen.
Cómo solucionarlo
- 1.
Ver la cadena y el almacén de confianza en uso
openssl s_client muestra lo que envía el servidor. javax.net.debug=ssl:handshake:verbose hace que Java imprima la ruta del almacén de confianza que cargó y cada certificado que recibió.
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.
Usar un almacén de confianza de la aplicación que incluya tu raíz
Copia el cacerts del JDK (para que las CA públicas sigan funcionando), importa la raíz de la empresa o del proxy con keytool y apunta la aplicación a esa copia con javax.net.ssl.trustStore y trustStorePassword (o con JAVA_TOOL_OPTIONS para las herramientas que no arrancas tú mismo).
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.
Importar la raíz en el cacerts del JDK
keytool -cacerts actúa sobre el almacén predeterminado del JDK que ejecuta keytool (contraseña «changeit» por defecto). Todas las aplicaciones de ese JDK confían en la raíz; el cambio se pierde cuando se sustituye el 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.
Contenedores: dejar que la imagen importe tu CA
Cuando USE_SYSTEM_CA_CERTS está definida, el entrypoint de las imágenes de Eclipse Temurin añade al arrancar los archivos de certificado *.crt montados en /certificates al cacerts de la JVM (solo se ejecuta con el entrypoint predeterminado de la imagen). Las demás imágenes necesitan el paso de keytool en el 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.
Dar a un cliente su propio almacén de confianza
Construye un SSLContext a partir de un TrustManagerFactory inicializado con tu almacén de confianza y pásalo a HttpClient (o a OkHttp, Apache HttpClient, Netty). Nunca instales un gestor de confianza que acepte cualquier certificado.
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();
Errores relacionados
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