Java는 Let 's Encrypt 인증서를 지원합니까?


125

HTTP를 통해 원격 서버에서 REST API를 쿼리하는 Java 애플리케이션을 개발 중입니다. 보안상의 이유로이 통신은 HTTPS로 전환되어야합니다.

이제 Let 's Encrypt 가 공개 베타를 시작 했으므로 Java가 현재 기본적으로 인증서를 사용하여 작동하는지 (또는 향후 작동 할 것으로 확인되었는지) 알고 싶습니다.

Let 's Encrypt는 IdenTrust의해 중간 교차 서명을 받았으며 이는 좋은 소식입니다. 그러나이 명령의 출력에서 ​​다음 두 가지 중 하나를 찾을 수 없습니다.

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

신뢰할 수있는 CA를 각 시스템에 수동으로 추가 할 수 있다는 것을 알고 있지만 내 애플리케이션은 추가 구성없이 무료로 다운로드하고 실행할 수 있어야하므로 "즉시"작동하는 솔루션을 찾고 있습니다. 나에게 좋은 소식이 있습니까?


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 aug

답변:


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 's Encrypt 인증서를 지원합니까?

예. Let 's Encrypt 인증서는 일반 공개 키 인증서입니다. Java는이를 지원합니다 ( Java 7> = 7u111 및 Java 8> = 8u101 에 대한 Let 's Encrypt Certificate Compatibility 에 따라 ).

Java는 Let 's Encrypt 인증서를 즉시 신뢰합니까?

아니요 / JVM에 따라 다릅니다. 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.

그러나 고유 한 유효성 검사기를 제공하거나 필수 루트 CA가 포함 된 사용자 지정 키 저장소를 정의하거나 인증서를 JVM 신뢰 저장소로 가져올 수 있습니다.

https://community.letsencrypt.org/t/will-the-cross-root-cover-trust-by-the-default-list-in-the-jdk-jre/134/10 에서도 주제에 대해 설명합니다.


다음은 런타임에 기본 신뢰 저장소에 인증서를 추가하는 방법을 보여주는 몇 가지 예제 코드입니다. 인증서를 추가하기 만하면됩니다 (firefox에서 .der로 내보내고 클래스 경로에 넣음).

을 바탕으로 어떻게 자바에서 신뢰할 수있는 루트 인증서 목록을받을 수 있나요? 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는 다운로드 할 수있는 루트 1 개와 중간 인증서 4 개 를 제공합니다. 나는 isrgrootx1.der및을 시도 lets-encrypt-x1-cross-signed.der했지만 둘 다 옳지 않은 것 같습니다.
Hexaholic

@Hexaholic 무엇을 신뢰하고 사용하는 사이트에 인증서가 구성되어 있는지에 따라 다릅니다. 이동 https://helloworld.letsencrypt.org예를 들어 브라우저의 인증서 체인 (녹색 아이콘을 클릭)를 검사합니다. 이를 위해서는 사이트 별 인증서, X1 중간 인증서 (IdenTrust에 의해 교차 서명 됨) 또는 DSTRootCAX3 인증서가 필요합니다. 그만큼ISRG Root X1 이 대안 체인 체인,에없는 때문에 helloworld를 사이트에 대한 작동하지 않습니다. 나는 DSTRoot를 사용하고, 어디서도 다운로드 할 수 없었기 때문에 브라우저를 통해 내보냈습니다.
zapl 2015

1
코드 주셔서 감사합니다. keyStore.load (Files.newInputStream (ksPath))에서 InputStream을 닫는 것을 잊지 마십시오.
Michael Wyraz

3
@ adapt-dev 아니요, 소프트웨어가 좋은 인증서를 제공하기 위해 알 수없는 시스템을 신뢰해야하는 이유는 무엇입니까? 소프트웨어는 실제로 신뢰하는 인증서를 신뢰할 수 있어야합니다. 이것이 내 자바 프로그램이 다른 사람의 프로그램에 대한 인증서를 설치할 수 있다는 것을 의미한다면 보안 구멍이 될 것입니다. 하지만 여기서는 발생하지 않습니다. 코드는 자체 런타임 동안에 만 인증서를 추가합니다
zapl

3
javax.net.ssl.trustStore시스템 속성 을 설정하여 속임수를 쓰지 않는 코드를 표시하는 +1이 아니라 JVM의 기본값을 설정하는 경우 -1입니다 SSLContext. VM 전체 SSL 구성을 대체하는 것보다 새 파일 SSLSocketFactory을 만든 다음 다양한 연결 (예 :)에 사용하는 HttpUrlConnection것이 좋습니다. (이 변경 사항은 실행중인 JVM에만 효과적이며 다른 프로그램에 영향을주지 않습니다. 구성이 적용되는 위치를 명시하는 것이 더 나은 프로그래밍 관행이라고 생각합니다.)
Christopher Schultz

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
예, OP는 그것을 요구하지 않았지만 이것이 나에게 가장 간단한 해결책이었습니다!
Auspex 2016

이 인증서 letsencrypt.org/certs/lets-encrypt-x3-cross-signed.pem.txt 를 이전 Java 8 빌드로 가져 왔고 lets encrypt 인증서를 사용하는 웹 서비스에 연결할 수 없습니다! 이 솔루션은 빠르고 쉬웠습니다.
ezwrighter

56

구성 파일 백업을 포함하여 로컬 구성을 변경하려는 사용자를위한 자세한 답변 :

1. 변경 전에 작동하는지 테스트

아직 테스트 프로그램이없는 경우 TLS 핸드 셰이크를 테스트하는 Java SSLPing ping 프로그램을 사용할 수 있습니다 (HTTPS뿐만 아니라 모든 SSL / TLS 포트에서 작동 함). 미리 빌드 된 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. 인증서 가져 오기

JAVA_HOME 환경 변수가 설정된 Mac OS X에 있습니다. 이후 명령은 수정중인 Java 설치에 대해이 변수가 설정된 것으로 가정합니다.

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

JDK를 다시 설치하지 않고도 변경 사항을 취소 할 수 있도록 수정할 cacerts 파일의 백업을 만듭니다.

$ 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를 들어, 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
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.