Чи підтримує Java Давайте шифруємо сертифікати?


125

Я розробляю програму Java, яка запитує REST API на віддаленому сервері через HTTP. З міркувань безпеки це повідомлення слід переключити на HTTPS.

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

Давайте Encrypt отримав їх проміжний перехресний підпис від IdenTrust , що має стати хорошою новиною. Однак я не можу знайти жодного з цих двох у виході цієї команди:

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

Я знаю, що довірені ЦО можна додавати вручну на кожній машині, але оскільки моя програма повинна бути безкоштовною для завантаження та виконання, без додаткової конфігурації, я шукаю рішення, які працюють "поза коробкою". Чи є у вас добрі новини?


1
Можна також перевірити Давайте шифруємо
потама

@potame "з Java 8u131 вам все одно доведеться додати свій сертифікат до вашого довіреного магазину", тож якщо ви отримаєте сертифікат від Let's Encrypt, вам потрібно буде додати сертифікат, який ви отримали до довіри? Чи не повинно бути достатньо, щоб їх КС було включено?
mxro

1
@mxro Привіт - дякую за звернення моєї уваги на це. Мої коментарі вище взагалі не відповідають дійсності (насправді проблема була складнішою за цю проблему і стосувалася нашої інфраструктури), і я збираюся їх усунути, оскільки вони справді призводять лише до плутанини. Отже, якщо у вас є jdk> Java 8u101, сертифікат Let Encrypt повинен працювати і належним чином розпізнавати та довіряти йому.
Потама

@potame Це чудово. Дякую за роз’яснення!
mxro

Відповіді:


142

[ Оновлення 2016-06-08 : Відповідно до https://bugs.openjdk.java.net/browse/JDK-8154757 IdenTrust CA буде включено до Oracle Java 8u101.]

[ Оновлення 2016-08-05 : Java 8u101 була випущена і дійсно включає IdenTrust CA: нотатки до випуску ]


Чи підтримує Java Давайте шифруємо сертифікати?

Так. Сертифікат Let Encrypt - це лише звичайний сертифікат відкритого ключа. Java підтримує це (згідно з даними Шифруємо сумісність сертифікатів , для Java 7> = 7u111 та Java 8> = 8u101).

Чи довіряє Java Давайте шифрувати сертифікати з коробки?

Ні / це залежить від СВМ. Довірений магазин Oracle JDK / JRE до 8u66 не містить конкретно Let’s Encrypt CA, а також IdenTrust CA, який перехрестив його. 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 і помістити в classpath)

На підставі Як я можу отримати список надійних кореневих сертифікатів на 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);
        }
    }

}

І ще одне: який файл мені потрібно використовувати? Let's Encrypt пропонує один кореневий та чотири проміжні сертифікати для завантаження. Я спробував isrgrootx1.derі lets-encrypt-x1-cross-signed.der, але жоден з них не здається правильним.
Гексаголік

@Hexaholic Залежить від того, що ви хочете довіряти і як на веб-сайті, який ви використовуєте, налаштовано certs. Перейдіть до https://helloworld.letsencrypt.orgприкладу та огляньте ланцюжок сертифікатів у браузері (натиснувши зелену піктограму). Для цього вам потрібен або спеціальний сертифікат для сайту, проміжний сертифікат X1 (хрест підписаний IdenTrust), або сертифікат DSTRootCAX3. Це ISRG Root X1не працює для сайту helloworld, оскільки він не в ланцюзі, тобто в альтернативному ланцюжку. Я використовував би DSTRoot, просто експортувався через браузер, тому що я його ніде не бачив для завантаження.
zapl

1
Дякую за код. Не забудьте закрити InputStream у keyStore.load (Files.newInputStream (ksPath)).
Майкл Вайраз

3
@ adapt-dev Ні, чому програмне забезпечення довіряє невідомій системі для надання хороших сертифікатів? Програмному забезпеченню потрібно мати можливість довіряти сертифікатам, яким він фактично довіряє. Було б дірку в безпеці, якщо це означатиме, що моя програма Java може встановлювати сертифікати для програми хтось інший. Але цього не відбувається, код лише додає cert під час власного виконання
zapl

3
+1 для показу коду, який не просто обманюється, встановивши javax.net.ssl.trustStoreвластивість системи, але -1 для встановлення за замовчуванням JVM SSLContext. Було б краще створити нове, SSLSocketFactoryа потім використовувати його для різних підключень (наприклад HttpUrlConnection), а не замінювати конфігурацію SSL, що відповідає загальній VM. (Я усвідомлюю, що ця зміна ефективна лише для запущеного JVM і не зберігається і не впливає на інші програми. Я просто думаю, що краща практика програмування має бути чіткою щодо того, де застосовується ваша конфігурація.)
Крістофер Шульц,

65

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

$ 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 build, а веб-сервіс, який використовує сертифікат шифрування lets, недоступний! Це рішення було швидко і легко.
ezwrighter

56

Детальна відповідь для тих, хто бажає внести локальні зміни конфігурації, що включає в себе резервне копіювання файлу конфігурації:

1. Перевірте, чи працює він до змін

Якщо у вас ще немає програми тестування, ви можете використовувати мою програму java SSLPing ping, яка тестує рукостискання 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 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

8

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

Завантажте всі сертифікати на https://letsencrypt.org/certificate/ (виберіть формат 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: Не вдалося створити ключ
керування
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.