Java, Let's Encrypt sertifikalarını destekliyor mu?


125

HTTP üzerinden uzak bir sunucuda bir REST API sorgulayan bir Java uygulaması geliştiriyorum. Güvenlik nedenleriyle bu iletişim HTTPS'ye geçirilmelidir.

Şimdi bu edelim Şifrele onların genel beta başladık, Java şu anda çalışır (veya ileride çalışıyor onaylandıktan) varsayılan olarak kendi sertifikaları ile olmadığını bilmek istiyorum.

Let's Encrypt, aracı IdenTrust tarafından çapraz imzaladı ki bu iyi bir haber olmalı. Ancak bu komutun çıktısında bu ikisinin hiçbirini bulamıyorum:

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

Güvenilir CA'ların her makineye manuel olarak eklenebileceğini biliyorum, ancak uygulamamın başka bir yapılandırma olmadan indirilmesi ve çalıştırılabilmesi için ücretsiz olması gerektiğinden, "kutudan çıkar çıkmaz" çalışan çözümler arıyorum. Benim için iyi haberleriniz var mı?


1
Ayrıca Let's Encrypt uyumluluğunu buradan kontrol edebilirsiniz letsencrypt.org/docs/certificate-compatibility
potame

@potame "Java 8u131 ile sertifikanızı yine de güvenli deponuza eklemeniz gerekir" bu yüzden Let's Encrypt'ten bir sertifika alırsanız, sahip olduğunuz sertifikayı güven deposuna eklemeniz gerekir mi? CA'larının dahil edilmesi yeterli olmamalı mı?
mxro

1
@mxro Merhaba - dikkatimi buna çektiğiniz için teşekkürler. Yukarıdaki yorumlarım hiç doğru değil (aslında sorun bundan daha karmaşıktı ve altyapımızla ilgiliydi) ve onları kaldıracağım çünkü aslında sadece kafa karışıklığına yol açıyorlar. Dolayısıyla, bir jdk> Java 8u101'e sahipseniz, Let's Encrypt sertifikası çalışmalı ve uygun şekilde tanınmalı ve güvenilir olmalıdır.
potame

@potame Bu mükemmel. Açıklama için teşekkürler!
mxro

Yanıtlar:


142

[ Güncelleme 2016-06-08 : https://bugs.openjdk.java.net/browse/JDK-8154757'ye göre IdenTrust CA, Oracle Java 8u101'e dahil edilecektir.]

[ Güncelleme 2016-08-05 : Java 8u101 yayınlandı ve gerçekten de IdenTrust CA: sürüm notlarını içeriyor ]


Java, Let's Encrypt sertifikalarını destekliyor mu?

Evet. Let's Encrypt sertifikası yalnızca normal bir genel anahtar sertifikasıdır. Java bunu destekler ( Java 7> = 7u111 ve Java 8> = 8u101 için Let's Encrypt Sertifika Uyumluluğuna göre ).

Java kutudan çıktığı anda Let's Encrypt sertifikalarına güveniyor mu?

Hayır / JVM'ye bağlıdır. Oracle JDK / JRE'nin 8u66'ya kadar olan güven deposu, ne özel olarak Let's Encrypt CA'yı ne de onu çapraz imzalayan IdenTrust CA'sını içerir. new URL("https://letsencrypt.org/").openConnection().connect();örneğin ile sonuçlanır javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException.

Bununla birlikte, kendi doğrulayıcınızı sağlayabilir / gerekli kök CA'yı içeren özel bir anahtar deposu tanımlayabilir veya sertifikayı JVM güven deposuna aktarabilirsiniz.

https://community.letsencrypt.org/t/will-the-cross-root-cover-trust-by-the-default-list-in-the-jdk-jre/134/10 da konuyu tartışıyor.


Burada, çalışma zamanında varsayılan güven deposuna nasıl sertifika ekleneceğini gösteren bazı örnek kod verilmiştir. Sertifikayı eklemeniz yeterlidir (firefox'tan .der olarak dışa aktarılır ve sınıf yoluna yerleştirilir)

Dayanarak nasıl Java güvenilen kök sertifika listesini alabilirsiniz? ve 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);
        }
    }

}

Bir şey daha: Hangi dosyayı kullanmam gerekiyor? Let's Encrypt, indirilmek üzere bir kök ve dört ara sertifika sunar . Denedim isrgrootx1.derve lets-encrypt-x1-cross-signed.derama ikisi de doğru değil gibi görünüyor.
Hexaholic

