Java日記~Apache HttpComponents でクライアント認証~

こんにちは、技術研究所(通称:技研)のブルマンです。

今回は「Apache HttpComponents HttpClient」について書きます。

コトの始まり

技術研究所ではRedmineの環境にAWS・EC2を使用しているのですが、サーバ通信(HTTPS)は自己署名証明書を使用しています。

自己署名証明書(通称:オレオレ証明書)はインターネット上の一般公開で使用するのは厳禁ですが、
クローズドな環境でHTTPS通信をする場合はやはり頼らざるを得ません。
(AWSのFWでのアクセス制限をかけているだけなので厳密にはクローズでありませんが)

今回はEC2側に自己CA認証局を建てて、クライアント証明書を発行、それでクライアント認証をするという仕組みを作りました。これの構築もかなり苦労したのですが、当記事ではJavaからRestAPIでRedmineを叩くため苦労した結果を紹介します。

「Apache HttpComponents」

さて通信に使うライブラリはどうしましょうか、、
最近は「JAX-RS Client」が流行っている?ようなので使ってみたかったのですが、
情報量が少なく苦戦する可能性があるので、素直に「Apache HttpComponents HttpClient」を使用することに。
バージョンは最新の「4.5.1」を使用します。

で、Google先生で調べてみますとこちらのサイトがTOPに引っかかりました。
http://note.chiebukuro.yahoo.co.jp/detail/n215280

お、いい感じです。
というかやりたいことまんまです。

HttpClientって情報量が多いのですが、3系の情報が引っかかる事が多く、
4系ではそれらのメソッドが片っ端からDeprecatedになっていて結構面倒なんですよね。
素晴らしいことに、このサイトでは4系で書いてあります。

keytoolを使うとキーストアに証明書を登録できるのですが、
今回は動的にCA証明書を登録する方法でやってみます。

コンパイルエラー

動くか確認するためコードを張り付けてみます。
…あれ、コンパイルエラーになりますね。。。


// サーバー証明書のキーストアとクライアント証明書をコンテキストに設定する
SSLContext sslcontext = SSLContexts.custom()
.loadKeyMaterial(keyStore, keyPass)
.loadTrustMaterial(trustStore).build(); ←ここでコンパイルエラー

※http://note.chiebukuro.yahoo.co.jp/detail/n215280抜粋

サイトの説明では4系と書いてはありますが、私が使ってるのは4.5.1と最新版ですので
どうやらメソッドの引数が変わっている模様。

引数に「org.apache.http.ssl.TrustStrategy」が必要となっている。
TrustStrategyはInterfaceなのでサンプルをインターネットで探してみると、
以下のような実装が多く見つかりました。


TrustStrategy trustStrategy = new TrustStrategy() {
public boolean isTrusted(X509Certificate[] certificate, String type) {
return true;
}
};

以下のように実装しなおしてみると、、


// サーバー証明書のキーストアとクライアント証明書をコンテキストに設定する
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の実装、、、セキュリティ的に怪しい感じがする、、、

実行してみると

とりあえず、気になるところは一旦置いておいて動かしてみます。
、、、例外が発生します。


Caused by: java.io.IOException: Invalid keystore format

例外が発生しているのはサーバ証明書の処理の部分ですね。


// サーバー証明書の処理
KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
FileInputStream instream = new FileInputStream(new File("c:/cacert"));
try {
trustStore.load(instream, "nopassword".toCharArray());
} finally {
instream.close();
}

※http://note.chiebukuro.yahoo.co.jp/detail/n215280抜粋

なんだ、これ?調べてみます、、、、
あー、そうか私のCA証明書のファイルPEM形式なのでJavaではそのまま読み込めないようです。

OpenSSLを利用してPEM形式のファイルをPKCS#12形式に変換して、
それを読み込むようにすればいいみたいですが、なんとかこのPEM形式のファイル読み込めないか?

またいろいろ調べてみた結果CertificateFactoryを利用して1回読み込んでからKeyStore
に登録すればいけることを確認。ソースコードを以下のように修正しました。


// CA認証局証明書の処理
KeyStore trustStore = null;
try (FileInputStream is = new FileInputStream(new File("c:/tmp/ca.crt"));) {
CertificateFactory cerFactory = CertificateFactory.getInstance("X.509");
Certificate cert = cerFactory.generateCertificates(is).iterator().next();
trustStore = KeyStore.getInstance("JKS");
trustStore.load(null, "passwordhogehoge".toCharArray());
trustStore.setCertificateEntry("1", cert);
} catch (NoSuchAlgorithmException | CertificateException | IOException | KeyStoreException e) {
throw new RuntimeException(e);
}

動きました!が、うーん、なんかイマイチ、、
やはりPEM形式をやめてPKCS#12形式に変換してから使うべきなようです。

気になる

一応動きはしましたが、この実装っていいのかしら?

httpClientって以下のようなセキュリティホールが以前報告されており、
「JVNDB-2014-003892 Apache HttpComponents HttpClient および HttpAsyncClient の org.apache.http.conn.ssl.AbstractVerifier における SSL サーバになりすまされる脆弱性」

こんなスライドもあったりしますので、
「脆弱性事例に学ぶセキュアコーディング「SSL/TLS証明書検証」編」

このままではいけないように思われます。

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

  • このエントリーをはてなブックマークに追加