前回までの「今度こそopensslコマンドを理解して使いたい」:
前回までで、プライベートCAとサーバー/クライアント証明書の作成を自動化することができました。
今回は証明書の失効とCRL(証明書失効リスト)の発行をスクリプトで処理します。
また実際にOpenVPNサーバーにCRLを配布して、失効したクライアント証明書が認証されなくなることを確認します。
OpenVPNの試験環境を作る
その前に、OpenVPNの動作に必要なファイルの作成と設定を行います。
環境とバージョン
サーバー用OS: Amazon Linux 2
クライアント用OS: CentOS 7.6
CA用OS: CentOS 7.6
CentOS 7.6を使用するので、OpenSSLのバージョンは1.0.2を使用します。
参考: OpenSSL Manpages for 1.0.2: https://www.openssl.org/docs/man1.0.2/
OpenVPNは、CentOS 7.6とAmazon Linux 2にyumでインストール可能な2.4.7を使用します(2019年8月現在)。
参考: OpenVPN コミュニティ Wiki: https://community.openvpn.net/openvpn
OpenVPN用の追加ファイルを作成する
これまでに作成した認証関係のファイルと、OpenVPNサーバー/クライアントに配置するファイルの関係は以下のとおりです。
ファイル | サーバー | クライアント | 作成回 |
---|---|---|---|
CA秘密鍵 | 第1回、第3回 | ||
CA証明書 | X | X | 第1回、第3回 |
サーバー秘密鍵 | X | 第4回 | |
サーバー証明書 | X | 第4回 | |
クライアント秘密鍵 | X | 第4回 | |
クライアント証明書 | X | 第4回 | |
DHパラメータ | X | 今回 | |
TLS認証鍵 | X | X | 今回 |
サーバーに5つ、クライアントに4つのファイルを配置します。まだ作成していないファイルは、DHパラメータとTLS認証鍵の2つです。
DHパラメータ
セッションの共通鍵を安全に生成するための Diffie Hellman パラメータを、openssl
のdhparam
コマンドを使って作成します。
-out
で任意のファイル名を指定して、最後に鍵長をビット数で指定します。2048はデフォルト値なので、省略しても同じ結果になります。
openssl dhparam -out ファイル名 2048
参考: dhparam
コマンド manpage (1.0.2): https://www.openssl.org/docs/man1.0.2/man1/openssl-dhparam.html
TLS認証鍵
DoS攻撃への対策として認証の前段階で不正なパケットを破棄するための静的な共有鍵です。
これだけはopenssl
ではなくopenvpn
コマンドで作成します。以下のコマンドで、任意のファイル名を指定して作成します。
openvpn --genkey --secret ファイル名
参考: OpenVPN.JP How To:「OpenVPNのセキュリティを強化する」: https://www.openvpn.jp/document/how-to/#Security
OpenVPNの設定ファイル
以上のファイルをOpenVPNサーバー/クライアントの任意の場所に配置して、設定ファイルでそれぞれのパスを指定します。
サーバー
配置した5つのファイルのパスを以下のように指定します。tls-auth
行の2番目のパラメータは、サーバーでは0
を指定します。
ca CA証明書のパス
cert サーバー証明書のパス
key サーバー秘密鍵のパス
dh DHパラメータのパス
tls-auth TLS認証鍵のパス 0
設定ファイルのパスは/etc/openvpn/任意の名前.conf
です。
サービスのユニット名は設定ファイル名から動的に生成されて「openvpn@任意の名前.service
」になります。
クライアント
配置した4つのファイルのパスを以下のように指定します。CA証明書とTLS認証鍵は、サーバーに配置したファイルと同じです。tls-auth
行の2番目のパラメータは、クライアントでは1
を指定します。
ca CA証明書のパス
cert クライアント証明書のパス
key クライアント秘密鍵のパス
tls-auth TLS認証鍵のパス 1
サーバーと同様に、設定ファイルのパスが/etc/openvpn/任意の名前.conf
なら、サービスのユニット名は「openvpn@任意の名前.service
」になります。
証明書の失効処理
証明書の失効処理は、各証明書を発行した(署名した)CAで行います。
前回、以下のように CA「ca1.mydomain」で「vpnserver_1」と「vpnclient_1」の証明書を発行して署名しました。
├── ca1.mydomain/
│ ├── certs/
│ │ ├── vpnclient_1.crt.pem # 「vpnclient_1」のクライアント証明書
│ │ ├── vpnclient_1.key.pem # 「vpnclient_1」の秘密鍵
│ │ ├── vpnserver_1.crt.pem # 「vpnserver_1」のサーバー証明書
│ │ └── vpnserver_1.key.pem # 「vpnserver_1」の秘密鍵
「ca1.mydomain」のデータベースインデックス(index.txt)には、これら2つの証明書の情報が正しく記録されています。
cat ca1.mydomain/index.txt
V 220821110732Z 1000 unknown /C=JP/ST=Chiba/O=myorg/CN=vpnserver_1
V 200821110732Z 1001 unknown /C=JP/ST=Chiba/O=myorg/CN=vpnclient_1
今回はこの中の「vpnclient_1」のクライアント証明書を失効します。
失効対象リスト
前回サーバー/クライアントの証明書を一括生成した時と同じように、複数の失効対象を指定できるようにします。サーバー/クライアントは混在可能です。また前回と同様に、処理済みの対象はスキップするようにします。
1列目: 失効対象のサーバー/クライアントのコモンネーム
2列目: 証明書に署名したCAのコモンネーム
vpnclient_1 ca1.mydomain
設定ファイル(openssl.cnf)
OpenSSLの設定ファイルは前回と同じopenssl.cnf
を使用し、CAのディレクトリをスクリプトから指定できるようにします。
参考: 第2回 「環境変数」
CA_DIR = /etc/pki/CA # 全体の保存場所(環境変数がない場合のデフォルト値)
[ CA_default ]
dir = $ENV::CA_DIR # 全体の保存場所(環境変数)
# 以下変更なし
certs = $dir/certs # Where the issued certs are kept
crl_dir = $dir/crl # Where the issued crl are kept
database = $dir/index.txt # database index file.
CA秘密鍵のパスフレーズ
CRLに署名するために、CAの秘密鍵のパスフレーズを指定する必要があります。CAを作成した時のファイル.capass
をそのまま使用して、先頭行で定義されたパスフレーズを入力します。
mycap@ss
証明書一括失効用スクリプト
以上の仕様でスクリプトを作成します。エラー処理などは省略しています。
#!/bin/bash
CATOP="$(dirname "${0}")"
# 失効対象のコモンネーム、署名するCA名を失効対象リストから1件ずつ読み込む
while read common_name ca_name
do
[[ ${common_name} != "" && ${ca_name} != "" ]] || continue
# CAのディレクトリ
ca_dir="${CATOP}/${ca_name}"
# データーベースインデックスのレコードを見て、現在のターゲットが失効済み
# ならスキップする。
grep "^R.*/CN=${common_name//./\\.}$" "${ca_dir}/index.txt" && continue
# CAのディレクトリを環境変数に出力する。
export CA_DIR="${ca_dir}"
# caコマンドで証明書を失効する。
# -revoke 失効対象の証明書ファイルのパス
# -passin CAの秘密鍵のパスフレーズ入力
openssl ca \
-revoke "${ca_dir}/certs/${common_name}.crt.pem" \
-passin "file:${CATOP}/.capass"
done <"${CATOP}/revoke-list"
参考: ca
コマンド manpage (1.0.2): https://www.openssl.org/docs/man1.0.2/man1/openssl-ca.html
正常に終了すると以下のように表示されます。「1001」は失効した証明書のシリアル番号です。
Using configuration from /etc/pki/tls/openssl.cnf
Revoking Certificate 1001.
Data Base Updated
実行後、データベースインデックスは以下のように更新されました。
cat ${CA_DIR}/index.txt
V 220821110732Z 1000 unknown /C=JP/ST=Chiba/O=myorg/CN=vpnserver_1
R 200821110732Z 190831162004Z 1001 unknown /C=JP/ST=Chiba/O=myorg/CN=vpnclient_1
以下は実行前のデータベースインデックスです。実行後はシリアル番号1001の行の先頭が「V」から「R」(失効)になり、2列目に失効日時が追加されたことが分かります。
V 220821110732Z 1000 unknown /C=JP/ST=Chiba/O=myorg/CN=vpnserver_1
V 200821110732Z 1001 unknown /C=JP/ST=Chiba/O=myorg/CN=vpnclient_1
以上で失効対象リストで指定した証明書の失効が、CAのデータベースインデックスに正しく記録されたことが確認できました。
CRL(証明書失効リスト)の作成
各CAのデータベースインデックス(index.txt)の情報を元に、新しいCRLを作成します。
対象CAリスト
第3回でCAを作成した時に使用したファイルca-list
を使用します。リスト内のすべてのCAでCRLを作成します。「ca2.mydomain」ではまだ1つも失効処理をしていないので、「失効数0件のCRL」が作成されます。
ca1.mydomain
ca2.mydomain
CAのサブディレクトリとファイル
スクリプトを実行すると、各CAのディレクトリに以下のサブディレクトリとファイルが、デフォルトの名前で自動的に生成されます。
.
├── ca1.mydomain/
│ ├── certs/
│ ├── crl/ # CRLの出力用ディレクトリ
│ ├── newcerts/
│ ├── private/
│ │ └── cakey.pem
│ ├── cacert.pem
│ ├── crlnumber # 次回のCRL番号ファイル。初期値が必要だが、インクリメントは自動
│ ├── index.txt
│ └── serial
CRL一括生成用スクリプト
以上の仕様でスクリプトを作成します。
#!/bin/bash
CATOP="$(dirname "${0}")"
# CRLを作成するCAのコモンネームをCAリストから1件ずつ読み込む
while read ca_name
do
[[ ${ca_name} != "" ]] || continue
# CAのディレクトリ
ca_dir="${CATOP}/${ca_name}"
# CRLのサブディレクトリとファイルがなければ作成する。
# - crlnumberにはCRL番号の初期値を出力する。
mkdir -p "${ca_dir}/crl"
[[ -e "${ca_dir}/crlnumber" ]] || echo "00" >"${ca_dir}/crlnumber"
# CAのディレクトリを環境変数に出力する。
export CA_DIR="${ca_dir}"
# caコマンドでCRLを生成する。
# -gencrl CRLを生成する
# -crldays 次回CRL更新までの日数
# -out 出力するCRLファイル
# -passin CAの秘密鍵のパスフレーズ入力
openssl ca \
-gencrl \
-crldays 365 \
-out "${ca_dir}/crl/crl.pem" \
-passin "file:${CATOP}/.capass"
done <"${CATOP}/ca-list"
注意: -crldays
で次回CRL更新までの日数を指定します。この期限を過ぎてCRLが更新されないとすべての証明書が認証されなくなってしまうので、CRLの運用が確立していない場合は多めの日数を指定します。このオプションを指定しない場合は、設定ファイルのdefault_crl_days
の値が使用されます。
参考: ca
コマンド manpage (1.0.2): https://www.openssl.org/docs/man1.0.2/man1/openssl-ca.html
コマンドが正常に修了すると、以下のようにファイルが生成/更新されます。
.
├── ca1.mydomain
│ ├── certs
│ │ ├── vpnclient_1.crt.pem
│ │ ├── vpnclient_1.key.pem
│ │ ├── vpnserver_1.crt.pem
│ │ └── vpnserver_1.key.pem
│ ├── crl
│ │ └── crl.pem # ca1.mydomainのCRL
│ ├── newcerts
│ ├── private
│ │ └── cakey.pem
│ ├── cacert.pem
│ ├── crlnumber # CRL番号ファイル
│ ├── crlnumber.old
│ ├── index.txt
│ ├── index.txt.attr
│ ├── index.txt.attr.old
│ ├── index.txt.old
│ ├── serial
│ └── serial.old
├── ca2.mydomain
│ ├── certs
│ │ ├── vpnserver_2.crt.pem
│ │ └── vpnserver_2.key.pem
│ ├── crl
│ │ └── crl.pem # ca2.mydomainのCRL
│ ├── newcerts
│ ├── private
│ │ └── cakey.pem
│ ├── cacert.pem
│ ├── crlnumber # CRL番号ファイル
│ ├── crlnumber.old
│ ├── index.txt
│ ├── index.txt.attr
│ ├── index.txt.old
│ ├── serial
│ └── serial.old
├── .capass
├── ca-list
├── make-crt.sh
├── newca.sh
├── revoke-list
├── revoke.sh
├── target-list
└── update-crl.sh
各CAで初めてのCRLを発行したので、crlnumber
の初期値「00」が正しくインクリメントされて、次回のCRL番号「01」になりました。
find . -name crlnumber | xargs grep .
./ca1.mydomain/crlnumber:01
./ca2.mydomain/crlnumber:01
CA「ca1.mydomain」のCRLの内容を、以下のコマンドで確認します。
参考: crl
コマンド manpage (1.0.2): https://www.openssl.org/docs/man1.0.2/man1/openssl-crl.html
openssl crl -in ca1.mydomain/crl/crl.pem -text -noout
Certificate Revocation List (CRL):
Version 2 (0x1)
Signature Algorithm: sha256WithRSAEncryption
Issuer: /C=JP/ST=Chiba/O=myorg/CN=ca1.mydomain
Last Update: Aug 31 16:39:28 2019 GMT
Next Update: Aug 30 16:39:28 2020 GMT
CRL extensions:
X509v3 CRL Number:
0
Revoked Certificates:
Serial Number: 1001
Revocation Date: Aug 31 16:20:04 2019 GMT
Signature Algorithm: sha256WithRSAEncryption
(略)
各項目の値が以下のようになっていることが確認できます。
-
Next Update:
次回CRL更新までの日数を365と指定したので、1年後の日付けになっています。この日までに次のCRLをサーバーに再配布しないと、すべての証明書が認証されなくなります。 -
X509v3 CRL Number:
crlnumberで「00」を指定したので、CRL番号が0になっています。 -
Revoked Certificates:
失効された証明書のシリアル番号「1001」が記載されています。複数の証明書を失効した後でCRLを発行すると、失効したすべての証明書のシリアル番号がここに列挙されます。
もう1つのCA「ca2.mydomain」には失効した証明書がないので、CRLの内容を見ると「No Revoked Certificates.」になっています。
openssl crl -in ca2.mydomain/crl/crl.pem -text -noout
Certificate Revocation List (CRL):
Version 2 (0x1)
Signature Algorithm: sha256WithRSAEncryption
Issuer: /C=JP/ST=Chiba/O=myorg/CN=ca2.mydomain
Last Update: Aug 31 16:39:28 2019 GMT
Next Update: Aug 30 16:39:28 2020 GMT
CRL extensions:
X509v3 CRL Number:
0
No Revoked Certificates.
Signature Algorithm: sha256WithRSAEncryption
(略)
以上で、証明書を1件だけ失効したCA、1件も失効していないCAともに、CRLが正しく作成されたことが確認できました。
OpenVPNサーバーにCRLを配布する
発行したCRLファイルを OpenVPN サーバーの任意の場所に配置して、設定ファイルに以下の行を追加します。
crl-verify CRLのパス
OpenVPN サーバーを再起動してCRLを認識させます。クライアントは接続したままで構いません。
systemctl restart openvpn@任意の名前.service
正常に起動すれば証明書を失効したクライアントだけが再接続されず、その他のクライアントは自動的に再接続されます。
以後も新しいCRLを作成したら、ファイルを上書きして OpenVPN サーバーを再起動します。
あとがき
以上で、今度こそopensslコマンドを理解して使いたいと考えていた作業と調査を終了します。
まだまだほんの一部を知ったに過ぎませんが、簡易ツールやコピペで対応していた時に比べれば、多少は自信を持って証明書の作成と運用ができるようになりました。
記事一覧
今度こそopensslコマンドを理解して使いたい (1) ルートCAをスクリプトで作成する
今度こそopensslコマンドを理解して使いたい (2) 設定ファイル(openssl.cnf)を理解する
今度こそopensslコマンドを理解して使いたい (3) CA証明書の拡張設定を検証する
今度こそopensslコマンドを理解して使いたい (4) サーバー/クライアント証明書を一括生成する
今度こそopensslコマンドを理解して使いたい (5) CRL(証明書失効リスト)を作成してOpenVPNに配布する
今度こそopensslコマンドを理解して使いたい (補足1) サンプルスクリプトのまとめ