MySQLでSSLクライアント証明書を使う

MySQLでSSLクライアント証明書を使ってみたくなったので試してみました。

## MySQL のバージョンとか

 - CentOS 6.4
 - MySQL 5.5.34  (mysql-server-5.5.34-1.el6.remi.x86_64)

## CA証明書とサーバ証明書を作成

証明書を保存するディレクトリを作成します。
どこでもいいのですがとりあえず `/etc/mysql/ssl/` にします。

```console
# mkdir -p /etc/mysql/ssl/
# cd /etc/mysql/ssl/
```

CAの秘密鍵と自己署名証明書を作成します。

```console
# openssl req -new -out ca.crt -keyout ca.key -x509 -days 3650 -newkey rsa:2048 -nodes -subj "/C=JP/ST=Tokyo/L=SonoHen/O=OreOre/OU=Dev/CN=OreOre CA"

Generating a 2048 bit RSA private key
..............................
writing new private key to 'ca.key'
-----
```

秘密鍵のパーミッションを変更します。0644 のままでも動作しますが変更しておいて損はないでしょう。

```console
# chmod 600 ca.key
```

サーバ証明書の秘密鍵とCSRを作成します。CN（`example.jp`）にはサーバのホスト名を指定します。

```console
# openssl req -new -out server.csr -keyout server.key -newkey rsa:2048 -nodes -subj "/C=JP/ST=Tokyo/L=SonoHen/O=OreOre/OU=Dev/CN=example.jp"

Generating a 2048 bit RSA private key
..............................
writing new private key to 'server.key'
-----
```

秘密鍵のパーミッションとオーナーをを変更します。パーミッションを変更する場合 mysqld から読めるようにオーナーの変更も必要です。

```console
# chmod 600 server.key
# chown mysql: server.key
```

サーバ証明書を作成します。

```console
# openssl x509 -req -days 3650 -in server.csr -out server.crt -CA ca.crt -CAkey ca.key -CAcreateserial

Signature ok
subject=/C=JP/ST=Tokyo/L=SonoHen/O=OreOre/OU=Dev/CN=example.jp
Getting CA Private Key
```

クライアント証明書の秘密鍵とCSRを作成し、CSRをサーバに送信します。 *この作業はクライアント側で行います。*

```console
$ openssl req -new -out client.csr -keyout client.key -newkey rsa:2048 -nodes -subj "/C=JP/ST=Tokyo/L=SonoHen/O=OreOre/OU=Dev/CN=ore@example.jp"

Generating a 2048 bit RSA private key
..............................
writing new private key to 'client.key'
-----

$ chmod 600 client.key
$ scp client.csr example.jp:/tmp/
```

サーバ側でクライアント証明書を作成します。

```console
# cd /etc/mysql/ssl/
# openssl x509 -req -days 3650 -in /tmp/client.csr -out /tmp/client.crt -CA ca.crt -CAkey ca.key -CAcreateserial

Signature ok
subject=/C=JP/ST=Tokyo/L=SonoHen/O=OreOre/OU=Dev/CN=ore@example.jp
Getting CA Private Key
```

クライアント側にCA証明書とクライアント証明書をダウンロードします。

```console
$ scp example.jp:/etc/mysql/ssl/ca.crt ./
$ scp example.jp:/tmp/client.crt ./
```

## MySQL の設定

MySQLでサーバ証明書を設定します。
とりあえずSSL通信だけを試すので `ssl-ca` は設定しません。

```conf:my.cnf
ssl-key  = /etc/mysql/ssl/server.key
ssl-cert = /etc/mysql/ssl/server.crt
```

設定を反映します。

```console
# service mysqld restart
```

設定が有効になっていることを確認します。

```console
# mysql -e "SHOW VARIABLES LIKE '%ssl%'"
```

次のように出力されるはずです。

```
+---------------+---------------------------+
| Variable_name | Value                     |
+---------------+---------------------------+
| have_openssl  | YES                       |
| have_ssl      | YES                       |
| ssl_ca        |                           |
| ssl_capath    |                           |
| ssl_cert      | /etc/mysql/ssl/server.crt |
| ssl_cipher    |                           |
| ssl_key       | /etc/mysql/ssl/server.key |
+---------------+---------------------------+
```

SSLが必須なユーザーを作成します。

```console
# mysql
```

次のように作成します。

```sql
GRANT ALL ON test.* TO ssluser@'%' IDENTIFIED BY 'pass' REQUIRE SSL;
```

## SSLで接続

まずは普通に接続してみます。`ssluser` はSSLが必須なので弾かれます。

```console
$ mysql -h example.jp -u ssluser -ppass
ERROR 1045 (28000): Access denied for user 'ssluser'@'192.0.2.101' (using password: YES)
```

`--ssl`オプションを指定してみます。が、これだけだとSSL通信にはなりませんでした。

```console
$ mysql -h example.jp -u ssluser -ppass --ssl
ERROR 1045 (28000): Access denied for user 'ssluser'@'192.0.2.101' (using password: YES)
```

SSLで接続するためには `--ssl-cipher` か `--ssl-ca` か `--ssl-cert` の指定が必要です。

`--ssl-cert` はクライアント証明書を指定するものなので今は使いません。ひとまず他の２つで接続してみます。

