HTTPS経由で2つのWebサーバーに接続するアプリケーションをJavaで記述しています。1つはデフォルトの信頼チェーンを介して証明書を取得し、もう1つは自己署名証明書を使用します。最初のサーバーへの接続はそのまま使用できましたが、自己署名証明書を使用したサーバーへの接続は、そのサーバーからの証明書を使用してtrustStoreを作成するまで機能しませんでした。なぜなら、自分で作成すると、デフォルトのトラストストアは無視されるように見えるからです。
私が見つけた解決策の1つは、デフォルトのトラストストアから自分の証明書に証明書を追加することでした。ただし、このtrustStoreを管理し続ける必要があるため、このソリューションは好きではありません。 (これらの証明書が近い将来に静的のままであるとは思いませんよね?)
それとは別に、私は同様の問題を持つ2つの5歳のスレッドを見つけました:
Javaサーバー に対して複数のSSL証明書を取得するにはどうすればよいですか?
これらは両方ともJava SSLインフラストラクチャに深く入り込んでいます。コードのセキュリティレビューで簡単に説明できる、より便利なソリューションがあることを望んでいました。
前の回答 (別の問題について)で言及したものと同様のパターンを使用できます。
基本的に、デフォルトのトラストマネージャーを取得し、独自のトラストストアを使用する2番目のトラストマネージャーを作成します。両方の呼び出しを委任するカスタム信頼マネージャー実装で両方をラップします(一方が失敗した場合、他方にフォールバックします)。
TrustManagerFactory tmf = TrustManagerFactory
.getInstance(TrustManagerFactory.getDefaultAlgorithm());
// Using null here initialises the TMF with the default trust store.
tmf.init((KeyStore) null);
// Get hold of the default trust manager
X509TrustManager defaultTm = null;
for (TrustManager tm : tmf.getTrustManagers()) {
if (tm instanceof X509TrustManager) {
defaultTm = (X509TrustManager) tm;
break;
}
}
FileInputStream myKeys = new FileInputStream("truststore.jks");
// Do the same with your trust store this time
// Adapt how you load the keystore to your needs
KeyStore myTrustStore = KeyStore.getInstance(KeyStore.getDefaultType());
myTrustStore.load(myKeys, "password".toCharArray());
myKeys.close();
tmf = TrustManagerFactory
.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(myTrustStore);
// Get hold of the default trust manager
X509TrustManager myTm = null;
for (TrustManager tm : tmf.getTrustManagers()) {
if (tm instanceof X509TrustManager) {
myTm = (X509TrustManager) tm;
break;
}
}
// Wrap it in your own class.
final X509TrustManager finalDefaultTm = defaultTm;
final X509TrustManager finalMyTm = myTm;
X509TrustManager customTm = new X509TrustManager() {
@Override
public X509Certificate[] getAcceptedIssuers() {
// If you're planning to use client-cert auth,
// merge results from "defaultTm" and "myTm".
return finalDefaultTm.getAcceptedIssuers();
}
@Override
public void checkServerTrusted(X509Certificate[] chain,
String authType) throws CertificateException {
try {
finalMyTm.checkServerTrusted(chain, authType);
} catch (CertificateException e) {
// This will throw another CertificateException if this fails too.
finalDefaultTm.checkServerTrusted(chain, authType);
}
}
@Override
public void checkClientTrusted(X509Certificate[] chain,
String authType) throws CertificateException {
// If you're planning to use client-cert auth,
// do the same as checking the server.
finalDefaultTm.checkClientTrusted(chain, authType);
}
};
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, new TrustManager[] { customTm }, null);
// You don't have to set this as the default context,
// it depends on the library you're using.
SSLContext.setDefault(sslContext);
そのコンテキストをデフォルトのコンテキストとして設定する必要はありません。使用方法は、使用しているクライアントライブラリ(およびソケットファクトリの取得元)によって異なります。
とはいえ、原則として、とにかく必要に応じてトラストストアを常に更新する必要があります。 Java 7 JSSEリファレンスガイドには、これに関する「重要なメモ」がありましたが、現在は同じガイドのバージョン8の 「メモ」 :
JDKのJava-home/lib/security/cacertsファイルには、限られた数の信頼できるルート証明書が付属しています。 keytoolのリファレンスページに記載されているように、このファイルをトラストストアとして使用する場合、このファイルに含まれる証明書を維持(つまり、追加および削除)するのはユーザーの責任です。
接続するサーバーの証明書構成に応じて、ルート証明書を追加する必要があります。適切なベンダーから必要な特定のルート証明書を取得します。