Поддерживает ли Java сертификаты Let's Encrypt?

125

Я разрабатываю приложение Java, которое запрашивает REST API на удаленном сервере через HTTP. По соображениям безопасности эту связь следует переключить на HTTPS.

Теперь, когда Let's Encrypt запустил публичную бета-версию, я хотел бы знать, работает ли Java в настоящее время (или подтверждено, что будет работать в будущем) с их сертификатами по умолчанию.

Let's Encrypt получил свою промежуточную перекрестную подпись от IdenTrust , что должно быть хорошей новостью. Однако я не могу найти ни одного из этих двух в выводе этой команды:

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

Я знаю, что доверенные центры сертификации могут быть добавлены вручную на каждую машину, но поскольку мое приложение должно быть бесплатным для загрузки и исполняться без какой-либо дополнительной настройки, я ищу решения, которые работают «из коробки». У тебя есть для меня хорошие новости?

Hexaholic
источник
1
Также можно проверить совместимость Let's Encrypt здесь letsencrypt.org/docs/certificate-compatibility
potame
@potame "с Java 8u131 вам все равно нужно добавить сертификат в хранилище доверенных сертификатов", поэтому, если вы получите сертификат от Let's Encrypt, вам нужно будет добавить полученный сертификат в хранилище доверенных сертификатов? Разве не должно быть достаточно того, что их CA включен?
mxro
1
@mxro Привет, спасибо, что обратил на это мое внимание. Мои комментарии выше совершенно не соответствуют действительности (на самом деле проблема была более сложной, чем эта, и относилась к нашей инфраструктуре), и я собираюсь удалить их, потому что они действительно приводят только к путанице. Так что, если у вас jdk> Java 8u101, сертификат Let's Encrypt должен работать и быть правильно распознанным и надежным.
potame
@potame Отлично. Спасибо за пояснение!
mxro

Ответы:

142

[ Обновление 2016-06-08 : согласно https://bugs.openjdk.java.net/browse/JDK-8154757, удостоверяющий центр IdenTrust будет включен в Oracle Java 8u101.]

[ Обновление от 05.08.2016 : выпущена Java 8u101, которая действительно включает в себя IdenTrust CA: примечания к выпуску ]


Поддерживает ли Java сертификаты Let's Encrypt?

Да. Сертификат Let's Encrypt - это обычный сертификат открытого ключа. Java поддерживает его (согласно Let's Encrypt Certificate Compatibility для Java 7> = 7u111 и Java 8> = 8u101).

Доверяет ли Java сертификатам Let's Encrypt из коробки?

Нет / это зависит от JVM. Хранилище доверенных сертификатов Oracle JDK / JRE до 8u66 не содержит ни CA Let's Encrypt, ни удостоверяющего личность центра сертификации, который подписал его перекрестно. new URL("https://letsencrypt.org/").openConnection().connect();например приводит к javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException.

Однако вы можете предоставить свой собственный валидатор / определить собственное хранилище ключей, которое содержит требуемый корневой ЦС, или импортировать сертификат в хранилище доверенных сертификатов JVM.

https://community.letsencrypt.org/t/will-the-cross-root-cover-trust-by-the-default-list-in-the-jdk-jre/134/10 также обсуждает эту тему.


Вот пример кода, который показывает, как добавить сертификат в хранилище доверенных сертификатов по умолчанию во время выполнения. Вам просто нужно добавить сертификат (экспортированный из firefox как .der и вставленный в путь к классам)