@Hexaholic Neye güvenmek istediğinize ve kullandığınız sitenin sertifikalarının nasıl yapılandırıldığına bağlıdır. Gidin https://helloworld.letsencrypt.orgörneğin ve tarayıcıda sertifika zincirini (yeşil simgesini tıklatarak) inceleyin. Bunun için siteye özel sertifikaya, X1 ara sertifikasına (IdenTrust tarafından çapraz imzalı) veya DSTRootCAX3 sertifikasına ihtiyacınız var. ISRG Root X1O alternatif zinciridir zinciri içinde olmadığı için helloworld site için çalışmaz. DSTRoot'u kullanırdım, sadece tarayıcı aracılığıyla dışa aktarılır çünkü onu hiçbir yerde indirmek için görmedim.
zapl

1
Kod için teşekkürler. KeyStore.load (Files.newInputStream (ksPath)) içindeki InputStream'i kapatmayı unutmayın.
Michael Wyraz

3
@ adapt-dev Hayır, yazılım iyi sertifikalar sağlamak için neden bilinmeyen bir sisteme güveniyor? Yazılımın gerçekten güvendiği sertifikalara güvenebilmesi gerekir. Bu, java programımın başka biri programı için sertifika yükleyebileceği anlamına gelirse, bu bir güvenlik açığı olur. Ancak bu burada olmaz, kod sertifikayı yalnızca kendi çalışma süresi boyunca ekler
zapl

3
Yalnızca javax.net.ssl.trustStoresistem özelliğini ayarlayarak hile yapmayan kodu göstermek için +1 , JVM'nin varsayılanını ayarlamak için -1 SSLContext. Yeni bir tane oluşturmak SSLSocketFactoryve daha sonra bunu çeşitli bağlantılar için (örneğin HttpUrlConnection) VM çapında SSL yapılandırmasını değiştirmek yerine kullanmak daha iyi olacaktır . (Bu değişikliğin yalnızca çalışan JVM için etkili olduğunu ve diğer programları etkilemediğinin farkındayım. Yapılandırmanızın nerede geçerli olduğunu açıkça belirtmek için daha iyi bir programlama uygulaması olduğunu düşünüyorum.)
Christopher Schultz

65

Operatörün yerel yapılandırma değişiklikleri olmadan bir çözüm istediğini biliyorum, ancak güven zincirini anahtar deposuna kalıcı olarak eklemek istemeniz durumunda:

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

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


6
Evet, OP bunu istemedi, ancak bu benim için açık ara en basit çözümdü!
Auspex

Bu sertifikayı letsencrypt.org/certs/lets-encrypt-x3-cross-signed.pem.txt dosyasını eski java 8 yapıma aktardım ve lets encrypt sertifikasını kullanan web hizmetine erişilemiyor! Bu çözüm hızlı ve kolaydı.
ezwrighter

57

Yapılandırma dosyasını yedeklemeyi içeren yerel yapılandırma değişiklikleri yapmak isteyenler için ayrıntılı yanıt:

1. Değişikliklerden önce çalışıp çalışmadığını test edin

Halihazırda bir test programınız yoksa, TLS anlaşmasını test eden java SSLPing ping programımı kullanabilirsiniz (yalnızca HTTPS ile değil, herhangi bir SSL / TLS bağlantı noktasıyla çalışacaktır). Önceden oluşturulmuş SSLPing.jar dosyasını kullanacağım, ancak kodu okumak ve kendiniz oluşturmak hızlı ve kolay bir iştir:

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

Java sürümüm 1.8.0_101'den daha eski olduğundan (bu yazının yazıldığı tarihte piyasaya sürülmedi), Let's Encrypt sertifikası varsayılan olarak doğrulanmayacaktır. Düzeltmeyi uygulamadan önce hatanın neye benzediğini görelim:

$ 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. Sertifikayı içe aktarın

JAVA_HOME ortam değişkeni setiyle Mac OS X kullanıyorum. Daha sonraki komutlar, bu değişkenin değiştirdiğiniz java kurulumu için ayarlandığını varsayacaktır:

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

Değiştireceğimiz cacerts dosyasının bir yedeğini alın, böylece herhangi bir değişikliği JDK'yi yeniden yüklemeden geri alabilirsiniz:

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

İçe aktarmamız gereken imza sertifikasını indirin:

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

İçe aktarma işlemini gerçekleştirin:

$ 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. Değişikliklerden sonra çalıştığını doğrulayın

Java'nın artık SSL bağlantı noktasına bağlanmaktan memnun olduğunu doğrulayın:

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

8

Henüz edelim Şifrele sertifikalarını desteklemeyen hangi JDK için, JDK için ekleyebilirsiniz cacertsbu süreçte (sayesinde şu bu ).

Https://letsencrypt.org/certificates/ adresinden tüm sertifikaları indirin ( der formatını seçin ) ve bu tür bir komutla tek tek ekleyin (örneğin letsencryptauthorityx1.der):

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

Bu durumu iyileştirir, ancak sonra Bağlantı hatası alıyorum: javax.net.ssl.SSLException: java.lang.RuntimeException: DH anahtar çifti oluşturulamadı
nafg
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.