Ubuntu 10.10のMySQLとSSL通信でJDBC接続する

apt-getでUbuntu 10.10にインストールしたMySQLはmy.cnfへ設定を追加すればSSLでの接続が可能です。


MySQLSSL接続については、パブリッククラウド上のMySQLと社内サーバーをセキュアに繋ぐ方法の1つとして調べています。
社内サーバーであればLANを切ってセキュリティを確保しますが、クラウド上のMySQLサーバーと通信する場合は、VPNか今回試すSSL接続で行う必要があります。
また、AWSニフティクラウド*1の場合、内部ネットワークは共有なのでセキュリティの重要度が高いデータは同様に暗号化しなければなりません。


今回、MySQLSSL接続を有功にしてJavaのアプリケーションからSSLで接続するところまで通して試してみました。
以降、その手順のメモになります。
(クラウド環境をターゲットとしているのであれば、CentOSの方が利用できるところが多いですが、とりあえず手元のUbuntuの環境で試しています)

MySQLの設定は以下のサイトを参考にしました。
http://mifosforge.jira.com/wiki/display/MIFOS/How+to+enable+MySQL+SSL+on+Ubuntu

公式のドキュメントはこちら:
http://dev.mysql.com/doc/refman/5.1/ja/secure-create-certs.html

SSLが有効か確認
mysql -u root -p
mysql> show variables like 'have_ssl';
+---------------+----------+
| Variable_name | Value    |
+---------------+----------+
| have_ssl      | DISABLED |
+---------------+----------+
1 row in set (0.00 sec)
接続用のキーを生成する

以下のコマンドで必要な証明書やキーを生成します。オプションの箇所は適当に指定しました。

$ cd /etc/mysql
$ sudo openssl genrsa -out ca-key.pem 2048
Generating RSA private key, 2048 bit long modulus
.....................+++
.........+++
e is 65537 (0x10001)
$ sudo openssl req -new -x509 -nodes -days 1000 -key ca-key.pem -out cacert.pem
You are about to be asked to enter information that will be incorporated
into your certificate request.
...
Country Name (2 letter code) [AU]:JP
State or Province Name (full name) [Some-State]:Tokyo
Locality Name (eg, city) :Shinjuku
Organization Name (eg, company) [Internet Widgits Pty Ltd]:co-meeting Inc
Organizational Unit Name (eg, section) :
Common Name (eg, YOUR name) :Hiroyuki Endoh
Email Address :hrendoh@gmail.com
$ sudo openssl req -newkey rsa:2048 -days 1000 -nodes -keyout server-key.pem -out server-req.pem
Generating a 2048 bit RSA private key
.................+++
...
Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password :
An optional company name :    
$ sudo openssl x509 -req -in server-req.pem -days 1000 -CA cacert.pem -CAkey ca-key.pem -set_serial 01 -out server-cert.pem
Signature ok
subject=/C=JP/ST=Tokyo/L=Shinjuku/O=co-meeting Inc/CN=hrendoh/emailAddress=hrendoh@gmail.com
Getting CA Private Key
$ sudo openssl req -newkey rsa:2048 -days 1000 -nodes -keyout client-key.pem -out client-req.pem
Generating a 2048 bit RSA private key
............................+++
...
Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password :
An optional company name :
$ sudo openssl x509 -req -in client-req.pem -days 1000 -CA cacert.pem -CAkey ca-key.pem -set_serial 01 -out client-cert.pem
Signature ok
subject=/C=JP/ST=Tokyo/L=Shinjuku/O=co-meeting Inc/CN=hrendoh/emailAddress=hrendoh@gmail.com
Getting CA Private Key

2011/06/24追記
注意: CentOS 5.5でも試して見たところ、server-key.pemとclient-key.pemの「Common Name」が同じだと接続がエラーとなりました。「Common Name」にそれぞれ異なる値を指定してください。

my.cnfに設定を追加する
$ sudo vi my.cnf

クライアントは太字箇所を追加

# This will be passed to all mysql clients
# It has been reported that passwords should be enclosed with ticks/quotes
# escpecially if they contain "#" chars...
# Remember to edit /etc/mysql/debian.cnf when changing the socket location.
[client]
port            = 3306
socket          = /var/run/mysqld/mysqld.sock
ssl-ca=/etc/mysql/cacert.pem
ssl-cert=/etc/mysql/client-cert.pem
ssl-key=/etc/mysql/client-key.pem

サーバー側は太字箇所のコメント外すだけ

# * Security Features
#
# Read the manual, too, if you want chroot!
# chroot = /var/lib/mysql/
#
# For generating SSL certificates I recommend the OpenSSL GUI "tinyca".
#
ssl-ca=/etc/mysql/cacert.pem 
ssl-cert=/etc/mysql/server-cert.pem
ssl-key=/etc/mysql/server-key.pem
MySQLを再起動
sudo /etc/init.d/mysql restart
設定の確認
$ mysql -uroot
mysql> show variables like 'have_ssl';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| have_ssl      | YES   |
+---------------+-------+
1 row in set (0.00 sec)

有効になりました。

補足:
mysqlに接続するとき、以下のようなエラーが出たら

$ mysql -uroot
ERROR 2026 (HY000): SSL connection error

各ファイルの設定が問題ないかチェック。それでもエラーが出るときは、以下のコマンドでpemファイルを検証

