1. ホーム
  2. java

[解決済み] 特定の接続で異なる証明書を使用するにはどうすればよいですか?

2022-04-23 05:26:21

質問

当社の大規模なJavaアプリケーションに追加しているモジュールは、他社のSSLで保護されたウェブサイトと通信する必要があります。 問題は、そのサイトが自己署名証明書を使用していることです。 私は中間者攻撃に遭遇していないことを確認するために証明書のコピーを持っており、私はサーバーへの接続が成功するように、この証明書を私たちのコードに組み込む必要があります。

これが基本的なコードです。

void sendRequest(String dataPacket) {
  String urlStr = "https://host.example.com/";
  URL url = new URL(urlStr);
  HttpURLConnection conn = (HttpURLConnection)url.openConnection();
  conn.setMethod("POST");
  conn.setRequestProperty("Content-Length", data.length());
  conn.setDoOutput(true);
  OutputStreamWriter o = new OutputStreamWriter(conn.getOutputStream());
  o.write(data);
  o.flush();
}

自己署名証明書の処理を追加しないと、conn.getOutputStream()で次のような例外が発生して終了します。

Exception in thread "main" 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
....
Caused by: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
....
Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target

理想的には、私のコードはJavaに、この自己署名証明書を、アプリケーションのこの場所だけで、他のどこにも受け入れないように教える必要があります。

JREの認証局ストアに証明書をインポートすれば、Javaがそれを受け入れるようになることは分かっています。 同じJREを使用している他のすべてのJavaアプリケーションに影響しますし、他のJavaアプリケーションがこのサイトにアクセスする確率はゼロであるにもかかわらず、私はそれが好きではありません。 UNIXでは、この方法でJREを変更するためにはアクセス権を取得しなければなりません。

また、いくつかのカスタムチェックを行うTrustManagerのインスタンスを作成できることもわかりました。 この証明書以外のすべてのインスタンスを本物のTrustManagerに委ねるTrustManagerを作成することもできそうです。 しかし、その TrustManager はグローバルにインストールされるようで、我々のアプリケーションからの他のすべての接続に影響すると思われ、私にとってもあまり良い感じではない。

自己署名証明書を受け入れるためにJavaアプリケーションをセットアップするための好ましい、標準的な、または最良の方法は何ですか? 私が上で考えている目標をすべて達成できるのか、それとも妥協しなければならないのか? ファイルやディレクトリ、コンフィギュレーション設定を含み、コードをほとんど含まないオプションはありますか?

解決方法は?

を作成します。 SSLSocket ファクトリーを自分で作成し、それを HttpsURLConnection を使用して接続します。

...
HttpsURLConnection conn = (HttpsURLConnection)url.openConnection();
conn.setSSLSocketFactory(sslFactory);
conn.setMethod("POST");
...

を1つ作成することになります。 SSLSocketFactory を作成し、保持しています。以下は、それを初期化する方法のスケッチです。

/* Load the keyStore that includes self-signed cert as a "trusted" entry. */
KeyStore keyStore = ... 
TrustManagerFactory tmf = 
  TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(keyStore);
SSLContext ctx = SSLContext.getInstance("TLS");
ctx.init(null, tmf.getTrustManagers(), null);
sslFactory = ctx.getSocketFactory();

キーストアの作成でお困りの方は、コメントください。


キーストアを読み込む例です。

KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(trustStore, trustStorePassword);
trustStore.close();

PEM形式の証明書でキーストアを作成するには、以下のようにコードを記述します。 CertificateFactory でインポートするか、あるいは keytool をJDKからダウンロード(キーツール はしない。 は、"キーエントリ" に対して動作しますが、"信頼されたエントリ" に対しては問題ありません)。

keytool -import -file selfsigned.pem -alias server -keystore server.jks