Czy Java obsługuje certyfikaty Let ' s Encrypt?

Rozwijam aplikację Java, która wysyła zapytania do REST API na zdalnym serwerze przez HTTP. Ze względów bezpieczeństwa komunikacja ta powinna zostać przełączona na HTTPS.

Teraz, gdyLet ' s Encrypt rozpoczęła publiczną wersję beta, chciałbym wiedzieć, czy Java obecnie działa (lub jest potwierdzona, że działa w przyszłości) z ich certyfikatami domyślnie.

Let ' s Encrypt got their intermediate cross-signed by IdenTrust , co powinno być dobrą wiadomością. Jednakże, Nie mogę znaleźć żadnego z tych dwóch w wyjściu tego polecenia:

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

Wiem, że zaufany CAs można dodać ręcznie na każdej maszynie, ale ponieważ moja aplikacja powinna być Darmowa do pobrania i wykonywalna bez dalszej konfiguracji, Szukam rozwiązań, które działają "po wyjęciu z pudełka". Masz dla mnie dobre wieści?

Author: Hexaholic, 2015-12-05

4 answers

[Aktualizacja 2016-06-08 : Według https://bugs.openjdk.java.net/browse/JDK-8154757 IDENTRUST CA zostanie dołączony do Oracle Java 8u101.]

[Aktualizacja 2016-08-05: Java 8u101 została wydana i rzeczywiście zawiera identyfikator ca: uwagi do wydania ]


Czy Java obsługuje certyfikaty Let ' s Encrypt?

Tak. Certyfikat Let ' s Encrypt jest zwykłym certyfikatem klucza publicznego. Obsługa Javy (zgodnie z Let ' s Encrypt Certificate Compatibility , dla Java 7 > = 7u111 i Java 8 > = 8u101).

Czy Java trust Let ' s Encrypt certificates out of the box?

Nie / to zależy od JVM. Truststore Oracle JDK / JRE do 8u66 nie zawiera ani specyficznego CA Let ' s Encrypt, ani identyfikatora CA, który go podpisał. new URL("https://letsencrypt.org/").openConnection().connect(); na przykład wyniki w javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException.

Możesz jednak podać swój własny walidator / zdefiniować własny keystore, który zawiera wymagany główny urząd certyfikacji lub zaimportuj certyfikat do JVM truststore.

Https://community.letsencrypt.org/t/will-the-cross-root-cover-trust-by-the-default-list-in-the-jdk-jre/134/10 omawia również ten temat.


Oto przykładowy kod, który pokazuje, jak dodać certyfikat do domyślnego truststore w czasie wykonywania. Wystarczy dodać certyfikat (wyeksportowany z Firefoksa jako .der i umieścić w classpath)

Na podstawie Jak mogę uzyskać lista zaufanych certyfikatów głównych w Javie? i 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);
        }
    }

}
 147
Author: zapl,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-13 11:33:40

Wiem, że OP poprosił o rozwiązanie bez zmian konfiguracji lokalnej, ale na wypadek, gdybyś chciał dodać łańcuch zaufania do keystore na stałe:

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

Źródło: https://community.letsencrypt.org/t/will-the-cross-root-cover-trust-by-the-default-list-in-the-jdk-jre/134/13

 68
Author: Jan Berkel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-02-20 15:38:03

Szczegółowa odpowiedź dla tych z nas, którzy chcą wprowadzić lokalne zmiany konfiguracyjne, które obejmują tworzenie kopii zapasowej pliku konfiguracyjnego:

1. Sprawdź, czy działa przed zmianami

Jeśli nie masz jeszcze programu testowego, możesz użyć mojego programu java SSLPing ping, który testuje uścisk dłoni TLS (będzie działał z dowolnym portem SSL/TLS, a nie tylko HTTPS). Użyję prebuilt SSLPing.jar, ale odczytanie kodu i samodzielne zbudowanie go jest szybkim i łatwym zadaniem:

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

Ponieważ moja wersja Javy jest wcześniej niż 1.8.0_101 (Nie wydana w momencie pisania tego tekstu), certyfikat Let ' s Encrypt domyślnie nie zostanie zweryfikowany. Zobaczmy, jak wygląda awaria przed zastosowaniem poprawki:

$ 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. Import certyfikatu

Jestem na Mac OS X z ustawioną zmienną środowiskową JAVA_HOME. Późniejsze polecenia zakładają, że zmienna jest ustawiona dla modyfikowanej instalacji Javy:

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

Wykonaj kopię zapasową pliku cacerts, który będziemy modyfikować, abyś mógł wycofać dowolną Zmiana bez ponownej instalacji JDK:

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

Pobierz certyfikat podpisywania, który musimy zaimportować:

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

Wykonaj import:

$ 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. Sprawdź, czy działa po zmianach

Sprawdź, czy Java jest teraz zadowolona z połączenia z portem SSL:

$ java -jar SSLPing/dist/SSLPing.jar helloworld.letsencrypt.org 443
About to connect to 'helloworld.letsencrypt.org' on port 443
Successfully connected
 60
Author: dimalinux,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-02-18 11:01:20

Dla JDK, które nie obsługują jeszcze certyfikatów Let ' s Encrypt, możesz dodać je do JDK cacerts po tym procesie (dzięki this ).

Pobierz wszystkie certyfikaty na https://letsencrypt.org/certificates/ (Wybierz format der ) i dodaj je jeden po drugim za pomocą tego rodzaju polecenia (przykład dla letsencryptauthorityx1.der):

keytool -import -keystore PATH_TO_JDK\jre\lib\security\cacerts -storepass changeit -noprompt -trustcacerts -alias letsencryptauthorityx1 -file PATH_TO_DOWNLOADS\letsencryptauthorityx1.der
 8
Author: Anthony O.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-03-20 10:18:14