Java supporta i certificati Let's Encrypt?


125

Sto sviluppando un'applicazione Java che interroga un'API REST su un server remoto tramite HTTP. Per motivi di sicurezza questa comunicazione dovrebbe essere impostata su HTTPS.

Ora che Let's Encrypt ha avviato la sua beta pubblica, mi piacerebbe sapere se Java attualmente funziona (o è confermato che funzionerà in futuro) con i loro certificati per impostazione predefinita.

Let's Encrypt ha ottenuto la loro firma incrociata intermedia da IdenTrust , che dovrebbe essere una buona notizia. Tuttavia, non riesco a trovare nessuno di questi due nell'output di questo comando:

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

So che è possibile aggiungere manualmente CA affidabili su ogni macchina, ma poiché la mia applicazione dovrebbe essere scaricabile gratuitamente ed eseguibile senza ulteriori configurazioni, sto cercando soluzioni che funzionino "fuori dagli schemi". Hai buone notizie per me?


1
Si può anche controllare la compatibilità di Let's Encrypt qui letsencrypt.org/docs/certificate-compatibility
potame

@potame "con Java 8u131 devi ancora aggiungere il tuo certificato al truststore" quindi se ottieni un certificato da Let's Encrypt, dovrai aggiungere il certificato che hai ottenuto al truststore? Non dovrebbe essere sufficiente che la loro CA sia inclusa?
mxro

1
@mxro Ciao - grazie per aver attirato la mia attenzione su questo. I miei commenti sopra non sono affatto veri (in effetti il ​​problema era più complicato di così e relativo alla nostra infrastruttura) e li rimuoverò perché in effetti stanno solo portando a confusione. Quindi, se hai un jdk> Java 8u101, il certificato Let's Encrypt dovrebbe funzionare ed essere adeguatamente riconosciuto e attendibile.
potame

@potame È eccellente. Grazie per il chiarimento!
mxro

Risposte:


142

[ Aggiornamento 2016-06-08 : secondo https://bugs.openjdk.java.net/browse/JDK-8154757 IdenTrust CA sarà incluso in Oracle Java 8u101.]

[ Aggiornamento 2016-08-05 : Java 8u101 è stato rilasciato e in effetti include IdenTrust CA: note di rilascio ]


Java supporta i certificati Let's Encrypt?

Sì. Il certificato Let's Encrypt è solo un normale certificato di chiave pubblica. Java lo supporta (secondo Let's Encrypt Certificate Compatibility , per Java 7> = 7u111 e Java 8> = 8u101).

Java si fida subito dei certificati Let's Encrypt?

No / dipende dalla JVM. Il truststore di Oracle JDK / JRE fino a 8u66 non contiene né la CA Let's Encrypt in particolare né la CA IdenTrust che l'ha firmata. new URL("https://letsencrypt.org/").openConnection().connect();ad esempio si traduce in javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException.

È tuttavia possibile fornire il proprio validatore / definire un keystore personalizzato che contenga la CA root richiesta o importare il certificato nel truststore JVM.

https://community.letsencrypt.org/t/will-the-cross-root-cover-trust-by-the-default-list-in-the-jdk-jre/134/10 discute anche l'argomento.


Di seguito è riportato un codice di esempio che mostra come aggiungere un certificato al truststore predefinito in fase di esecuzione. Dovrai solo aggiungere il certificato (esportato da Firefox come .der e inserito in classpath)

Basato su Come posso ottenere un elenco di certificati radice attendibili in Java? e 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);
        }
    }

}

Un'altra cosa: quale file devo usare? Let's Encrypt offre una radice e quattro certificati intermedi per il download. Ho provato isrgrootx1.dere lets-encrypt-x1-cross-signed.der, ma nessuno dei due sembra essere quello giusto.
Hexaholic

@Hexaholic Dipende da ciò di cui vuoi fidarti e da come il sito che usi ha i certificati configurati. Andare ad https://helloworld.letsencrypt.orgesempio su e ispezionare la catena di certificati nel browser (facendo clic sull'icona verde). Per questo è necessario o il certificato site specific, quello intermedio X1 (croce firmato da IdenTrust) o quello DSTRootCAX3. Il ISRG Root X1non funziona per il sito helloworld perché non è nella catena, questa è la catena alternativa. Userei quello DSTRoot, semplicemente esportato tramite browser perché non l'ho visto per il download da nessuna parte.
zapl

