Je développe une application Java qui interroge une API REST sur un serveur distant via HTTP. Pour des raisons de sécurité, cette communication doit être basculée sur HTTPS.
Maintenant que Let's Encrypt a commencé sa version bêta publique, j'aimerais savoir si Java fonctionne actuellement (ou est confirmé qu'il fonctionnera ultérieurement) avec ses certificats par défaut.
Let's Encrypt a obtenu son intermédiaire signature croisée par IdenTrust , ce qui devrait être une bonne nouvelle. Cependant, je ne trouve aucun de ces deux éléments dans le résultat de cette commande:
keytool -keystore "..\lib\security\cacerts" -storepass changeit -list
Je sais que des CA de confiance peuvent être ajoutées manuellement sur chaque machine, mais comme mon application devrait pouvoir être téléchargée gratuitement et exécutée sans autre configuration, je cherche des solutions "prêtes à l'emploi". Avez-vous de bonnes nouvelles pour moi?
[ Update 2016-06-08 : selon https://bugs.openjdk.Java.net/browse/JDK-8154757 la CA IdenTrust sera incluse dans Oracle Java 8u101.]
[ Update 2016-08-05 : Java 8u101 a été publié et inclut effectivement le CA IdenTrust: version notes ]
Est-ce que Java prend en charge les certificats Let's Encrypt?
Oui. Le certificat Let's Encrypt est simplement un certificat de clé publique ordinaire. Java le prend en charge (selon chiffrons la compatibilité des certificats , pour Java 7> = 7u111 et Java 8> = 8u101).
Est-ce que Java trust Encryptons les certificats immédiatement?
Non/cela dépend de la machine virtuelle Java. Le magasin de clés de confiance d'Oracle JDK/JRE jusqu'à 8u66 ne contient ni l'autorité de certification Let's Encrypt en particulier, ni l'autorité de certification IdenTrust qui l'a signé. new URL("https://letsencrypt.org/").openConnection().connect();
, par exemple, donne javax.net.ssl.SSLHandshakeException: Sun.security.validator.ValidatorException
.
Vous pouvez cependant fournir votre propre validateur/définir un fichier de clés personnalisé contenant l'autorité de certification racine requise ou importer le certificat dans le fichier de clés certifiées JVM.
https://community.letsencrypt.org/t/will-the-cross-root-cover-trust-by-the-default-list-in-the-jdk-jre/134/1 discute aussi du sujet.
Voici un exemple de code indiquant comment ajouter un certificat au fichier de clés certifiées par défaut au moment de l'exécution. Vous aurez juste besoin d'ajouter le certificat (exporté depuis firefox sous le nom .der et placé dans classpath)
Basé sur Comment puis-je obtenir une liste de certificats racine approuvés en Java? et 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);
}
}
}
Je sais que l'OP a demandé une solution sans modification de la configuration locale, mais si vous souhaitez ajouter la chaîne de confiance au magasin de clés de façon permanente:
$ keytool -trustcacerts \
-keystore $Java_HOME/jre/lib/security/cacerts \
-storepass changeit \
-noprompt \
-importcert \
-file /etc/letsencrypt/live/hostname.com/chain.pem
Réponse détaillée pour ceux d'entre nous désirant apporter des modifications de configuration locales incluant la sauvegarde du fichier de configuration:
Si vous ne possédez pas encore de programme de test, vous pouvez utiliser mon programme de ping Java SSLPing qui teste la négociation TLS (fonctionnera avec n'importe quel port SSL/TLS, pas uniquement HTTPS). Je vais utiliser le fichier SSLPing.jar préconfiguré, mais lire le code et le construire vous-même est une tâche simple et rapide:
$ git clone https://github.com/dimalinux/SSLPing.git
Cloning into 'SSLPing'...
[... output snipped ...]
Étant donné que ma version Java est antérieure à 1.8.0_101 (non publiée à ce jour), un certificat Let's Encrypt ne sera pas vérifié par défaut. Voyons à quoi ressemble l'échec avant d'appliquer le correctif:
$ 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 ...]
Je suis sur Mac OS X avec le jeu de variables d’environnement Java_HOME. Les commandes ultérieures supposent que cette variable est définie pour l’installation Java que vous modifiez:
$ echo $Java_HOME
/Library/Java/JavaVirtualMachines/jdk1.8.0_92.jdk/Contents/Home/
Faites une sauvegarde du fichier cacerts que nous allons modifier afin que vous puissiez annuler toutes les modifications sans avoir à réinstaller le JDK:
$ Sudo cp -a $Java_HOME/jre/lib/security/cacerts $Java_HOME/jre/lib/security/cacerts.orig
Téléchargez le certificat de signature que nous devons importer:
$ wget https://letsencrypt.org/certs/lets-encrypt-x3-cross-signed.der
Effectuer l'importation:
$ 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
Vérifiez que Java se connecte maintenant correctement au port SSL:
$ Java -jar SSLPing/dist/SSLPing.jar helloworld.letsencrypt.org 443
About to connect to 'helloworld.letsencrypt.org' on port 443
Successfully connected
Pour les JDK qui ne prennent pas encore en charge les certificats Let's Encrypt, vous pouvez les ajouter à JDK cacerts
après ce processus (grâce à this ).
Téléchargez tous les certificats sur https://letsencrypt.org/certificates/ (choisissez le format der ) et ajoutez-en un par un avec ce type de commande (exemple pour letsencryptauthorityx1.der
):
keytool -import -keystore PATH_TO_JDK\jre\lib\security\cacerts -storepass changeit -noprompt -trustcacerts -alias letsencryptauthorityx1 -file PATH_TO_DOWNLOADS\letsencryptauthorityx1.der