こんにちは、技研のブルマンです。

 

だいぶ間が開いてしまいましたが、前回の投稿
Java日記~Apache HttpComponents でクライアント認証~
からの続きとなります。

前回のおさらい

前回は

ただ情報量が少なすぎて調べきれずに今回はタイムアップです。
この件なんと続きます、、、すみません

と続けてしまいました。

続けた理由は以下の実装の

// サーバー証明書のキーストアとクライアント証明書をコンテキストに設定する
TrustStrategy trustStrategy = new TrustStrategy() {
public boolean isTrusted(X509Certificate[] certificate, String type) {
return true;
}
};
SSLContext sslcontext = SSLContexts.custom()
.loadKeyMaterial(keyStore, keyPass)
.loadTrustMaterial(trustStore, acceptingTrustStrategy).build();

TrustStrategyの実装がセキュリティ的にどうなんだ?と疑問が残ったためです。

TrustStrategyクラスを見た限りでは4.1から導入されていますが、情報量が少なすぎて正しい使い道が分かりません。

無効なホスト名

TrustStrategyの実装で常にtrueを返すように実装した場合に、
無効なホスト名が設定されたオレオレ証明書を利用するとどうなるんでしょうか?

動かしてみると以下のエラーがでます。

Caused by: javax.net.ssl.SSLPeerUnverifiedException: Host name ‘AAA’ does not match the certificate subject provided by the peer (EMAILADDRESS=xxx@xxx, CN=BBB, OU=xxx, O=xxx, L=xxx, ST=xxx, C=–)
at org.apache.http.conn.ssl.SSLConnectionSocketFactory.verifyHostname(SSLConnectionSocketFactory.java:465)
at org.apache.http.conn.ssl.SSLConnectionSocketFactory.createLayeredSocket(SSLConnectionSocketFactory.java:395)
at org.apache.http.impl.conn.DefaultHttpClientConnectionOperator.upgrade(DefaultHttpClientConnectionOperator.java:185)

どうやらTrustStrategyの実装にかかわらずホスト名が不正であればエラーになるようです。
TrustStrategyの実装で常にtrueを返すようにしても、一定のセキュリティは担保されている模様です。

TrustStrategyの使い方

TrustStrategyの正しい使い方が分からずにいろいろ調べていくうちに以下のページにたどり着きました。
http://d.hatena.ne.jp/Kazuhira/20151213/1450024135
私が実験した結果も、こちらにほとんど書かれていますね、、、
最初からこのページを見つけていれば、、、

このページではTrustStrategyの実装として、TrustSelfSignedStrategyを利用しています。
オレオレ証明書用にApache HttpComponentsでデフォルトで提供されているクラスのようです。

中の実装を見てみますと、

@Override
public boolean isTrusted(
final X509Certificate[] chain, final String authType) throws CertificateException {
return chain.length == 1;
}

なるほど、証明書チェーンが一つかどうかだけを判定しています。
これ、オレオレ証明書の作り方によってはうまく動かないかもしれませんので注意が必要ですね。

SSLContextBuilderの実装

調べてもよくわからないので、SSLContextBuilderの実装を見てTrustStrategyの使われ方を確認します。

public SSLContextBuilder loadTrustMaterial(
final KeyStore truststore,
final TrustStrategy trustStrategy) throws NoSuchAlgorithmException, KeyStoreException {
final TrustManagerFactory tmfactory = TrustManagerFactory.getInstance(
TrustManagerFactory.getDefaultAlgorithm());
tmfactory.init(truststore);
final TrustManager[] tms = tmfactory.getTrustManagers();
if (tms != null) {
if (trustStrategy != null) {
for (int i = 0; i < tms.length; i++) {
final TrustManager tm = tms[i];
if (tm instanceof X509TrustManager) {
tms[i] = new TrustManagerDelegate(
(X509TrustManager) tm, trustStrategy);
}
}
}
for (final TrustManager tm : tms) {
this.trustmanagers.add(tm);
}
}
return this;
}

、、あーなるほど。
TrustStrategyのインスタンスを渡すとデフォルトの実装を置き換えるんですね。

よく読んでみるとTrustStrategyのJavaDocに書いてあります。

This interface can be used to override the standard JSSE certificate verification process.

デフォルトではJSSE(Java Secure Socket Extension)に沿ったチェック処理が行われますが、
TrustStrategy実装クラスを渡すとそれで置き換えられる。
つまり、どうTrustStrategyを実装するかというより「使わない」がセキュリティ的に正しいようです。
我ながら間抜けです。。

結局のところ

きちんとした証明書を利用する場合は以下のようにTrustStrategyをnullとする。


SSLContext sslcontext = SSLContexts.custom()
.loadKeyMaterial(keyStore, keyPass)
.loadTrustMaterial(trustStore, null).build();

オレオレ証明書を利用する場合は上記実装では動かないので、TrustStrategy#isTrustedを実装したクラスを準備する。
オレオレ証明書の中身は想定できるのでisTrustedメソッドで証明書をチェックするようにする。
といったところでしょうか。

二週以上も引っ張ったにもかかわらず、かなりしょっぱい終わりになってしまいました。。