クライアント証明書で通す

らじる★らじる を録音する仕組みを作って自分だけで使うのはともかく、

秘密のURLってだけで、これといったアクセス制御はしてなかったことを思い出した。

パスワード認証という方法もあるが、いちいちパスワード打つのもめんどくさいし。

そこでクライアント証明書認証を導入することにした。


クライアント証明書認証はStartSSLのコントロールパネルへのログイン時に使ってるのを見たぐらい。

StartSSLが発行したE-mail証明書を、StartSSLのログインに使うと。そういう仕組みなんだね。

これを自分でやるならこういう形でやればよい。

  1. 認証局を作ってオレオレ証明書を作る (cf. SSLの鍵を打ち出す単純な方法)
  2. Apacheに作成した認証局証明書をSSLCACertificateFileに登録する
  3. アクセス制限したいところに SSLVerifyClient require を設定する

自分で認証局を作るのは本当は必須ではないのだが(既存の認証局の作った証明書を使うこともできそう)、

自分で認証局を作ると、自分の認証局が認めた人は全てOKという簡単なアクセス制御が使える。

これについてはオレオレ証明書でなんの問題もないわけだから、自分で認証局を作っちゃうのが得策でしょう。


認証局の立ち上げ方の詳しいところは、以前の記事に書いてあるが、基本的にこれでOK。

# /etc/pki/tls/misc/CA –newca
# openssl ca –gencrl –out /etc/pki/CA/crl.pem

作った認証局証明書は配る必要はないので、とりあえず置いておけばOK。

めんどくさければopenssl.cnfの設定で有効期間36500日とかにしておけばいいだろう。

ただし、認証局証明書とセットでCRLは作っておいた方がいいかもしれない。後に作った証明書を強制失効させるときに使う。

ここまでできたらクライアント証明書を作るのだが、まぁこんな具合。

$ openssl genrsa -out client.key 2048
$ openssl req -new -key client.key -out newreq.pem
# /etc/pki/tls/misc/CA -sign
$ openssl pkcs12 -export -inkey client.key -in newcert.pem -out client.p12 -name "foobar"

最終的にPKCS12形式にしておけば、FirefoxなりWindowsなりAndroidにインポートできる。


Apacheの設定はこんな具合。

SSLCACertificateFile /etc/pki/CA/cacert.pem
SSLCARevocationFile /etc/pki/CA/crl.pem
<Location /SECRET>
  SSLRequireSSL
  SSLVerifyClient require
</Location>

作った認証局証明書とCRLをセットする。

そしてアクセス制限したいところにSSLVerifyClient requireを設定する。

ただし、これだけだとHTTPSを使わずアクセスすると普通に通ってしまうので、SSLRequireSSLを付けてHTTPS必須にする。

これでHTTPSでアクセスすると、クライアント証明書を選択するダイアログが出てくるので、選んでOKとすると、正しくアクセス出来る。

一方、証明書を持たない人がアクセスしても、403エラーということでアクセス拒否される。


とりあえずこれで証明書を持っているか持っていないかのアクセス制御が実現できた。

さらに踏み込んで、証明書でユーザーの識別と認証を行う方法を考えてみる。

そこでBlogの管理画面のログインにクライアント証明書を使う仕組みを作ってみた。

まずはクライアント証明書の呈示を求めるための設定をApacheで行う。

<Location /d/admin.php>
  SSLRequireSSL
  SSLVerifyClient optional
</Location>

さっきの設定にそっくりだが、SSLVerifyClient optional と証明書の呈示を任意にしていること。

必ず証明書で認証を行うなら require にすればよいのだが、今回はID・パスワードでの認証か選択するようにしたので任意にしている。


ここまでしておけば、環境変数にクライアント証明書の情報が格納されるので、これを使えばよい。

PHPだと認証の成否は $_SERVER[“SSL_CLIENT_VERIFY”] に格納され、SUCCESS だと成功を意味する。(認証なしだとNONEになる)

その上で、証明書の情報も格納されているので、例えば $_SERVER[“SSL_CLIENT_S_DN_CN”] に格納されたCNでユーザーを識別できる。

証明書のCNというと、サーバー証明書だとドメイン名(ex. hdmr.org)を格納されることに決まっている。

会社内で使うクライアント証明書だとCNに従業員番号を格納したりするのかな? そういう使い方をしてもいいはず。

僕はCNにE-mailアドレスを格納しといたけど。


最後に、もし証明書を失効させなければならなくなったときの作業を書いておく。

/etc/pki/CA/index.txtに過去に作成した証明書のリスト、newcert 以下に発行した証明書が格納されているので、

失効させたい証明書ファイルを選択して失効させて、それからCRLを更新すればよい。

# openssl ca –revoke /etc/pki/CA/newcerts/xxxxxx.pem
# openssl ca –gencrl –out /etc/pki/CA/crl.pem

PC用の証明書とAndroid用の証明書を作り分けて、Android用の証明書を失ったと仮定して失効させてみた。

ここまでして、Apacheにreloadさせたら、これでAndroidからはアクセスできなくなった。

同じCNを持つ証明書でもPC用の証明書は今まで通り使えるので、特定の証明書を狙い撃ちで失効できたことがわかる。


タブレットの画面でパスワードを打ち込んでというのは、以前より煩わしいと思っていたので、その手間が省けるのは楽だ。

PCはまだましだが、それでも打たなくていいのはそれはそれで楽だと。

なおかつ、クライアント証明書認証に一本化した部分はたいへん攻撃に強いはずである。

証明書さえ失わなければけっこう有用なんじゃないのかなぁ。

HTTPS以外でも、Thunderbirdとdovecotの設定を見る限りではIMAPSの認証手段にもクライアント証明書は使えるみたいだ。

ただ、かなり情報が不足しているので、やろうとすると苦労しそうだが。