Message d’erreur
PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
Également affiché sous la forme
- 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
Les JDK 21 et 25 font précéder le message de l’alerte TLS : « javax.net.ssl.SSLHandshakeException: (certificate_unknown) PKIX path building failed: … ». Les journaux des JDK plus anciens affichent « javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: … ». Les frameworks et les outils de build (Spring, Maven, Gradle, IDE) l’enveloppent dans leurs propres messages, mais conservent le texte PKIX dans la cause.
Java (JSSE, SunX509 trust manager) — javax.net.ssl.SSLHandshakeException caused by sun.security.validator.ValidatorException
Ce que cela signifie
Le constructeur de chemins de certificats PKIX de Java n’a pas pu bâtir de chaîne allant du certificat du serveur à l’une des ancres de confiance du magasin de confiance qu’utilise la JVM. La mention « requested target » désigne le certificat du serveur. Java ne fait confiance qu’à son propre fichier cacerts (ou au magasin indiqué par javax.net.ssl.trustStore), et non au magasin du système d’exploitation : un certificat émis par une AC d’entreprise ou par un proxy d’inspection TLS, ou présenté par un serveur sans son certificat intermédiaire, échoue donc ici.
Pourquoi cela se produit
Le certificat du serveur provient d’une AC privée
fréquenteLes services internes, les ingress Kubernetes, les dépôts Nexus ou Artifactory et les bases de données dotés de certificats d’entreprise remontent à une racine qui ne figure pas dans le cacerts du JDK.
Un proxy d’inspection TLS re-signe la connexion
fréquenteSur les réseaux d’entreprise, Maven Central, les portails de plugins Gradle et les API publiques sont présentés avec des certificats issus de la racine du proxy. Installer cette racine dans Windows ou macOS n’a aucun effet sur Java.
Le serveur n’envoie pas son certificat intermédiaire
fréquentePar défaut, Java ne récupère pas les certificats intermédiaires manquants (com.sun.security.enableAIAcaIssuers est désactivé) : un serveur configuré avec son seul certificat feuille échoue donc, alors que les navigateurs l’acceptent.
La JVM utilise un autre magasin de confiance que celui que vous croyez
occasionnelleLes IDE et les outils de build embarquent leur propre JDK, les serveurs d’applications font pointer javax.net.ssl.trustStore vers un magasin qui ne contient que quelques AC, et les conteneurs peuvent utiliser un JRE dont le cacerts n’a jamais été mis à jour. Une racine importée dans le cacerts d’un JDK ne sert à rien pour un autre JDK.
Un JDK très ancien ne contient pas les racines publiques récentes
rareLes builds de Java 8 non mis à jour, antérieurs à l’ajout de la racine d’une AC dans cacerts, échouent sur les sites qui utilisent cette racine ; les mises à jour actuelles la contiennent.
Comment corriger l’erreur
- 1.
Afficher la chaîne et le magasin de confiance utilisé
openssl s_client montre ce que le serveur envoie. Avec javax.net.debug=ssl:handshake:verbose, Java affiche le chemin du magasin de confiance qu’il a chargé et chaque certificat qu’il a reçu.
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.
Utiliser un magasin de confiance applicatif qui contient votre racine
Copiez le cacerts du JDK (pour que les AC publiques fonctionnent toujours), importez la racine de l’entreprise ou du proxy avec keytool, puis indiquez ce magasin à l’application avec javax.net.ssl.trustStore et trustStorePassword (ou JAVA_TOOL_OPTIONS pour les outils que vous ne lancez pas vous-même).
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.
Importer la racine dans le cacerts du JDK
keytool -cacerts cible le magasin par défaut du JDK qui exécute keytool (mot de passe « changeit » par défaut). Toutes les applications de ce JDK font alors confiance à la racine ; la modification est perdue lorsque le JDK est remplacé.
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.
Conteneurs : laisser l’image importer votre AC
Lorsque USE_SYSTEM_CA_CERTS est défini, le point d’entrée des images Eclipse Temurin ajoute au démarrage les fichiers *.crt montés dans /certificates au cacerts de la JVM (cela ne fonctionne qu’avec le point d’entrée par défaut de l’image). Les autres images nécessitent l’étape keytool dans le 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.
Donner à un client son propre magasin de confiance
Construisez un SSLContext à partir d’une TrustManagerFactory initialisée avec votre magasin de confiance et passez-le à HttpClient (ou à OkHttp, Apache HttpClient, Netty). N’installez jamais de gestionnaire de confiance (trust manager) qui accepte tous les certificats.
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();
Erreurs associées
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