```console
$ mysql -h example.jp -u ssluser -ppass --ssl-cipher=DHE-RSA-AES256-SHA
```

```console
$ mysql -h example.jp -u ssluser -ppass --ssl-ca=ca.crt
```

どちらでも接続できました。

`--ssl-ca` を指定する場合はサーバ証明書がそのCA証明書で署名されている必要があります。
なので適当な自己署名証明書を指定しても接続できません。

```console
$ openssl req -new -out another.crt -keyout another.key -x509 -days 3650 -newkey rsa:2048 -nodes -subj "/C=JP/ST=Tokyo/L=SonoHen/O=OreOre/OU=Dev/CN=OreOre Another CA"

Generating a 2048 bit RSA private key
.................................
writing new private key to 'another.key'
-----

$ mysql -h example.jp -u ssluser -ppass --ssl-ca=another.crt
ERROR 2026 (HY000): SSL connection error: error:00000001:lib(0):func(0):reason(1)
```

`--ssl-verify-server-cert` を追加で指定するとサーバ証明書の CommonName と接続先ホスト名の比較も行われます。
例えば、次のように IP アドレスを指定すると接続できません。

```console
$ mysql -h 192.0.2.100 -u ssluser -ppass --ssl-ca=ca.crt --ssl-verify-server-cert
ERROR 2026 (HY000): SSL connection error: SSL certificate validation failure
```

サーバ証明書の CommonName と同じホスト名で接続する必要があります。

```console
$ mysql -h example.jp -u ssluser -ppass --ssl-ca=ca.crt --ssl-verify-server-cert
```

## クライアント証明書を使って接続

次にクライアント証明書を使ってみます。

まずは my.cnf でクライアント証明書に署名したCA証明書を `ssl-ca` に指定します。

```conf:my.cnf
ssl-key  = /etc/mysql/ssl/server.key
ssl-cert = /etc/mysql/ssl/server.crt
ssl-ca   = /etc/mysql/ssl/ca.crt
```

設定を反映します。

```console
# service mysqld restart
```

設定が反映されていることを確認します。

```console
# mysql -e "SHOW VARIABLES LIKE '%ssl%'"
```

次のように表示されます。

```
+---------------+---------------------------+
| Variable_name | Value                     |
+---------------+---------------------------+
| have_openssl  | YES                       |
| have_ssl      | YES                       |
| ssl_ca        | /etc/mysql/ssl/ca.crt     |
| ssl_capath    |                           |
| ssl_cert      | /etc/mysql/ssl/server.crt |
| ssl_cipher    |                           |
| ssl_key       | /etc/mysql/ssl/server.key |
+---------------+---------------------------+
```

クライアント証明書が必要なユーザーを作成します。

```console
$ mysql
```

次のように作成します。

```sql
GRANT ALL ON test.* TO ssluser@'%' IDENTIFIED BY 'pass' REQUIRE X509;
```

クライアント証明書を使わずに接続してみます。`ssluser` はクライアント証明書が必要なので接続できません。

```console
$ mysql -h example.jp -u ssluser -ppass --ssl-cipher=DHE-RSA-AES256-SHA
ERROR 1045 (28000): Access denied for user 'ssluser'@'192.0.2.101' (using password: YES)

$ mysql -h example.jp -u ssluser -ppass --ssl-ca=ca.crt
ERROR 1045 (28000): Access denied for user 'ssluser'@'192.0.2.101' (using password: YES)

$ mysql -h example.jp -u ssluser -ppass --ssl-ca=ca.crt --ssl-verify-server-cert
ERROR 1045 (28000): Access denied for user 'ssluser'@'192.0.2.101' (using password: YES)
```

次のようにクライアント証明書を指定すれば接続出来ます。

```console
$ mysql -h example.jp -u ssluser -ppass --ssl-cert=client.crt --ssl-key=client.key
```

もちろん `--ssl-ca` や `--ssl-verify-server-cert` を一緒に指定することも出来ます。

```console
$ mysql -h example.jp -u ssluser -ppass --ssl-ca=ca.crt --ssl-verify-server-cert --ssl-cert=client.crt --ssl-key=client.key
```

## その他

今回はCA証明書とサーバ証明書を別にしましたが、同じでも大丈夫です。

その場合は次のようにサーバ証明書を自己署名で作成し、

```console
$ openssl req -new -out server.crt -keyout server.key -x509 -days 3650 -newkey rsa:2048 -nodes -subj "/C=JP/ST=Tokyo/L=SonoHen/O=OreOre/OU=Dev/CN=example.jp"
```

クライアント証明書にはサーバ証明書で署名して、

```console
$ openssl x509 -req -days 3650 -in /tmp/client.csr -out /tmp/client.crt -CA server.crt -CAkey server.key -CAcreateserial
```

my.cnf でCA証明書にサーバ証明書を指定して、

```conf:my.cnf
ssl-key  = /etc/mysql/ssl/server.key
ssl-cert = /etc/mysql/ssl/server.crt
ssl-ca   = /etc/mysql/ssl/server.crt
```

クライアント側は `--ssl-ca` にサーバ証明書を指定します。

```console
$ mysql -h example.jp -u ssluser -ppass --ssl-ca=server.crt --ssl-verify-server-cert --ssl-cert=client.crt --ssl-key=client.key
```