На основе Как я могу получить список доверенных корневых сертификатов в Java? и 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
источник
Еще один вопрос: какой файл мне нужно использовать? Let's Encrypt предлагает для загрузки один корневой и четыре промежуточных сертификата . Я пробовал isrgrootx1.derи lets-encrypt-x1-cross-signed.der, но ни один из них не подходит.
Hexaholic 06
@Hexaholic Зависит от того, чему вы хотите доверять, и от того, как на вашем сайте настроены сертификаты. Перейдите, https://helloworld.letsencrypt.orgнапример, к и проверьте цепочку сертификатов в браузере (щелкнув зеленый значок). Для этого вам понадобится сертификат для конкретного сайта, промежуточный сертификат X1 (перекрестная подпись IdenTrust) или сертификат DSTRootCAX3. ISRG Root X1Не работает на сайте HelloWorld , потому что это не в цепочке, которая является альтернативой цепи. Я бы использовал DSTRoot, просто экспортированный через браузер, потому что я нигде не видел его для загрузки.
запл 06
1
Спасибо за код. Не забудьте закрыть InputStream в keyStore.load (Files.newInputStream (ksPath)).
Майкл Вайраз,
3
@ Adapt-dev Нет, почему программное обеспечение должно доверять неизвестной системе для предоставления надежных сертификатов? Программное обеспечение должно иметь возможность доверять сертификатам, которым оно действительно доверяет. Это было бы дырой в безопасности, если бы это означало, что моя java-программа могла бы устанавливать сертификаты для другой программы. Но здесь этого не происходит, код добавляет сертификат только во время его собственного выполнения
zapl 01
3
+1 для отображения кода, который не просто обманывает, устанавливая javax.net.ssl.trustStoreсистемное свойство, но -1 для последующей установки JVM по умолчанию SSLContext. Было бы лучше создать новый, SSLSocketFactoryа затем использовать его для различных подключений (например HttpUrlConnection), чем заменять конфигурацию SSL для всей виртуальной машины. (Я понимаю, что это изменение эффективно только для работающей JVM и не сохраняется и не влияет на другие программы. Я просто думаю, что лучше четко указать, где применяется ваша конфигурация.)
Кристофер Шульц,
65

Я знаю, что OP запросил решение без изменений локальной конфигурации, но если вы хотите добавить цепочку доверия в хранилище ключей навсегда:

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

источник: https://community.letsencrypt.org/t/will-the-cross-root-cover-trust-by-the-default-list-in-the-jdk-jre/134/13

Ян Беркель
источник
6
Да, ОП не просил об этом, но для меня это было самым простым решением!
Auspex
Я импортировал этот сертификат letsencrypt.org/certs/lets-encrypt-x3-cross-signed.pem.txt в свою старую сборку java 8, и веб-сервис, который использует сертификат позволяет зашифровать, недоступен! Это решение было быстрым и простым.
ezwrighter
57

Подробный ответ для тех из нас, кто хочет внести локальные изменения конфигурации, включая резервное копирование файла конфигурации:

1. Перед внесением изменений проверьте, работает ли он.

Если у вас еще нет тестовой программы, вы можете использовать мою программу ping java SSLPing, которая проверяет рукопожатие TLS (будет работать с любым портом SSL / TLS, а не только с HTTPS). Я буду использовать предварительно созданный SSLPing.jar, но прочитать код и собрать его самостоятельно - это быстрая и простая задача:

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

Поскольку моя версия Java более ранняя, чем 1.8.0_101 (на момент написания этой статьи не выпущена), сертификат Let's Encrypt по умолчанию не проверяется. Давайте посмотрим, как выглядит сбой, прежде чем применять исправление:

$ 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. Импортируйте сертификат.

Я использую Mac OS X с установленной переменной среды JAVA_HOME. Более поздние команды будут предполагать, что эта переменная установлена ​​для изменяемой установки Java:

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

Сделайте резервную копию файла cacerts, который мы будем изменять, чтобы вы могли отменить любые изменения без переустановки JDK:

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

Загрузите сертификат подписи, который нам нужно импортировать:

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

Выполните импорт:

$ 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. Убедитесь, что после изменений он работает.

Убедитесь, что Java теперь успешно подключается к порту SSL:

$ java -jar SSLPing/dist/SSLPing.jar helloworld.letsencrypt.org 443
About to connect to 'helloworld.letsencrypt.org' on port 443
Successfully connected
dimalinux
источник
8

Для JDK, которые еще не поддерживают сертификаты Let's Encrypt, вы можете добавить их в JDK cacertsпосле этого процесса (благодаря этому ).

Загрузите все сертификаты на https://letsencrypt.org/certificates/ (выберите формат der ) и добавьте их один за другим с помощью такой команды (пример для letsencryptauthorityx1.der):

keytool -import -keystore PATH_TO_JDK\jre\lib\security\cacerts -storepass changeit -noprompt -trustcacerts -alias letsencryptauthorityx1 -file PATH_TO_DOWNLOADS\letsencryptauthorityx1.der
Энтони О.
источник
Это улучшает ситуацию, но затем я получаю сообщение об ошибке подключения: javax.net.ssl.SSLException: java.lang.RuntimeException: не удалось создать пару ключей DH.
nafg