1
Grazie per il codice. Non dimenticare di chiudere InputStream in keyStore.load (Files.newInputStream (ksPath)).
Michael Wyraz

3
@ adapt-dev No, perché il software dovrebbe fidarsi di un sistema sconosciuto per fornire buoni certificati? Il software deve essere in grado di considerare attendibili i certificati di cui si fida effettivamente. Sarebbe un buco di sicurezza se questo significasse che il mio programma Java potrebbe installare certificati per qualcun altro programma. Ma questo non accade qui, il codice aggiunge il certificato solo durante il proprio runtime
zapl

3
+1 per mostrare il codice che non si limita a imbrogliare impostando la javax.net.ssl.trustStoreproprietà di sistema, ma -1 per poi impostare il valore predefinito della JVM SSLContext. Sarebbe meglio crearne uno nuovo SSLSocketFactorye poi usarlo per varie connessioni (ad esempio HttpUrlConnection) piuttosto che sostituire la configurazione SSL a livello di VM. (Mi rendo conto che questa modifica è efficace solo per la JVM in esecuzione e non persiste o influisce su altri programmi. Penso solo che sia una pratica di programmazione migliore essere espliciti su dove si applica la configurazione.)
Christopher Schultz

65

So che l'OP ha chiesto una soluzione senza modifiche alla configurazione locale, ma nel caso in cui desideri aggiungere permanentemente la catena di fiducia al keystore:

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

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


6
Sì, l'OP non lo chiedeva, ma questa è stata di gran lunga la soluzione più semplice per me!
Auspex

Ho importato questo certificato letsencrypt.org/certs/lets-encrypt-x3-cross-signed.pem.txt nella mia vecchia build java 8 e il servizio web che utilizza il certificato lascia crittografare non è raggiungibile! Questa soluzione è stata rapida e semplice.
ezwrighter

56

Risposta dettagliata per quelli di noi che desiderano apportare modifiche alla configurazione locale che include il backup del file di configurazione:

1. Verifica se funziona prima delle modifiche

Se non hai già un programma di test, puoi utilizzare il mio programma java SSLPing ping che verifica l'handshake TLS (funzionerà con qualsiasi porta SSL / TLS, non solo HTTPS). Userò SSLPing.jar precompilato, ma leggere il codice e crearlo da solo è un'operazione semplice e veloce:

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

Poiché la mia versione di Java è precedente alla 1.8.0_101 (non rilasciata al momento della stesura di questo documento), un certificato Let's Encrypt non verrà verificato per impostazione predefinita. Vediamo come si presenta l'errore prima di applicare la correzione:

$ 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. Importare il certificato

Sono su Mac OS X con la variabile d'ambiente JAVA_HOME impostata. I comandi successivi presumeranno che questa variabile sia impostata per l'installazione java che stai modificando:

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

Eseguire un backup del file cacerts che modificheremo in modo da poter annullare qualsiasi modifica senza reinstallare JDK:

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

Scarica il certificato di firma che dobbiamo importare:

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

Eseguire l'importazione:

$ 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. Verificare che funzioni dopo le modifiche

Verifica che Java sia ora felice di connettersi alla porta SSL:

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

8

Per JDK che non supportano ancora i certificati Let's Encrypt, puoi aggiungerli al JDK cacertsseguendo questo processo (grazie a questo ).

Scarica tutti i certificati su https://letsencrypt.org/certificates/ (scegli il formato der ) e aggiungili uno ad uno con questo tipo di comando (esempio per letsencryptauthorityx1.der):

keytool -import -keystore PATH_TO_JDK\jre\lib\security\cacerts -storepass changeit -noprompt -trustcacerts -alias letsencryptauthorityx1 -file PATH_TO_DOWNLOADS\letsencryptauthorityx1.der

Ciò migliora la situazione ma poi ottengo un errore di connessione: javax.net.ssl.SSLException: java.lang.RuntimeException: Impossibile generare la coppia di chiavi DH
nafg
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.