ApacheとOpenSSL

署名済みサーバ証明書実装の手順

商用CAに証明してもらう場合

まず、Symantec(旧Verisign)などに署名された正式な証明書を実装する場合の手順。昨今では、やれキー長は 2048 bit以上でなきゃだめだの、ハッシュアルゴリズムは SHA1 ではもう弱いだのと五月蠅い。389 Directory Serverのページなどで GnuTLS Utilsを使うやり方を紹介したが、時代に追いついていく気がないらしく、どうもウマくないので、OpenSSL に立ち戻ることにした。

Apache専用のキー管理ディレクトリを作らなければならない必然性はないが、整理整頓の意味で専用のツリーを作ることにする。

プライベートキーの生成

# cd /etc/pki/
# mkdir apache && cd apache
# mkdir private certs
# cd private
# openssl genrsa -des3 -out server.key 2048
Enter pass phrase: <-パスフレーズ入力。ちゃんと覚えておくこと->
# openssl rsa -text -noout -in server.key  <-内容確認
# chmod 600 server.key

CSRの作成

-sha256オプションについては、例えば Symantec-Verisignでは「不要」と注意書きがしてあったりする。CAの公開している手順書の確認が必要である。

# openssl req -new -key server.key -out server.csr -sha256
Enter pass phrase for server.key: <-プライベートキーのパスフレーズ入力->
<-Contry-code, State or Province(県名), Locality(市町村名), O(団体名), OU(部署), CN(公開ServerのFQDN)など入力->
# openssl req -text -noout -in server.csr  <-内容確認

server.csr を商用CAに送り署名してもらう。

送り返されてきた署名済み証明書全文("--BEGIN CERTIFICATE--"から"--END CERTIFICATE--"までそれらも含めて) をテキストにペースト。基本的にはLF改行で保存。ファイル名は任意だが例として server.crt として保存したとする。

最近は2段階式の中間証明書チェーンが一緒に必要。証明書の案内に書かれたURLからコピペして、これもテキストに保存。2段階それぞれの"--BEGIN CERTIFICATE--"から"--END CERTIFICATE--"までをそれらも含めてひとつのテキストファイルに保存。ファイル名は任意だが例として caintchain.crt に保存したとする。

これらをWebサーバマシンに保存。

/etc/pki/apache/
            \_ certs/
                  \_ server.crt      root:apache 640
                     caintchain.crt  root:apache 640

ApacheのSSLコンフィグインクルードファイル(通常なら/etc/httpd/conf.d/ssl.conf) の以下の箇所を編集。

SSLCertificateFile       /etc/pki/apache/certs/server.crt
SSLCertificateKeyFile    /etc/pki/apache/private/server.key
SSLCertificateChainFile  /etc/pki/apache/certs/caintchain.crt
 
SSLPassPhraseDialog  exec:/etc/httpd/conf/passtool

上記最終行は、httpdの起動時にサーバプライベートキーのパスフレーズを自動入力させるための仕掛け。次のような /etc/httpd/conf/passtool スクリプトを作る。

#!/bin/sh --
if [ "$1/$2" = "servername.com:443/RSA" ]; then
    echo mypassphrase
    exit 0
fi
exit 0

パーミッションで保護。root:root 700 に。httpd はSSLサイト毎に "SERVERNAME:PORT ", "ENC"(DSA,RSA,ECCのいずれか) を第1,第2引数として渡してくるので、if 文でそれを確認してから出力するようにしている。

ちなみに、最近はSSLv2も穴があってダメだとか世知辛い世の中。SSL設定ファイルの SSLCipherSuite ディレクティブで、許容するサイファースペックを絞り上げておいたほうがよい。例えば ここ によると、

互換性重視なら、

SSLCipherSuite EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:ECDHE-RSA-AES128-SHA:DHE-RSA-AES128-GCM-SHA256:\
AES256+EDH:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:\
ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:DHE-RSA-AES256-SHA256:\
DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:\
AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:HIGH:\
!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4

