Unterstützt Java Let's Encrypt-Zertifikate?

124

Ich entwickle eine Java-Anwendung, die eine REST-API auf einem Remote-Server über HTTP abfragt. Aus Sicherheitsgründen sollte diese Kommunikation auf HTTPS umgestellt werden.

Nachdem Let's Encrypt die öffentliche Beta gestartet hat, möchte ich wissen, ob Java derzeit standardmäßig mit seinen Zertifikaten funktioniert (oder bestätigt wird, dass es in Zukunft funktioniert).

Lassen Sie uns Encrypt von IdenTrust mit einem Zwischensignal versehen , was eine gute Nachricht sein sollte. Ich kann jedoch keine dieser beiden in der Ausgabe dieses Befehls finden:

keytool -keystore "..\lib\security\cacerts" -storepass changeit -list

Ich weiß, dass vertrauenswürdige Zertifizierungsstellen manuell auf jedem Computer hinzugefügt werden können. Da meine Anwendung jedoch ohne weitere Konfiguration kostenlos heruntergeladen und ausgeführt werden kann, suche ich nach Lösungen, die "out of the box" funktionieren. Hast du gute Nachrichten für mich?

Hexaholic
quelle
1
Man kann auch Lassen Sie uns Encrypt Kompatibilität hier letsencrypt.org/docs/certificate-compatibility
potame
@potame "Mit Java 8u131 müssen Sie das Zertifikat noch zu Ihrem Truststore hinzufügen." Wenn Sie also ein Zertifikat von Let's Encrypt erhalten, müssen Sie das Zertifikat, das Sie erhalten haben, zum Truststore hinzufügen. Sollte es nicht ausreichen, dass ihre Zertifizierungsstelle enthalten ist?
MXRO
1
@mxro Hallo - danke, dass du mich darauf aufmerksam gemacht hast. Meine obigen Kommentare gelten überhaupt nicht (tatsächlich war das Problem komplizierter und bezog sich auf unsere Infrastruktur), und ich werde sie entfernen, weil sie tatsächlich nur zu Verwirrung führen. Wenn Sie also ein jdk> Java 8u101 haben, sollte das Let's Encrypt-Zertifikat funktionieren und ordnungsgemäß erkannt und vertrauenswürdig sein.
Potame
@potame Das ist ausgezeichnet. Danke für die Klarstellung!
Mxro

Antworten:

142

[ Update 2016-06-08 : Laut https://bugs.openjdk.java.net/browse/JDK-8154757 wird die IdenTrust-Zertifizierungsstelle in Oracle Java 8u101 enthalten sein.]

[ Update - 2016.08.05 : Java 8u101 wurde veröffentlicht und in der Tat die Identrust CA enthält: Release Notes ]


Unterstützt Java Let's Encrypt-Zertifikate?

Ja. Das Let's Encrypt-Zertifikat ist nur ein reguläres Zertifikat mit öffentlichem Schlüssel. Java unterstützt dies (laut Let's Encrypt Certificate Compatibility für Java 7> = 7u111 und Java 8> = 8u101).

Vertraut Java auf die sofortige Standardverschlüsselung von Zertifikaten?

Nein / es hängt von der JVM ab. Der Truststore von Oracle JDK / JRE bis 8u66 enthält weder die Let's Encrypt-Zertifizierungsstelle noch die IdenTrust-Zertifizierungsstelle, die ihn kreuzsigniert hat. new URL("https://letsencrypt.org/").openConnection().connect();zum Beispiel ergibt javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException.

Sie können jedoch Ihren eigenen Validator bereitstellen / einen benutzerdefinierten Schlüsselspeicher definieren, der die erforderliche Stammzertifizierungsstelle enthält, oder das Zertifikat in den JVM-Truststore importieren.

https://community.letsencrypt.org/t/will-the-cross-root-cover-trust-by-the-default-list-in-the-jdk-jre/134/10 behandelt das Thema ebenfalls.


Hier ist ein Beispielcode, der zeigt, wie Sie zur Laufzeit ein Zertifikat zum Standard-Truststore hinzufügen. Sie müssen nur das Zertifikat hinzufügen (aus Firefox als .der exportiert und in den Klassenpfad eingefügt).

Basierend auf Wie kann ich eine Liste vertrauenswürdiger Stammzertifikate in Java abrufen? und http://developer.android.com/training/articles/security-ssl.html#UnknownCa

import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.KeyStore;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.PKIXParameters;
import java.security.cert.TrustAnchor;
import java.security.cert.X509Certificate;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLHandshakeException;
import javax.net.ssl.TrustManagerFactory;

