HTTP経由でリモートサーバー上のREST APIをクエリするJavaアプリケーションを開発しています。セキュリティ上の理由から、この通信はHTTPSに切り替える必要があります。
これで Let's Encrypt が公開ベータ版を開始しました。Javaがデフォルトで証明書を使用して現在動作している(または将来動作することが確認された)かどうかを知りたいです。
暗号化してみましょう IdenTrustによってクロスサインされました これは良いニュースであるはずです。しかし、私はこのコマンドの出力でこれら二つのどれも見つけることができません:
keytool -keystore "..\lib\security\cacerts" -storepass changeit -list
信頼できるCAは各マシンに手動で追加できることを知っていますが、私のアプリケーションは追加の設定なしにダウンロードして実行可能にする必要があるため、「すぐに使える」ソリューションを探しています。あなたは私にとって良い知らせを持っていますか?
[更新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の場合、 証明書の互換性の暗号化 に従って)。
Javaは信頼できるLet's Encrypt証明書を信頼していますか?
いいえ/それは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/1 トピックについても説明します。
これは、実行時にデフォルトのトラストストアに証明書を追加する方法を示すコード例です。証明書を追加するだけです(Firefoxから.derとしてエクスポートされ、クラスパスに配置されます)。
に基づいて どのように私はJavaで信頼されたルート証明書のリストを取得できますか? and 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);
}
}
}
私は、OPがローカルの設定を変更せずに解決策を要求したことを知っていますが、信頼チェーンをキーストアに恒久的に追加したい場合は:
$ keytool -trustcacerts \
-keystore $Java_HOME/jre/lib/security/cacerts \
-storepass changeit \
-noprompt \
-importcert \
-file /etc/letsencrypt/live/hostname.com/chain.pem
設定ファイルのバックアップを含むローカルの設定変更を喜んで行う私達の人々のための詳細な答え:
まだテストプログラムを持っていないのであれば、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 ...]
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
JavaがSSLポートに接続できたことを確認します。
$ Java -jar SSLPing/dist/SSLPing.jar helloworld.letsencrypt.org 443
About to connect to 'helloworld.letsencrypt.org' on port 443
Successfully connected
Let's Encrypt証明書をまだサポートしていないJDKの場合は、このプロセスに従ってJDKのcacerts
に証明書を追加できます( this のおかげで)。
すべての証明書を 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