JavaはLet's Encrypt証明書をサポートしていますか?


124

HTTP経由でリモートサーバー上のREST APIをクエリするJavaアプリケーションを開発しています。セキュリティ上の理由から、この通信はHTTPSに切り替える必要があります。

今すぐことレッツ・暗号化は、彼らのパブリックベータ版を開始し、私はJavaは現在、デフォルトでは、証明書で動作します(または将来的に仕事ができることが確認された)かどうかを知りたいのです。

Let's EncryptはIdenTrustから中間署名を取得しました。これは朗報です。ただし、このコマンドの出力でこれら2つのいずれも見つかりません。

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

信頼できるCAを各マシンに手動で追加できることは知っていますが、私のアプリケーションはそれ以上の構成なしで自由にダウンロードして実行できるはずなので、「そのまま」機能するソリューションを探しています。私に朗報はありますか?


1
Let's Encryptの互換性については、こちらで確認できます。letsencrypt.org
docs /

@potame「Java 8u131では、トラストストアに証明書を追加する必要がある」ので、Let's Encryptから証明書を取得した場合、トラストストアに取得した証明書を追加する必要がありますか?CAが含まれていれば十分ではないでしょうか。
mxro 2017

1
@mxroこんにちは-これに私の注意を引いてくれてありがとう。上記の私のコメントはまったく当てはまりません(実際、問題はそれよりも複雑で、インフラストラクチャに関連しています)。混乱を招いているだけなので、削除します。したがって、jdk> Java 8u101を使用している場合は、Let's Encrypt証明書が機能し、適切に認識および信頼されるはずです。
ポテム2017

@potameすばらしいですね。説明していただきありがとうございます!
mxro 2017

回答:


142

[ アップデート2016-06-08https : //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証明書の互換性に従って)。

Java Trustはそのままで証明書を暗号化しましょうか?

いいえ/ JVMに依存します。8u66までのOracle JDK / JREのトラストストアには、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としてエクスポートされ、クラスパスに配置されます)。

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);
        }
    }

}

もう1つ:どのファイルを使用する必要がありますか?Let's Encryptは、ダウンロード用に1つのルートと4つの中間証明書を提供 します。私が試したisrgrootx1.derlets-encrypt-x1-cross-signed.der、どちらもそれらの正しいものであるように思いません。
ヘキサホリック

@Hexaholic信頼するものと、使用するサイトで証明書を構成する方法によって異なります。https://helloworld.letsencrypt.orgたとえば、に移動し、ブラウザーで証明書チェーンを調べます(緑色のアイコンをクリックします)。これには、サイト固有の証明書、X1中間証明書(IdenTrustによって相互署名されたもの)、またはDSTRootCAX3証明書のいずれかが必要です。ISRG Root X1それは代替チェーンであるチェーン、ではないのでhelloworldのサイトでは動作しません。私はDSTRootを使用します。どこからダウンロードするか見たことがないので、ブラウザー経由でエクスポートしただけです。
zapl 2015

1
コードをありがとう。keyStore.load(Files.newInputStream(ksPath))でInputStreamを閉じることを忘れないでください。
マイケルウィラス2016

3
@ adapt-devいいえ、ソフトウェアは未知のシステムを信頼して適切な証明書を提供する必要があるのはなぜですか?ソフトウェアは、実際に信頼する証明書を信頼できる必要があります。これが私のJavaプログラムが他の誰かのプログラムのために証明書をインストールできることを意味するならば、それはセキュリティホールでしょう。しかし、それはここでは発生せず、コードは自身のランタイム中に証明書を追加するだけです
zapl

3
+1は、javax.net.ssl.trustStoreシステムプロパティを設定するだけで不正を行うコードを表示するのではなく、-1を指定すると、JVMのデフォルトを設定します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暗号化証明書を使用しているWebサービスに到達できません!このソリューションは迅速かつ簡単でした。
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形式を選択)、次の種類のコマンドを使用して1つずつ追加します(の例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:Could not generate DH
keypair
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.