public class SSLExample {
    // BEGIN ------- ADDME
    static {
        try {
            KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
            Path ksPath = Paths.get(System.getProperty("java.home"),
                    "lib", "security", "cacerts");
            keyStore.load(Files.newInputStream(ksPath),
                    "changeit".toCharArray());

            CertificateFactory cf = CertificateFactory.getInstance("X.509");
            try (InputStream caInput = new BufferedInputStream(
                    // this files is shipped with the application
                    SSLExample.class.getResourceAsStream("DSTRootCAX3.der"))) {
                Certificate crt = cf.generateCertificate(caInput);
                System.out.println("Added Cert for " + ((X509Certificate) crt)
                        .getSubjectDN());

                keyStore.setCertificateEntry("DSTRootCAX3", crt);
            }

            if (false) { // enable to see
                System.out.println("Truststore now trusting: ");
                PKIXParameters params = new PKIXParameters(keyStore);
                params.getTrustAnchors().stream()
                        .map(TrustAnchor::getTrustedCert)
                        .map(X509Certificate::getSubjectDN)
                        .forEach(System.out::println);
                System.out.println();
            }

            TrustManagerFactory tmf = TrustManagerFactory
                    .getInstance(TrustManagerFactory.getDefaultAlgorithm());
            tmf.init(keyStore);
            SSLContext sslContext = SSLContext.getInstance("TLS");
            sslContext.init(null, tmf.getTrustManagers(), null);
            SSLContext.setDefault(sslContext);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    // END ---------- ADDME

    public static void main(String[] args) throws IOException {
        // signed by default trusted CAs.
        testUrl(new URL("https://google.com"));
        testUrl(new URL("https://www.thawte.com"));

        // signed by letsencrypt
        testUrl(new URL("https://helloworld.letsencrypt.org"));
        // signed by LE's cross-sign CA
        testUrl(new URL("https://letsencrypt.org"));
        // expired
        testUrl(new URL("https://tv.eurosport.com/"));
        // self-signed
        testUrl(new URL("https://www.pcwebshop.co.uk/"));

    }

    static void testUrl(URL url) throws IOException {
        URLConnection connection = url.openConnection();
        try {
            connection.connect();
            System.out.println("Headers of " + url + " => "
                    + connection.getHeaderFields());
        } catch (SSLHandshakeException e) {
            System.out.println("Untrusted: " + url);
        }
    }

}
zapl
quelle
Noch etwas: Welche Datei muss ich verwenden? Let's Encrypt bietet ein Root- und vier Zwischenzertifikate zum Download an. Ich habe es versucht isrgrootx1.derund lets-encrypt-x1-cross-signed.der, aber keiner von ihnen scheint der richtige zu sein.
Hexaholic
@Hexaholic Hängt davon ab, was Sie vertrauen möchten und wie auf der von Ihnen verwendeten Site Zertifikate konfiguriert sind. Gehen Sie zum https://helloworld.letsencrypt.orgBeispiel zu und überprüfen Sie die Zertifikatskette im Browser (klicken Sie auf das grüne Symbol). Für dieses benötigen Sie entweder das standortspezifische Zertifikat, das X1-Zwischenzertifikat (von IdenTrust gekreuzt) oder das DSTRootCAX3-Zertifikat. Das ISRG Root X1funktioniert nicht für die helloworld-Site, da es nicht in der Kette ist, das ist die alternative Kette. Ich würde den DSTRoot verwenden, der einfach über den Browser exportiert wird, da ich ihn nirgendwo zum Download gesehen habe.
Zapl
1
Danke für den Code. Vergessen Sie nicht, den InputStream in keyStore.load (Files.newInputStream (ksPath)) zu schließen.
Michael Wyraz
3
@ adapt-dev Nein, warum sollte Software einem unbekannten System vertrauen, um gute Zertifikate zu liefern? Software muss in der Lage sein, Zertifikaten zu vertrauen, denen sie tatsächlich vertraut. Es wäre eine Sicherheitslücke, wenn dies bedeuten würde, dass mein Java-Programm Zertifikate für ein anderes Programm installieren könnte. Aber das passiert hier nicht, der Code fügt das Zertifikat nur zur eigenen Laufzeit hinzu
zapl
3
+1 zum Anzeigen von Code, der nicht nur durch Festlegen der javax.net.ssl.trustStoreSystemeigenschaft betrügt , sondern -1 zum Festlegen der Standardeinstellung der JVM SSLContext. Es ist besser, eine neue zu erstellen SSLSocketFactoryund diese dann für verschiedene Verbindungen (z. B. HttpUrlConnection) zu verwenden, als die VM-weite SSL-Konfiguration zu ersetzen. (Mir ist klar, dass diese Änderung nur für die laufende JVM wirksam ist und andere Programme nicht beibehält oder beeinflusst. Ich denke nur, dass es eine bessere Programmierpraxis ist, explizit anzugeben, wo Ihre Konfiguration angewendet wird.)
Christopher Schultz
65

Ich weiß, dass das OP nach einer Lösung ohne lokale Konfigurationsänderungen gefragt hat, aber falls Sie die Vertrauenskette dauerhaft zum Keystore hinzufügen möchten:

$ keytool -trustcacerts \
    -keystore $JAVA_HOME/jre/lib/security/cacerts \
    -storepass changeit \
    -noprompt \
    -importcert \
    -file /etc/letsencrypt/live/hostname.com/chain.pem

Quelle: https://community.letsencrypt.org/t/will-the-cross-root-cover-trust-by-the-default-list-in-the-jdk-jre/134/13

Jan Berkel
quelle
6
Ja, das OP hat nicht danach gefragt, aber dies war bei weitem die einfachste Lösung für mich!
Auspex
Ich habe dieses Zertifikat letsencrypt.org/certs/lets-encrypt-x3-cross-signed.pem.txt in meinen älteren Java 8-Build importiert und der Webdienst, der das Let- Encrypt- Zertifikat verwendet, ist nicht erreichbar! Diese Lösung war schnell und einfach.
Ezwrighter
56

Detaillierte Antwort für diejenigen von uns, die bereit sind, lokale Konfigurationsänderungen vorzunehmen, einschließlich der Sicherung der Konfigurationsdatei:

1. Testen Sie vor den Änderungen, ob es funktioniert

Wenn Sie noch kein Testprogramm haben, können Sie mein Java-SSLPing-Ping-Programm verwenden, das den TLS-Handshake testet (funktioniert mit jedem SSL / TLS-Port, nicht nur mit HTTPS). Ich werde die vorgefertigte SSLPing.jar verwenden, aber das Lesen des Codes und das Erstellen selbst ist eine schnelle und einfache Aufgabe:

$ git clone https://github.com/dimalinux/SSLPing.git
Cloning into 'SSLPing'...
[... output snipped ...]

Da meine Java-Version früher als 1.8.0_101 ist (zum Zeitpunkt dieses Schreibens noch nicht veröffentlicht), wird ein Let's Encrypt-Zertifikat standardmäßig nicht überprüft. Lassen Sie uns sehen, wie ein Fehler aussieht, bevor Sie das Update anwenden:

$ java -jar SSLPing/dist/SSLPing.jar helloworld.letsencrypt.org 443
About to connect to 'helloworld.letsencrypt.org' on port 443
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
[... output snipped ...]

2. Importieren Sie das Zertifikat

Ich arbeite unter Mac OS X mit der Umgebungsvariablen JAVA_HOME. Spätere Befehle setzen voraus, dass diese Variable für die Java-Installation festgelegt ist, die Sie ändern:

$ echo $JAVA_HOME 
/Library/Java/JavaVirtualMachines/jdk1.8.0_92.jdk/Contents/Home/

Erstellen Sie eine Sicherungskopie der Cacerts-Datei, die wir ändern werden, damit Sie alle Änderungen rückgängig machen können, ohne das JDK neu zu installieren:

$ sudo cp -a $JAVA_HOME/jre/lib/security/cacerts $JAVA_HOME/jre/lib/security/cacerts.orig

Laden Sie das zu importierende Signaturzertifikat herunter:

$ wget https://letsencrypt.org/certs/lets-encrypt-x3-cross-signed.der

Führen Sie den Import durch:

$ sudo keytool -trustcacerts -keystore $JAVA_HOME/jre/lib/security/cacerts -storepass changeit -noprompt -importcert -alias lets-encrypt-x3-cross-signed -file lets-encrypt-x3-cross-signed.der 
Certificate was added to keystore

3. Stellen Sie sicher, dass es nach den Änderungen funktioniert

Stellen Sie sicher, dass Java jetzt gerne eine Verbindung zum SSL-Port herstellt:

$ java -jar SSLPing/dist/SSLPing.jar helloworld.letsencrypt.org 443
About to connect to 'helloworld.letsencrypt.org' on port 443
Successfully connected
Dimalinux
quelle
8

Für JDK, die Let's Encrypt-Zertifikate noch nicht unterstützen, können Sie diese nach cacertsdiesem Vorgang (dank dessen ) zum JDK hinzufügen .

Laden Sie alle Zertifikate unter https://letsencrypt.org/certificates/ herunter (wählen Sie das der- Format) und fügen Sie sie nacheinander mit diesem Befehl hinzu (Beispiel für letsencryptauthorityx1.der):

keytool -import -keystore PATH_TO_JDK\jre\lib\security\cacerts -storepass changeit -noprompt -trustcacerts -alias letsencryptauthorityx1 -file PATH_TO_DOWNLOADS\letsencryptauthorityx1.der
Anthony O.
quelle
Dies verbessert die Situation, aber dann erhalte ich den Verbindungsfehler: javax.net.ssl.SSLException: java.lang.RuntimeException: DH-Schlüsselpaar konnte nicht generiert werden
nafg