サーバー証明書が、信頼するルートCAまで正しくたどれるかを確認したいときは、openssl verifyを使う。
$ openssl verify -CAfile root.crt -untrusted inter.crt leaf.crt
leaf.crt: OK
それぞれのオプションの意味は次のとおり。
| オプション | 意味 |
|---|---|
-CAfile | 信頼するルートCAの証明書 |
-untrusted | チェーンの途中に使う証明書(中間CAの証明書) |
| 最後の引数 | 検証するサーバー証明書 |
証明書は、サーバー証明書(leaf.crt)、中間CA(inter.crt)、ルートCA(root.crt)の3階層とする。実行例はUbuntu 24.04のOpenSSL 3.0.13のもの。
検証に使う証明書を用意する
実行例で使う3階層の証明書は、次のコマンドで作った。
# ルートCA
$ openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:P-256 -noenc \
-keyout root.key -out root.crt -days 3650 -subj "/CN=Test Root CA" \
-addext "basicConstraints=critical,CA:TRUE" \
-addext "keyUsage=critical,keyCertSign,cRLSign"
# 中間CA
$ openssl req -new -newkey ec -pkeyopt ec_paramgen_curve:P-256 -noenc \
-keyout inter.key -out inter.csr -subj "/CN=Test Intermediate CA"
$ printf 'basicConstraints=critical,CA:TRUE,pathlen:0\nkeyUsage=critical,keyCertSign,cRLSign\n' > inter.ext
$ openssl x509 -req -in inter.csr -CA root.crt -CAkey root.key -CAcreateserial \
-out inter.crt -days 1825 -extfile inter.ext
# サーバー証明書
$ openssl req -new -newkey ec -pkeyopt ec_paramgen_curve:P-256 -noenc \
-keyout leaf.key -out leaf.csr -subj "/CN=leaf.example.test"
$ printf 'subjectAltName=DNS:leaf.example.test\nbasicConstraints=CA:FALSE\nkeyUsage=digitalSignature\nextendedKeyUsage=serverAuth\n' > leaf.ext
$ openssl x509 -req -in leaf.csr -CA inter.crt -CAkey inter.key -CAcreateserial \
-out leaf.crt -days 365 -extfile leaf.ext
参考: 【openssl】自分用の認証局で証明書に署名する (x509 -req)
チェーンをたどれるか確認する
-show_chainを付けると、検証で使った証明書の階層を表示する。
$ openssl verify -show_chain -CAfile root.crt -untrusted inter.crt leaf.crt
leaf.crt: OK
Chain:
depth=0: CN = leaf.example.test (untrusted)
depth=1: CN = Test Intermediate CA (untrusted)
depth=2: CN = Test Root CA
depth=0が検証対象のサーバー証明書で、数字が大きいほど上位のCAである。(untrusted)は-untrustedで渡した証明書を表す。信頼の起点は-CAfileで渡したdepth=2だけである。
検証に失敗した場合は、終了コードが2になる。スクリプトで成否を判定できる。
よくあるエラー
unable to get local issuer certificate
中間CAの証明書を渡さないと、サーバー証明書の発行者が見つからず失敗する。error 20 at 0 depth lookupの0は、サーバー証明書で失敗したことを表す。
$ openssl verify -CAfile root.crt leaf.crt
CN = leaf.example.test
error 20 at 0 depth lookup: unable to get local issuer certificate
error leaf.crt: verification failed
-untrustedで中間CAの証明書を渡すと解決する。サーバーが中間CAの証明書を送らない構成では、ブラウザーでは接続できても、curlなどで失敗する場合がある。
同じエラーは、ルートCAの証明書を渡さない場合にも出る。depthの値が違う点で区別できる。
$ openssl verify -untrusted inter.crt leaf.crt
CN = Test Intermediate CA
error 20 at 1 depth lookup: unable to get local issuer certificate
error leaf.crt: verification failed
1 depthは、中間CAの発行者(ルートCA)が見つからないことを表す。
self-signed certificate
自己署名の証明書を、信頼するCAとして渡さないと失敗する。
$ openssl verify ss.crt
CN = ss
error 18 at 0 depth lookup: self-signed certificate
error ss.crt: verification failed
自己署名の証明書自身を-CAfileに渡すと成功する。
$ openssl verify -CAfile ss.crt ss.crt
ss.crt: OK
certificate has expired
有効期限が切れている証明書は、エラーになる。次の例は、2年後の時刻を仮定して実行した結果である。
$ openssl verify -CAfile root.crt -untrusted inter.crt leaf.crt
CN = leaf.example.test
error 10 at 0 depth lookup: certificate has expired
error leaf.crt: verification failed
-attimeに、UNIX時間(秒)で検証時刻を指定すると、期限切れの確認や、将来の期限切れを事前に検査できる。
$ openssl verify -attime $(date -d '+2 years' +%s) -CAfile root.crt -untrusted inter.crt leaf.crt
CN = leaf.example.test
error 10 at 0 depth lookup: certificate has expired
error leaf.crt: verification failed
date -dはGNU版のコマンドである。macOSではdate -v+2y +%sを使う。
有効期間の開始前の証明書は、certificate is not yet validになる。チェーンのどの階層で失敗したかは、at N depthで分かる。
参考: 【openssl】証明書ファイルの有効期限を確認する
unsuitable certificate purpose
-purposeで用途を指定して検証できる。サーバー証明書がserverAuthだけを許可している場合、クライアント証明書としての検証は失敗する。
$ openssl verify -CAfile root.crt -untrusted inter.crt -purpose sslclient leaf.crt
error 26 at 0 depth lookup: unsuitable certificate purpose
error leaf.crt: verification failed
サーバー証明書として検証するには-purpose sslserverを指定する。
$ openssl verify -CAfile root.crt -untrusted inter.crt -purpose sslserver leaf.crt
leaf.crt: OK
hostname mismatch
-verify_hostnameでホスト名を検証できる。SANに含まれていないホスト名は失敗する。
$ openssl verify -CAfile root.crt -untrusted inter.crt -verify_hostname leaf.example.test leaf.crt
leaf.crt: OK
$ openssl verify -CAfile root.crt -untrusted inter.crt -verify_hostname other.test leaf.crt
error 62 at 0 depth lookup: hostname mismatch
error leaf.crt: verification failed
path length constraint exceeded
中間CAのbasicConstraintsにpathlen:0があると、その中間CAの配下にさらにCAを作れない。下位のCAを作った場合は、検証に失敗する。
CN = Test Intermediate CA
error 25 at 2 depth lookup: path length constraint exceeded
error l2.crt: verification failed
中間CAの渡し方を選ぶ
中間CAの証明書は、-untrusted以外に、-CAfileへまとめて渡す方法もある。
$ cat root.crt inter.crt > bundle.pem
$ openssl verify -CAfile bundle.pem leaf.crt
leaf.crt: OK
この方法では、中間CAも信頼の起点として扱われる。検証の意図が変わるため、チェーンの構成を確認したい場合は-untrustedを使う。
サーバーから受け取った証明書が1つのファイルにまとまっている場合は、そのファイルを-untrustedに渡せる。
$ openssl verify -CAfile root.crt -untrusted leafchain.pem leaf.crt
leaf.crt: OK
中間CAを信頼の起点にする
-partial_chainを付けると、ルートCAではなく中間CAの証明書を信頼の起点にして検証できる。
$ openssl verify -CAfile inter.crt leaf.crt
CN = Test Intermediate CA
error 2 at 1 depth lookup: unable to get issuer certificate
error leaf.crt: verification failed
$ openssl verify -partial_chain -CAfile inter.crt leaf.crt
leaf.crt: OK
システムの信頼済みCAで検証する
-CAfileを省略すると、OSの信頼済みCAを使う。自分用のCAが含まれていない場合は、unable to get local issuer certificateになる。
$ openssl verify leaf.crt
error 20 at 0 depth lookup: unable to get local issuer certificate
error leaf.crt: verification failed
公開サーバーの証明書を検証する場合は、openssl s_clientで取得した証明書を渡す。
参考: 【openssl】指定したURLの証明書をコマンドで確認する
バージョンによる違い
OpenSSL 3.6でも、ここまでの例は同じように動く。
macOS標準の/usr/bin/openssl(LibreSSL 3.3.6)では、以下が異なる。
-show_chain、-partial_chain、-verify_hostnameに対応していない- エラー表示が
error 20 at 0 depth lookup:unable to get local issuer certificateのように、コロンの後に空白がない - 最後に
leaf.crt: verification failed: 20 (unable to get local issuer certificate)の行が出る - 用途のエラーは
unsupported certificate purposeになる
ホスト名の検証やチェーンの確認が必要な場合は、Homebrewなどでインストールしたopensslを使う。