より厳しい規制なら、

SSLCipherSuite EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH

がよろしかろう、だそうだ。前記はMozillaの推奨を元にしているという記述があり、Mozilla Wki に現在載っている基本的互換性仕様は、

SSLCipherSuite ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:\
ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:\
DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:\
ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:\
ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:\
DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:\
AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS

モダンな設定は、

SSLCipherSuite ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:\
ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:\
ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256

となっている。いずれにしろ寿限無じゅげむであり精査している暇がない。

プライベートCAを立てて署名する

テスト目的や、閉鎖系に近い小規模なネットワーク内で、お金をかけずにApacheを少しでもまともにSSL化するなら、プライベートCAという手もある。

プライベートCAの作成

私設認証局を立てる。Apacheの乗っているマシンでも別でもどちらでも構わないが、Webサーバは DMZ にあることが多く、何をされるか分からないので、安全なセグメントにある別のマシン上に開設する方が妥当だろう。検証は CentOS 6 で行った。

まず、/etc/pki/tls/openssl.cnf を修正する。変更点を示す。テキストファイルはこちら

--- openssl.cnf.org	2016-02-24 23:45:46.000000000 +0900
+++ openssl.cnf	2016-03-19 21:57:59.000000000 +0900
@@ -70,9 +70,9 @@
 # crlnumber must also be commented out to leave a V1 CRL.
 # crl_extensions	= crl_ext
 
-default_days	= 365			# how long to certify for
+default_days	= 3650			# how long to certify for
 default_crl_days= 30			# how long before next CRL
-default_md	= default		# use public key default MD
+default_md	= sha256		# use public key default MD
 preserve	= no			# keep passed DN ordering
 
 # A few difference way of specifying how similar the request should look
@@ -85,7 +85,7 @@
 countryName		= match
 stateOrProvinceName	= match
 organizationName	= match
-organizationalUnitName	= optional
+organizationalUnitName	= supplied
 commonName		= supplied
 emailAddress		= optional
 
@@ -104,7 +104,7 @@
 ####################################################################
 [ req ]
 default_bits		= 2048
-default_md		= sha1
+default_md		= sha256
 default_keyfile 	= privkey.pem
 distinguished_name	= req_distinguished_name
 attributes		= req_attributes
@@ -127,25 +127,25 @@
 
 [ req_distinguished_name ]
 countryName			= Country Name (2 letter code)
-countryName_default		= XX
+countryName_default		= JP
 countryName_min			= 2
 countryName_max			= 2
 
 stateOrProvinceName		= State or Province Name (full name)
-#stateOrProvinceName_default	= Default Province
+stateOrProvinceName_default	= Aichi
 
 localityName			= Locality Name (eg, city)
-localityName_default	= Default City
+localityName_default	= Nagoya
 
 0.organizationName		= Organization Name (eg, company)
-0.organizationName_default	= Default Company Ltd
+0.organizationName_default	= Hoge Net
 
 # we can do this but it is not needed normally :-)
 #1.organizationName		= Second Organization Name (eg, company)
 #1.organizationName_default	= World Wide Web Pty Ltd
 
 organizationalUnitName		= Organizational Unit Name (eg, section)
