stone の SSL 暗号化/復号の機能は、 おかげさまで沢山の方々に使って頂いている。 任意の TCP/IP 接続を手軽に SSL 化できるので、 SSL 化のためだけに stone を使っている、というかたも多そうだ。
例えば 25番ポートで接続を受付ける SMTP サーバがある場合、
stone -z key=key.pem -z cert=cert.pem localhost:25 465/ssl
などと実行するだけで、 SSL 化した SMTP を 465番ポートで受付けることができるようになる。 ここで、「key.pem」および「cert.pem」は、 それぞれ秘密鍵と公開鍵のファイルであり、 OpenSSL のコマンドを使って作ることができる (日経Linux に以前私が書いた連載記事の 「第 6 回 stone (後編) 公開かぎと秘密かぎ」でも作り方を説明した)。
SSL (Secure Socket Layer) というと 暗号化を思い浮かべる人が多いかも知れないが、 暗号化に負けず劣らず重要なのが、通信相手の認証である。 認証、つまり通信相手の確認をせずに通信内容だけ暗号化しても、 通信相手が意図した相手でなく盗聴者だったりしたら、 少しも「セキュア」ではない。
もちろん stone には通信相手を SSL で認証する機能がある。
クライアント認証、すなわち通信相手がクライアントの場合は、
「-z verify」を指定し、
サーバ認証、すなわち通信相手がサーバの場合は、
「-q verify」を指定する。
ちなみに stone の SSL 関連のオプションは 全て「-z」か「-q」が前につく。 「-z」は stone がサーバとして振る舞うときのオプション、 「-q」は stone がクライアントとして振る舞うときのオプションである。 通信相手がクライアントの場合というのはつまり stone がサーバとして振る舞う わけだから、「-z verify」になる。
前述した「SSL 化した SMTP」を受付ける stone の場合であれば、 通信相手はクライアントであるから、 「-z verify」を指定する。 このオプションを指定すると、 stone はクライアントに証明書の提示を要求する。 そして提示された証明書を検証するためには、 その証明書を発行した root CA の証明書が必要である。
もちろん、「証明書の提示」といっても、 証明書を送信すれば済む、というものではない。 証明書自体は公開情報 (公開鍵) なので、 それを送信してもクライアントの身の証にはならない。 証明書には対応する秘密鍵があって、 この秘密鍵を持っていることこそがクライアントの身の証である。 SSL プロトコルによってクライアントは秘密鍵をサーバに送信することなく、 自身が秘密鍵を有することをサーバに納得させることができる。
そして、クライアントの身元を第三者的に証明するのが CA の役割である。 ここで言う CA は、 Certificate Authority (認証局) のことであって、 もちろん Cabin Attendant のことではない。 CA を認証する親 CA といった形で CA の階層構造が作られ、 クライアントの証明書を発行するのは末端の CA であることもあるが、 stone が持っている必要があるのは階層構造の一番上、 root CA の証明書のみである (root つまり「根っこ」)。 root CA の証明書一つで、 root CA の子 CA, 孫 CA, ... 階層構造に含まれる全ての CA が発行した 証明書の検証を行なうことができる。
root CA の証明書を置くディレクトリは、
「-z CApath=ディレクトリ」で指定する。
例えば「/usr/local/ssl/certs」ディレクトリに
root CA の証明書を置く場合は、
「-z CApath=/usr/local/ssl/certs」と指定する。
stone がクライアントに証明書の提示を要求し、 クライアントがそれに答えた場合、 stone はクライアントが提示した証明書を発行した root CA を調べ、 その識別名の「ハッシュ値」を求める。 root CA の識別名のハッシュ値は、 root CA の証明書のファイル名を「CA-cert.pem」とすると、 次のように「openssl x509」コマンドで求めることができる。
% openssl x509 -hash -noout -in CA-cert.pem ee31d843
次に stone は、 「-z CApath=ディレクトリ」で指定された証明書ディレクトリ (この例では「/usr/local/ssl/certs」) の中の、 「ハッシュ値.数字」というファイル名の証明書を用いて、 クライアントが提示した証明書の検証を行なう。
例えば root CA の識別名のハッシュ値が「ee31d843」であり、 「/usr/local/ssl/certs/ee31d843.0」が存在するなら、 このファイルを root CA の証明書として用いる。 なぜ ファイル名に「.0」という拡張子をつけるかというと、 この証明書ディレクトリに複数の root CA 証明書を置きたい場合、 「たまたま」ハッシュ値が一致してしまう場合があり得るからである。 その場合は、どちらか一方の証明書の拡張子を「.1」にする。
root CA 証明書は、 「ハッシュ値.数字」という形式のファイル名でなければ stone は参照しないが、 かといって「ハッシュ値.数字」というファイル名では、 どのファイルがどの root CA の証明書だか分からなくなってしまうので、 次のように root CA 証明書「CA-cert.pem」のシンボリックリンクを 作成しておくとよいだろう。
% ln -s CA-cert.pem /usr/local/ssl/certs/ee31d843.0
Windows など、シンボリックリンクが存在しない OS の場合は、 単にコピーしておくだけでもよい。
C:\usr\local\ssl\certs> copy CA-cert.pem ee31d843.0
以上、まとめると stone の設定は次のようになる:
-z key=key.pem -z cert=cert.pem -z verify -z CApath=/usr/local/ssl/certs localhost:25 465/ssl
この設定を例えば stone.config というファイル名で保存し、 「stone -C stone.config」などと実行すれば、 正しいクライアント証明書を提示したクライアントからの接続のみ受付け、 証明書を提示しないクライアントや、 証明書ディレクトリに証明書が存在しない root CA が発行したクライアント証明書を 提示したクライアントからの接続を拒否する。
クライアントが、既知の root CA (の子 CA) が発行した証明書を提示した場合の例:
# stone -z verbose -C stone.config Aug 12 08:04:13.769309 16384 start (2.3c) [21093] Aug 12 08:04:13.773038 16384 stone 3: 127.0.0.1:smtp <- 0.0.0.0:465/ssl Aug 12 08:04:26.534690 16384 3 TCP 5: [depth2=/C=JP/ST=Kanagawa/L=Kawasaki/O=GCD/CN=GCD Root CA/emailAddress=root@gcd.org] Aug 12 08:04:26.551203 16384 3 TCP 5: [depth1=/C=JP/ST=Kanagawa/L=Kawasaki/O=GCD/OU=Hiroaki Sengoku/CN=GCD_Sengoku_CA/emailAddress=sengoku@gcd.org] Aug 12 08:04:26.551674 16384 3 TCP 5: [depth0=/C=JP/ST=Kanagawa/L=Kawasaki/O=GCD/OU=Hiroaki Sengoku/CN=test/emailAddress=sengoku@gcd.org] Aug 12 08:04:26.573916 16384 [SSL cipher=AES256-SHA] Aug 12 08:04:26.573974 16384 [SSL serial=13] Aug 12 08:04:26.574000 16384 [SSL subject=/C=JP/ST=Kanagawa/L=Kawasaki/O=GCD/OU=Hiroaki Sengoku/CN=test/emailAddress=sengoku@gcd.org] Aug 12 08:04:26.574022 16384 [SSL issuer=/C=JP/ST=Kanagawa/L=Kawasaki/O=GCD/OU=Hiroaki Sengoku/CN=GCD_Sengoku_CA/emailAddress=sengoku@gcd.org]
1行目のコマンドラインで「-z verbose」と指定しているが、 これは SSL 関係の詳細ログを表示させるためのオプションである。 「depth2=...」(4行目) が、 root CA の証明書の識別名である。 この証明書は stone にとって既知の証明書なので、 検証が成功する。 「depth1=...」(5行目) は、 root CA の子 CA の証明書の識別名であり、 「depth0=...」(6行目) は、 クライアントの証明書である。
クライアントが、未知の CA が発行したクライアント証明書を提示した場合の例:
Aug 12 08:05:15.587108 16384 3 TCP 5: [depth1=/C=JP/ST=Tokyo/L=Minato-ku/O=K Laboratory Co.,Ltd/CN=KLAB Root CA/emailAddress=root@klab.org] Aug 12 08:05:15.587172 16384 3 TCP 5: verify error err=19 self signed certificate in certificate chain Aug 12 08:05:15.587415 16384 3 TCP 5: SSL_accept lib error:140890B2:SSL routines:SSL3_GET_CLIENT_CERTIFICATE:no certificate returned
1行目に、別の root CA の識別名を表示しているが、 この root CA は証明書ディレクトリの中に証明書が存在しないので、 この stone にとって未知である。 したがって、2行目 「verify error err=19 self signed certificate in certificate chain」 つまり「クライアントが自分で署名した証明書 (いわゆる「オレオレ証明書」) を 提示した」と、 検証に失敗した旨のエラーメッセージが出力されている。 クライアントからの接続はこの直後に切断される。
(次回に続く)
おぉ
仙石浩明CTO の日記 お元気そうだなぁ。 打ち合せとか申し込んだら、OKしてく…
Comment by shibata(hi) shokudou — 2006年11月28日 @ 01:04