特定の接続で異なる証明書を使用するにはどうすればよいですか?
-
21-08-2019 - |
質問
大規模な Java アプリケーションに追加しているモジュールは、SSL で保護された別の会社の Web サイトと通信する必要があります。問題は、サイトが自己署名証明書を使用していることです。中間者攻撃に遭遇していないことを確認するための証明書のコピーを持っています。サーバーへの接続が成功するように、この証明書をコードに組み込む必要があります。
基本的なコードは次のとおりです。
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
理想的には、私のコードは、この 1 つの自己署名証明書をアプリケーション内のこの 1 つの場所で受け入れ、他の場所では受け入れないように Java に教える必要があります。
証明書を JRE の認証局ストアにインポートできることはわかっています。これにより、Java が証明書を受け入れることができるようになります。私が手助けできるのであれば、それはとりたいアプローチではありません。顧客が使用しない可能性のある 1 つのモジュールをすべての顧客のマシンで実行するのは、非常に侵襲的であるように思えます。同じ JRE を使用する他のすべての Java アプリケーションに影響を与えることになるため、他の Java アプリケーションがこのサイトにアクセスする可能性はゼロですが、私はそれが好きではありません。これは簡単な操作ではありません。UNIX では、この方法で JRE を変更するにはアクセス権を取得する必要があります。
また、カスタム チェックを行う TrustManager インスタンスを作成できることも確認しました。この 1 つの証明書を除くすべてのインスタンスで実際の 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
を使用して独自のコードを記述するか、単にJDK(キーツールの「キー入力のためのの動作しませんからkeytool
でそれをインポートすることができます信頼できるエントリー 『)」が、ためだけで結構です』。
keytool -import -file selfsigned.pem -alias server -keystore server.jks
他のヒント
私はこの事を解決するために、オンラインで多くの場所をお読みください。 これは、私はそれを動作させるために書いたコードです。
ByteArrayInputStream derInputStream = new ByteArrayInputStream(app.certificateString.getBytes());
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
X509Certificate cert = (X509Certificate) certificateFactory.generateCertificate(derInputStream);
String alias = "alias";//cert.getSubjectX500Principal().getName();
KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
trustStore.load(null);
trustStore.setCertificateEntry(alias, cert);
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
kmf.init(trustStore, null);
KeyManager[] keyManagers = kmf.getKeyManagers();
TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509");
tmf.init(trustStore);
TrustManager[] trustManagers = tmf.getTrustManagers();
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(keyManagers, trustManagers, null);
URL url = new URL(someURL);
conn = (HttpsURLConnection) url.openConnection();
conn.setSSLSocketFactory(sslContext.getSocketFactory());
app.certificateString証明書が含まれている文字列である、例えばます:
static public String certificateString=
"-----BEGIN CERTIFICATE-----\n" +
"MIIGQTCCBSmgAwIBAgIHBcg1dAivUzANBgkqhkiG9w0BAQsFADCBjDELMAkGA1UE" +
"BhMCSUwxFjAUBgNVBAoTDVN0YXJ0Q29tIEx0ZC4xKzApBgNVBAsTIlNlY3VyZSBE" +
... a bunch of characters...
"5126sfeEJMRV4Fl2E5W1gDHoOd6V==\n" +
"-----END CERTIFICATE-----";
私はそれが自己署名されている場合は、限り、あなたは上記の正確な構造を保つように、証明書の文字列内の任意の文字を置くことができることをテストしています。私は私のラップトップのターミナルコマンドラインでの証明書の文字列を取得します。
を作成する場合は、 SSLSocketFactory
オプションではありません。キーを JVM にインポートするだけです
公開キーを取得します。
$openssl s_client -connect dev-server:443
, 、ファイルを作成します 開発サーバー.pem それは次のようです-----BEGIN CERTIFICATE----- lklkkkllklklklklllkllklkl lklkkkllklklklklllkllklkl lklkkkllklk.... -----END CERTIFICATE-----
キーをインポートします。
#keytool -import -alias dev-server -keystore $JAVA_HOME/jre/lib/security/cacerts -file dev-server.pem
。パスワード: それを変更JVMを再起動します
私たちは、JREのトラストストアをコピーして、システムのプロパティでカスタムトラストストアを使用するようにアプリケーションを教え、そのトラストストアに当社独自の証明書を追加します。この方法では、我々だけでは、デフォルトのJREトラストストアを残しています。
の欠点は、あなたがJREを更新したときに自動的に新しいトラストストアを取得するカスタム1と合併していないということです。
あなたは多分不一致のためのトラストストア/ JDKおよびチェックを検証したり、自動的にトラストストアを更新インストーラまたはスタートアップルーチンを持っていることによって、このシナリオを扱うことができます。私は、アプリケーションの実行中にトラストストアを更新した場合に何が起こるかわかりません。
このソリューションは、100%エレガントまたはきわめて簡単ではありませんが、それは簡単です、動作し、何のコードを必要としません。
私は、自己署名証明書を使用して内部のHTTPSサーバにアクセスするためのコモンズ-にHTTPClientを使用している場合、このような何かをしなければなりませんでした。はい、私たちのソリューションは、単純に(デバッグメッセージをログに記録する)すべてを通過したカスタムのTrustManagerを作成することでした。
このは、それに関連付けられているだけで私たちの地元のTrustManagerを持つように設定されている地元のSSLContextからSSLソケットを作成し、私たち自身のSSLSocketFactoryを持つことに帰着します。あなたはすべてのキーストア/のCertStoreの近くに行く必要はありません。
だから、これが私たちのLocalSSLSocketFactoryである。
static {
try {
SSL_CONTEXT = SSLContext.getInstance("SSL");
SSL_CONTEXT.init(null, new TrustManager[] { new LocalSSLTrustManager() }, null);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("Unable to initialise SSL context", e);
} catch (KeyManagementException e) {
throw new RuntimeException("Unable to initialise SSL context", e);
}
}
public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
LOG.trace("createSocket(host => {}, port => {})", new Object[] { host, new Integer(port) });
return SSL_CONTEXT.getSocketFactory().createSocket(host, port);
}
SecureProtocolSocketFactoryを実装する他の方法と一緒に。 LocalSSLTrustManagerは、前述のダミートラストマネージャの実装です。