-#organizationalUnitName_default	=
+organizationalUnitName_default	= Information Tech
 
 commonName			= Common Name (eg, your name or your server\'s hostname)
 commonName_max			= 64
@@ -187,7 +187,7 @@            [ usr_cert ]ブロックの中(CAでCSRに署名する際に使われる)
 # nsCertType = client, email, objsign
 
 # This is typical in keyUsage for a client certificate.
-# keyUsage = nonRepudiation, digitalSignature, keyEncipherment
+keyUsage = nonRepudiation, digitalSignature, keyEncipherment
 
 # This will be displayed in Netscape's comment listbox.
 nsComment			= "OpenSSL Generated Certificate"
@@ -244,7 +244,7 @@           [ v3_ca ]ブロックの中(CA操作時に使われる)
 # Key usage: this is typical for a CA certificate. However since it will
 # prevent it being used as an test self-signed certificate it is best
 # left out by default.
-# keyUsage = cRLSign, keyCertSign
+keyUsage = cRLSign, keyCertSign
 
 # Some might want this also
 # nsCertType = sslCA, emailCA

nsCertType やその類はNetscape時代の遺物であり、現代では使うアプリケーションがほぼないらしく、頓着無用だ。

/etc/pki/tls/misc/CA スクリプトを改造 (diff)。後に述べる Client認証キー用ルーティンの追加を含む、というより大部分がそっちだ。
ついでに Perlスクリプト版 も作ってみた。GitHub から最新の CA.pl を拾ってきたのだがかなりテリブルだったので跡形がないほどに改造した。

CAのプライベートキーと自己署名済みパブリックキーの生成。

# ./CA -newca
# cd /etc/pki/CA
# openssl req -text -noout -in careq.pem  <-内容確認
# openssl x509 -text -noout -in cacert.pem  <-内容確認

ブラウザでのインポート用に公開鍵のフォーマットを変換。

# openssl x509 -in cacert.pem -inform pem -out cacert.crt -outform der
# openssl x509 -text -noout -in cacert.crt -inform der  <-内容確認
CA署名済みWebサーバキーの作成

サーバ秘密鍵とCSRの作成。CAスクリプトで -newreq してもいいのだが、この用法においては openssl コマンドを直接打ってもそう込み入ってはいないので、直接コマンドでやることにする。

# cd /etc/pki/tls
# mkdir apache && cd apache
# mkdir private certs
# cd private
# openssl genrsa -des3 -out server.key 2048  <-プライベートキー生成
# openssl rsa -text -noout -in server.key  <-確認
# openssl req -new -key server.key -out newreq.pem -sha256  <-CSR作成
# openssl req -text -noout -in newreq.pem  <-確認

プライベートCAによるサーバCSRへの署名。

# /etc/pki/tls/misc/CA -sign
# openssl x509 -text -noout -in newcert.pem  <-確認
# mv newcert.pem ../certs/server.pem
# rm newreq.pem

server.key を Webサーバの /etc/pki/apache/private/ へ、server.pem/etc/pki/apache/certs/ へ、CAのパブリックキー /etc/pki/CA/cacert.pem/etc/pki/CA/ へコピーする。

Apache設定ファイルへの反映

/etc/httpd/conf.d/ssl.conf の指定箇所。

SSLCertificateFile /etc/pki/apache/certs/server.pem
SSLCertificateKeyFile /etc/pki/apache/private/server.key
SSLCertificateChainFile /etc/pki/CA/cacert.pem
#SSLCACertificateFile  <-cacert.pemはこちらに書いてもよいがクライアント認証用にあけておく
ClientブラウザへのCAパブリックキー配布

クライアントPCに cacert.crt をコピーし、ブラウザで「認証局証明書」ストア (IEだと「信頼されたルート証明機関」) にインポートする。

クライアント認証の実装

プライベートCAで署名したクライアントキーによって、鍵を持っているクライアントしかサイトにアクセスできないようにする。もちろん、プライベートCAをまだ開局していない場合は先に作っておくこと。

準備

/etc/pki/tls/openssl.cnf を雛形にして openssl-webclient.cnf を作成。オリジナルファイルとの diff を示す。

--- openssl.cnf.org	2016-02-24 23:45:46.000000000 +0900
+++ openssl-webclient.cnf	2016-03-19 16:29:32.000000000 +0900
@@ -70,9 +70,9 @@
 # crlnumber must also be commented out to leave a V1 CRL.
 # crl_extensions	= crl_ext
 
-default_days	= 365			# how long to certify for
+default_days	= 730			# how long to certify for
 default_crl_days= 30			# how long before next CRL
-default_md	= default		# use public key default MD
+default_md	= sha256		# use public key default MD
 preserve	= no			# keep passed DN ordering
 
 # A few difference way of specifying how similar the request should look
@@ -85,7 +85,7 @@
 countryName		= match
 stateOrProvinceName	= match
 organizationName	= match
-organizationalUnitName	= optional
+organizationalUnitName	= supplied
 commonName		= supplied
 emailAddress		= optional
 
@@ -104,7 +104,7 @@
 ####################################################################
 [ req ]
 default_bits		= 2048
-default_md		= sha1
+default_md		= sha256
 default_keyfile 	= privkey.pem
 distinguished_name	= req_distinguished_name
 attributes		= req_attributes
@@ -127,25 +127,25 @@
 
 [ req_distinguished_name ]
 countryName			= Country Name (2 letter code)
-countryName_default		= XX
+countryName_default		= JP
 countryName_min			= 2
 countryName_max			= 2
 
 stateOrProvinceName		= State or Province Name (full name)
-#stateOrProvinceName_default	= Default Province
+stateOrProvinceName_default	= Aichi
 
 localityName			= Locality Name (eg, city)
-localityName_default	= Default City
+localityName_default	= Nagoya
 
 0.organizationName		= Organization Name (eg, company)
-0.organizationName_default	= Default Company Ltd
+0.organizationName_default	= Hoge Net
 
 # we can do this but it is not needed normally :-)
 #1.organizationName		= Second Organization Name (eg, company)
 #1.organizationName_default	= World Wide Web Pty Ltd
 
 organizationalUnitName		= Organizational Unit Name (eg, section)
-#organizationalUnitName_default	=
+organizationalUnitName_default	= Information Tech
 
 commonName			= Common Name (eg, your name or your server\'s hostname)
 commonName_max			= 64
@@ -187,7 +187,7 @@    <-[ usr_cert ]ブロック
 # nsCertType = client, email, objsign
 
 # This is typical in keyUsage for a client certificate.
-# keyUsage = nonRepudiation, digitalSignature, keyEncipherment
+keyUsage = nonRepudiation, digitalSignature, keyEncipherment
 
 # This will be displayed in Netscape's comment listbox.
 nsComment			= "OpenSSL Generated Certificate"
@@ -214,7 +214,7 @@
 #nsSslServerName
 
 # This is required for TSA certificates.
-# extendedKeyUsage = critical,timeStamping
+extendedKeyUsage = clientAuth
 
 [ v3_req ]
 
@@ -244,7 +244,7 @@    <-[ v3_ca ]ブロック
 # Key usage: this is typical for a CA certificate. However since it will
 # prevent it being used as an test self-signed certificate it is best
 # left out by default.
-# keyUsage = cRLSign, keyCertSign
+keyUsage = cRLSign, keyCertSign
 
 # Some might want this also
 # nsCertType = sslCA, emailCA

最初だけ、ディレクトリ構造を作らせるためにダミーのキーを一式作成する。

# cd /etc/pki/tls/misc
# ./CA -newwebclient client0 (-newwebclientはCAスクリプトの改造で加えたルーティン)
<中略>
Enter Export Password: <入力>
Verifying - Enter Export Password: <再入力>
# ls -lR /etc/pki/webclient

目的は達したのですぐ削除。

# openssl ca -revoke /etc/pki/webclient/certs/client0.pem
Enter pass phrase for /etc/pki/CA/private/cakey.pem: <入力>
Revoking Certificate XXXXXXXXXX.
Data Base Updated
# rm /etc/pki/webclient/*/client0.*

PKCS#12キーバッグ(Client秘密鍵とCA署名済みClient公開鍵の合成物)作成時のエクスポートパスワード入力を省くためのパスワードファイルを配置する。内容はパスワード文字列 1行のみ。

# echo 'exportpassword' >/etc/pki/webclient/private/exportpw
# chmod 600 /etc/pki/webclient/private/exportpw

実際のクライアントキー作成

# cd /etc/pki/tls/misc
# ./CA -newwebclient client1 (引数は整理のためのクライアント名で、ファイル名に使われる)
<中略>
Country Name (2 letter code) [JP]:
State or Province Name (full name) [Aichi]:
Locality Name (eg, city) [Nagoya]:
Organization Name (eg, company) [Hoge Net]:
Organizational Unit Name (eg, section) [Information Tech]:
Common Name (eg, your name or your server's hostname) []:client1 <-後々管理の糸口となるので必ず入力
Email Address []:
<中略>
Keys generated successfully:
/etc/pki/webclient/certs/client1.pem     Client public key(signed)
/etc/pki/webclient/private/client1.key   Client private key
/etc/pki/webclient/private/client1.p12   Client PFX(above two combined)<-実際必要なのはこれ
/etc/pki/CA/cacert.crt       Private-CA public key <-この時作られるわけではないが参考のためプリントさせている

なお、RHEL/CentOS 6 に付属しているシェルスクリプト版CAスクリプトの、もともと持っている機能として、環境変数による挙動切り替え機能がある。

# DAYS="-days 180" ./CA -newwebclient client1

とやると、署名後のクライアント公開鍵の有効期限は、openssl-webclient.cnfdefault_days やCAスクリプト序盤で定義しているDAYSを無視して、180 日になる。また、

# SSLEAY_CONFIG="-config /PATH/TO/ALT_OPENSSL.cnf" ./CA -newwebclient client1

とやれば、使用する openssl設定ファイルを一時的に切り替えることができる。同様な仕掛けの環境変数としては他に OPENSSL (既定値=openssl) と CATOP (既定値=/etc/pki/CA) があり、今回の大改造に伴い更に CERTTOP (既定値=/etc/pki/webclient) を付け加えた。

Perl版もだいたい同じ。ただし、-revoke の代わりに -revoke-webclient (ベースにしたCA.pl は既に汎用的な -revoke ファンクションを持っていたため)、環境変数 SSLEAY_CONFIG の代わりに OPENSSL_CONF となっている。

署名の行程で"failed to update database. TXT_DB error number 2"という openssl のエラーが出て署名できないことがある。原因はたいてい、DNの重複したパブリックキーがまだ有効なものとしてCAの記録に残っているためだ。まずは、revoke してからリトライしてみる。それでも駄目な場合は、テキストベースのデータベースである index.txt を空にすると回避できる。
# cd /etc/pki/CA
# cp -p index.txt index.txt.bak
# : >index.txt  <-'cat /dev/null >index.txt'でも何でもとにかくトランケートできればよし
ただし、クライアント認証キーに関するTips で後述する通り、テキストDBをトランケートすると以後の鍵管理に支障を生じる可能性があるため注意が必要だ。

Apache設定ファイルへの反映

CAのパブリックキー /etc/pki/CA/cacert.pem を Webサーバの /etc/pki/CA/ へコピーしておく。

/etc/httpd/conf.d/ssl.conf の修正箇所は以下。

SSLCACertificateFile /etc/pki/CA/cacert.pem
 
<LocationMatch "^/(?!(manual|server-info|server-status|balancer-manager))">
  SSLVerifyClient require
  SSLVerifyDepth  10
</LocationMatch>

実際的にはクライアント認証なしでアクセスしたいパスやロケーションがあると思うので、LocationMatch で特定のURIを除外する否定の前方参照正規表現の例を書いてみた。

Clientブラウザへのキー配布

クライアントPCに、cacert.crt (CAパブリックキーのDERフォーマット版) と client1.p12 をコピーする。cacert.crt をブラウザの「認証局証明書」(IEだと「信頼されたルート証明機関」) ストアにインポートする。そして、client1.p12 を (FireFox系)「あなたの証明書」ストア、(IE)「個人」ストアにインポートする。インポートの際に入力するのは exportpw に書いたパスワードだ。

クライアントキーの失効と削除

管理を少しでもラクにするよう、CAスクリプトには、発行済みキーセットのリヴォークと削除をするルーティンも取り入れた。初めてのリヴォークを実行する前には、まず、CRLナンバーをカウントアップするためのシリアルファイルを作っておかなければいけない。失効シリアル番号は発行キーのシリアルと桁数を合わせた方がいいと書いているサイトもあるので、気になる人は同ディレクトリにある serial ファイルをコピーしてもいいかもしれない。

# echo 01 >/etc/pki/CA/crlnumber

そうしておいてから、

# ./CA -revoke client1
Using configuration from /etc/pki/tls/openssl.cnf
Enter pass phrase for /etc/pki/CA/private/cakey.pem: <入力>
Revoking Certificate XXXXXXXXXXXXXX.
Data Base Updated
Using configuration from /etc/pki/tls/openssl.cnf
Enter pass phrase for /etc/pki/CA/private/cakey.pem: <再度入力>
rm: remove 通常ファイル `/etc/pki/webclient/certs/client1.pem'? y
rm: remove 通常ファイル `/etc/pki/webclient/private/client1.key'? y
rm: remove 通常ファイル `/etc/pki/webclient/private/client1.p12'? y
CLIENTID client1 successfully revoked

CA秘密鍵のパスフレーズを2回訊かれるのは、失効キーリストファイル(CRL)の更新もしているから。リヴォーク自体とリストの更新は一息に

openssl ca -revoke /PATH/TO/client1.pem -gencrl -out ${CATOP}/crl/crl.pem

という風にできると述べているサイトもあるのだが、そうすると、失効リストへは次回からしか反映されない。そのため仕方なく、ca-revoke と `-gencrl -out' の2回に分けて呼んでいるので計2回出るわけだ。バージョンによる違いだろうか(検証環境のopensslパッケージは 1.0.1e-42)。

失効をApacheに伝える仕組み作り

この項はオプションであって、やらなくてもクライアント認証が成り立たないわけではない。しかし、PKCS#12 キーは一応インポート用パスワードで保護されているとはいえ、ユーザを通じて悪意を持って再配布されたり、遠隔モニタウィルスによってキーとパスワードがセットで流出しないとも限らないので、「破棄してくれ」と言って従ってくれる場合ばかりとは限らないのだ。CRLをApacheで活用すれば、失効したキーをインストールしたブラウザはサイトを表示することができなくなる。OpenSSLのリヴォーク情報は勝手にApacheに伝わってはくれない。そのための設定が必要だ。序盤にも述べたように、安全性に鑑みて、CAマシンとWebサーバは別立てだとして話を進める。

関係するディレクトリおよびファイル。
CAマシン側:

/etc/pki/
        \_ CA/crl/
                 crl.pem   <-CAスクリプトでの`ca -gencrl -out'で作られる失効情報ファイル
        \_ webclient/
                 Makefile  <-crl.pemファイルをWebサーバへ送るための仕掛け (root:root 600)

Makefileの内容:

crldir       = /etc/pki/CA/crl
httpserver   = centos6u
httpcrldir   = /etc/httpd/conf/crl
syncuser     = root
SSH         := /usr/bin/ssh -l $(syncuser) -i /root/.ssh/id_rsa
 
.PHONY: sync
 
sync: $(crldir)/crl.pem
	/usr/bin/rsync -c --rsh="$(SSH)" $< \
	$(syncuser)@$(httpserver):$(httpcrldir)/ && \
	$(SSH) $(httpserver) \
	"cd $(httpcrldir) && make"

Webサーバ側:

/etc/httpd/conf/
             \_ crl/      <-root:rootで作っておく
                  crl.pem     <-CAマシンから同期転送される
                  xxxxxxxx.r0 <-crl.pemへのシンボリックリンク
                  Makefile    <-リンクの更新とApacheのリロードをするための仕掛け (root:root 600)
                  tstamp.t0   <-makeの都合で使うダミーファイル

Makefileの内容:

crl         = crl.pem
linkname    = $(shell openssl crl -hash -noout -in $(crl)).r0
tstamp      = tstamp.t0
 
$(tstamp): $(crl)
	find . -type l -name *.r0 -exec rm -f '{}' ';'
	ln -s $(crl) $(linkname)
	/sbin/service httpd graceful
	-@touch $(tstamp)

早い話が、

  1. CAサーバ側の crl.pem を、sshを介したrsyncでWebサーバ側へ同期。
  2. Webサーバ側では、crl.pem が更新されていたら、キー発行者名(issuer)のハッシュ値をファイル名とした、crl.pemへのシンボリックリンクを張り直し、
  3. Apacheを `apachectl graceful' でリロード。

というカラクリだ。見ての通り、Webサーバ側での`make'はCA側での`make'の中からキックされるので、特に事情のない限りWebサーバで make を直接コールする機会はない。CAでの make の際に、何の仕掛けもしないとrootのパスワードを何度も聞かれてしまい自動化できないので、CAマシン側のrootユーザからWebサーバ側rootへのssh非対称鍵を配備しておく必要がある (sshdのページを参照)。

そして肝心のApacheの設定。ssl.conf に下記の1項目を加える。前述の SSLVerifyClient とは異なり <Directory><Location> ブロック等には入れられないことに注意。

SSLCARevocationPath  conf/crl

これで準備はOK。`./CA -revoke client1' といった具合にキーを失効をさせたら、続いてCAマシン上で、

# cd /etc/pki/webclient
# make

これにて反映完了。client1用キーを持ったブラウザからはサイトにアクセスできなくなったはずだ。さらに楽チンにするなら、前述のカスタムCAスクリプト差分の末尾付近にある "cd ${CERTTOP} && make" のコメントを外してもらえば、make は `CA -revoke ...`の終わりに勝手にトリガーされる。

クライアント認証キーに関するTipsいろいろ

webclientディレクトリから削除してしまったキーのリヴォーク

/etc/pki/webclient 配下からキーセットを消してしまっても、CAの newcerts ディレクトリに公開鍵のコピーが <SERIAL>.pem のファイル名で蓄積されている。キーの Common Name(CN) とシリアルとの対応は index.txt に載っているので、

# grep client1 /etc/pki/CA/index.txt
V 180319175891Z   AB25B339750E086 unknown /C=JP/ST=Aichi/O=Hoge Net/OU=Information Tech/CN=client1

といった具合に検索できる。 ちなみに、第1フィールドは V=valid/R=revoked, 第2は有効期限のYYMMDDHHMMSSZ, 第3(上記では失効していないので空)が失効日時, 第4がシリアル, 第5はキーファイル名または"unknown", 最後の第6は言うまでもなくDN。よって、

# openssl ca -revoke /etc/pki/CA/newcerts/AB25B339750E086.pem
# openssl ca -gencrl -out /etc/pki/CA/crl/crl.pem

とやればリヴォークできる。CA/newcerts にさえ既にない場合でも、*.p12 ファイルさえ手に入れば、そこから公開鍵を取り出して失効させることは可能だ。-nodes オプションは「取り出す時に公開鍵を暗号化しなくてよい」の意:

# openssl pkcs12 -in clientX.p12 -clcerts -nodes -out clientX.pem
Enter Import Password: <Exportパスワードを入力>
MAC verified OK
# ls *.pem
clientX.pem <-あとはこれを入力にして失効させればよい
CAのキー管理情報の完全クリーンナップ

度重なるテストで失効済みキーだらけになったCAを掃除する手順。配布済みのクライアントキーを全部回収してからやらなくてはいけない。配布済みキーは二度とリヴォークできなくなるので、実運用中のCAでは基本的に御法度。キーとCRLのシリアルカウンターは放っておくの得策。

# cd /etc/pki/CA
# cat /dev/null >index.txt <-テキストデータベースをトランケート
# rm -f newcerts/*.pem     <-今までに発行したクライアント公開鍵のコピーを全削除
# rm crl/crl.pem           <-失効キーリストの削除。次回revoke操作時にまた新たに作られる