$ sudo openssl verify -purpose sslserver -CAfile cacert.pem /etc/mysql/server-cert.pem

OKの時は以下の出力が出ます。エラーの時は各証明書とキーを作り直した方が解決が早そうです。

/etc/mysql/server-cert.pem: /C=AU/ST=Some-State/O=Internet Widgits Pty Ltd
error 18 at 0 depth lookup:self signed certificate
OK

2011/06/24追記
CentOS 5.5では、server-key.pemとclient-key.pemの「Common Name」が同じ値の場合も同様の接続エラーとなります。その場合は「Common Name」に異なる値を指定してキーを作り直してください。

2011/07/01追記
CentOS 5.5では、my.cnfの[client]にUbuntuではデフォルトで設定されているportとsocketの定義があるとMySQLの起動が出来なくなるので注意。

tcpflowで暗号化を確認

show databasesの通信を見てみました。ちゃんと暗号化されています。

sudo tcpflow -c port 3306 -i wlan0
tcpflow[10901]: listening on wlan0
192.168.100.100.35577-192.168.100.102.03306: ....0N.Mo........D.o5:T.Y.....1..\:........<.t".cS]N.
192.168.100.102.03306-192.168.100.100.35577: .... r.../..k..s.>J.t.d~...@.jJ. .x.y.......PR...z.1.^......d.Ys...\..".3[...n...4..!.....V...>.>o
...X"b.@..Q_(:....4..V..~...(.y:......=.....I9..V}.>0...YI..G.y....[]...W.......+..ti.....qLw

元はこれ

hrendoh@hrendoh-UL20A:~$ sudo tcpflow -c port 3306 -i wlan0
tcpflow[11055]: listening on wlan0
192.168.100.100.48896-192.168.100.102.03306: .....show databases
192.168.100.102.03306-192.168.100.100.48896: .....K....def.information_schema.SCHEMATA.SCHEMATA.Database.SCHEMA_NAME...@................"......information_schema.....mysql.....performance_schema.....test.......".

JDBCで接続してみる

公式ドキュメントに従って設定してみました。
英語:http://dev.mysql.com/doc/refman/5.1/en/connector-j-reference-using-ssl.html
日本語:http://dev.mysql.com/doc/refman/5.1/ja/connector-j-reference-using-ssl.html

ドキュメントによるとSSLを使うとクエリーの実行が30〜50%遅くなるらしいです。
「The performance penalty for enabling SSL is an increase in query processing time between 35% and 50%」

keytoolでトラストストア?を作成

上で作成したサーバーの証明書cacert.pemをインポートします。

$ keytool -import -alias mysqlServerCACert -file /etc/mysql/cacert.pem -keystore truststore
キーストアのパスワードを入力してください:  
新規パスワードを再入力してください: 
所有者: O=Internet Widgits Pty Ltd, ST=Some-State, C=AU
発行者: O=Internet Widgits Pty Ltd, ST=Some-State, C=AU
シリアル番号: c8445e5e08aa9cf5

...

この証明書を信頼しますか? [no]:  yes
証明書がキーストアに追加されました。
クライアント証明書をDER形式に変換後、キーストアにインポート

証明書はDER形式に変換してからじゃないとキーストアにインポート出来ないらしいです。変換はopensslコマンドで実行します。

$ sudo openssl x509 -outform DER -in /etc/mysql/client-cert.pem -out client.cert
$ keytool -import -file client.cert -keystore keystore -alias mysqlClientCertificate
キーストアのパスワードを入力してください:  
新規パスワードを再入力してください: 
所有者: EMAILADDRESS=hrendoh@gmail.com, CN=hrendoh, O=co-meeting Inc, L=Shinjuku, ST=Tokyo, C=JP
発行者: O=Internet Widgits Pty Ltd, ST=Some-State, C=AU
シリアル番号: 1
有効期間の開始日: Thu Jun 23 21:57:22 JST 2011 終了日: Wed Mar 19 21:57:22 JST 2014
証明書のフィンガープリント:
	 MD5:  74:8D:12:1F:47:23:39:A4:E9:32:FF:CC:20:EC:27:24
	 SHA1: E2:48:4F:C8:70:FD:32:C4:FF:AC:F8:B4:DA:2E:22:8B:FD:75:0E:C1
	 署名アルゴリズム名: SHA1withRSA
	 バージョン: 1
この証明書を信頼しますか? [no]:  yes
証明書がキーストアに追加されました。
Java実行時にシステムパラメータを指定
-Djavax.net.ssl.keyStore=path_to_keystore_file
-Djavax.net.ssl.keyStorePassword=*********
-Djavax.net.ssl.trustStore=path_to_truststore_file
-Djavax.net.ssl.trustStorePassword=********* 

Tomcatであればbin/startup.shを呼ぶ際にJAVA_OPTSに上記のシステムパラメータを指定します。

$ export JAVA_OPTS="-Djavax.net.ssl.keyStore=/keystore
 -Djavax.net.ssl.keyStorePassword=keystor -Djavax.net.ssl.trustStore=/truststore -Djavax.net.ssl.trustStorePassword=keystore"
JDBCの設定

太字の「useSSL=true」と「requireSSL=true」を追加します。

 &useSSL=true&requireSSL=true"
                  username="root"
                  password=""
                  validationQuery="select 1" />

以上で、設定終了です。

*1:ニフティはオプションでプライベートに出来ます