検証環境とか開発環境で自己署名証明書(self signed certificate)を使っている場合、証明書の検証に失敗してSSLHandshakeExceptionが発生します。

SpringのRestTepmlateでSSL Certificate Validationを無効にする の様にHttpClient側でSSLの検証を行わない様に設定をすることもできますが、いちいち全てのクライアント側で設定を入れるのは面倒です。

JavaのKeyStoreに自己署名証明書を登録することでSSLの検証を成功させることができるので設定したときのメモを残しておく。

環境

  • CentOS 6
  • Java8
    $ rpm -qa | grep java
    java-1.8.0-openjdk-1.8.0.101-3.b13.el6_8.x86_64
    java-1.8.0-openjdk-devel-1.8.0.101-3.b13.el6_8.x86_64
    tzdata-java-2016f-1.el6.noarch
    java-1.8.0-openjdk-headless-1.8.0.101-3.b13.el6_8.x86_64

OSは違えど仕組みは一緒だと思うので適宜読み替えれば動くと思います。

Javaの自己署名証明書の検証について

CommonNameが正しく設定されていない場合は証明書の名前がおかしいよとExceptionが出ます。これはどうしようもないので証明書のCommonNameを正しく設定する必要があります。

Caused by: java.security.cert.CertificateException: No name matching localhost found
Caused by: javax.net.ssl.SSLHandshakeException: java.security.cert.CertificateException: No name matching localhost found

証明書の検証に失敗したときは以下の様なCausedが表示されるはずです。当然、自己署名証明書なので検証に失敗します。

Caused by: 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

あらかじめ、JavaのKeyStoreに信頼する証明書として登録しておくことでExceptionを回避することができます。

JavaのKeyStoreについて

Javaが参照しているデフォルトのKeyStoreは次の場所にあります。

$ ls -la /usr/lib/jvm/jre-1.8.0-openjdk.x86_64/lib/security/cacerts 
lrwxrwxrwx. 1 root root 41 8月 4 21:36 2016 /usr/lib/jvm/jre-1.8.0-openjdk.x86_64/lib/security/cacerts -> ../../../../../../../etc/pki/java/cacerts

CentOSの場合は上記ですが、${JAVA_HOME}/lib/security/cacertsにあるはずです。

/etc/pki/java/cacertsにCertファイルをImportすればOKです。

Nginxに自己署名証明書を登録する

ついでに動作確認様に使うNginxに自己署名証明書を登録しておく。

$ mkdir /etc/nginx/certs && chmod 700 /etc/nginx/certs
$ cd /etc/nginx/certs
$ openssl genrsa 2048 > server.key
$ openssl req -new -key server.key > server.csr
Common Name (eg, your name or your server's hostname) []:localhost
Common Nameは指定しておくこと。HostNameの検証は回避できないので正しく登録する。

$ openssl x509 -days 3650 -req -signkey server.key < server.csr > server.crt

$ chmod 600 /etc/nginx/certs/*
$ /etc/init.d/nginx configtest
$ /etc/init.d/nginx reload
$ vi /etc/nginx/conf.d/default.conf
server {
(...)
listen 443;
server_name localhost; # common name
ssl on;
ssl_certificate /etc/nginx/certs/server.crt;
ssl_certificate_key /etc/nginx/certs/server.key;
(...)
}

KeyStoreに自己証明書を登録する

Javaが使っているKeyStoreに自己署名証明書のcertificateファイルをimportするだけです。

/etc/nginx/certs/server.crtをKeyStoreにインポートすればOKです。

/etc/pki/java/cacertsのパスワードはchangeitです。

$ keytool -import -alias selfsigned -file /etc/nginx/certs/server.crt -keystore /etc/pki/java/cacerts
キーストアのパスワードを入力してください:
所有者: O=Default Company Ltd, L=Default City, C=XX
発行者: O=Default Company Ltd, L=Default City, C=XX
シリアル番号: 92c66ccd2c24bd98
有効期間の開始日: Tue Oct 18 23:23:15 JST 2016終了日: Fri Oct 16 23:23:15 JST 2026
証明書のフィンガプリント:
MD5: 95:A9:6B:DC:77:7A:14:4F:EF:7C:6C:D8:E3:57:C3:9E
SHA1: 20:B5:65:DC:EF:5A:35:74:BE:1C:CB:C7:81:BA:8C:04:A7:BA:7E:A1
SHA256: 31:8A:04:09:AC:C4:A6:68:13:BF:ED:C4:CB:73:62:F1:D9:3F:DC:25:47:EF:18:CA:90:B8:A8:6F:F3:6A:B2:85
署名アルゴリズム名: SHA1withRSA
バージョン: 1
この証明書を信頼しますか。 [いいえ]: yes
応答が間違っています。もう一度実行してください
この証明書を信頼しますか。 [いいえ]: はい
証明書がキーストアに追加されました

yesじゃだめなんだ:)

KeyStoreに登録できたかどうか確認する

登録できたか確認する。

$ keytool -v -list -keystore /etc/pki/java/cacerts -alias selfsigned
キーストアのパスワードを入力してください:
別名: selfsigned
作成日: 2016/10/29
エントリ・タイプ: trustedCertEntry

所有者: O=Default Company Ltd, L=Default City, C=XX
発行者: O=Default Company Ltd, L=Default City, C=XX
シリアル番号: 92c66ccd2c24bd98
有効期間の開始日: Tue Oct 18 23:23:15 JST 2016終了日: Fri Oct 16 23:23:15 JST 2026
証明書のフィンガプリント:
MD5: 95:A9:6B:DC:77:7A:14:4F:EF:7C:6C:D8:E3:57:C3:9E
SHA1: 20:B5:65:DC:EF:5A:35:74:BE:1C:CB:C7:81:BA:8C:04:A7:BA:7E:A1
SHA256: 31:8A:04:09:AC:C4:A6:68:13:BF:ED:C4:CB:73:62:F1:D9:3F:DC:25:47:EF:18:CA:90:B8:A8:6F:F3:6A:B2:85
署名アルゴリズム名: SHA1withRSA
バージョン: 1

手元にcertificateファイルがない場合

opensslコマンドで簡単に取得できます。取得したserver.crtをKeyStoreにインポートすればOKです。

$ echo -n | openssl s_client -connect localhost:443 | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > server.crt
$ cat server.crt
-----BEGIN CERTIFICATE-----
MIIDKDCCAhACCQCAkaHWrIaJPjANBgkqhkiG9w0BAQUFADBWMQswCQYDVQQGEwJY
(...)
bYQot8puKVIY3GWcWQCfQUXEAVpSzvozmZ7I4o0xktk5nrJllHdpDvbkgPo=
-----END CERTIFICATE-----

確認

Excepotionが出ていたプログラムを実行するとExcepotionが出ないことが確認できるはずです。

おわり。