Java && PKIX Path Building Failed Exception

Kurt Klinner

Beim Versuch aus einem Java Programm auf einen Third-Party-Webservices (der kurz vorher erst auf SSL umgestellt wurde) per https zu zuzugreifen, ging erst mal gar nichts. Das gleiche Phänomen hatten wir ebenfalls mal beim Umzug unserer Atlassian Domains auf einen Loadbalancer.

Folgende Exception wurde geworfen

sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:387)

Problem an der Stelle ist, dass Java beim Versuch via SSL zuzugreifen, prüft ob es sich beim Gegenstück um ein "vertrauenswertes" Etwas handelt.
Hierbei bedient sich Java eines Keystore/Truststore der in der Regel unter $JAVA_HOME/lib/security/cacerts liegt. Der Store beinhaltet eine Liste aller bekannten Certificate Authority (CA) Zertifikate. Java wird nur Gegenstellen vertrauen, die durch eine dieser CAs signiert sind oder auf enthaltenen öffentlichen Zertifikaten basieren.

Eine Möglichkeit zu verifizieren, ob der Trustsore die richtigen Zertifikate enthält stellt das SSLPoke Programm da (Referenz hierzu findet sich unter
https://confluence.atlassian.com/download/attachments/117455/SSLPoke.java)

import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import java.io.*;

/** Establish a SSL connection to a host and port, writes a byte and
 * prints the response. See
 * http://confluence.atlassian.com/display/JIRA/Connecting+to+SSL+services
 */
public class SSLPoke {
    public static void main(String[] args) {
        if (args.length != 2) {
            System.out.println("Usage: "+SSLPoke.class.getName()+" <host> <port>");
            System.exit(1);
        }
        try {
            SSLSocketFactory sslsocketfactory = (SSLSocketFactory) 
            SSLSocketFactory.getDefault();
            SSLSocket sslsocket = (SSLSocket) 
            sslsocketfactory.createSocket(args[0], Integer.parseInt(args[1]));

            InputStream in = sslsocket.getInputStream();
            OutputStream out = sslsocket.getOutputStream();

            // Write a test byte to get a reaction :)
            out.write(1);

            while (in.available() > 0) {
                System.out.print(in.read());
            }
            System.out.println("Successfully connected");

        } catch (Exception exception) {
            exception.printStackTrace();
        }
    }
}

Sobald die Klasse kompiliert wurde, ruft man sie per

<JAVA_HOME>/bin/java SSLPoke <HOST> 443

auf.

Ein erfolgreicher Versuch liefert folgenden Output

<JAVA_HOME>/bin/java SSLPoke <HOST> 443
Successfully connected

wohin gegen ein fehlendes Zertifikat die altbekannte Validarorexception Meldung liefert

sun.security.validator.ValidatorException: PKIX path building failed:
sun.security.provider.certpath.SunCertPathBuilderException: unable to find 
valid certification path to requested target

Um das entsprechend Cert in den Truststore aufzunehmen, erzeugen wir im ersten Schritt eine
Datei welche die Certificate-Chain enthält

echo -n | openssl s_client -connect <HOST>:443 -prexit 2>/dev/null | 
sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > <CERTFILE>

und importieren es im zweiten Schritt mittels des keytool Kommandos

sudo keytool -importcert -file <CERTFILE> -alias <ALIAS> -keystore
JAVA_HOME/lib/security/cacerts -storepass <PASSWORD>

Et voilà schon klappt der Zugriff wie gewünscht