ルート証明書・中間証明書・サーバー証明書の違いと自己署名証明書の作成・確認方法
証明書チェーンの仕組みを説明し、OpenSSL による自己署名証明書の作成、有効期限の確認、中間証明書が不足した場合の調査方法を紹介します。
ブラウザーが HTTPS の Web サイトに接続すると、サーバーは証明書を提示し、ブラウザーは接続先の正当性を確認します。この検証には通常、ルート証明書、中間証明書、サーバー証明書が関わります。この記事では、それぞれの役割を説明したうえで、OpenSSL を使った自己署名証明書の作成、証明書の確認、証明書チェーンの問題の調査方法を紹介します。
ルート証明書・中間証明書・サーバー証明書
一般的な証明書チェーンは次のとおりです。
1
2
3
ルート証明書(ルート CA)
└─ 中間証明書(中間 CA)
└─ サーバー証明書(リーフ証明書)
ルート証明書は信頼の起点です。通常は OS やブラウザーの信頼済み証明書ストアに登録されています。ルート証明書は一般に自己署名されていますが、自己署名されているだけで信頼されるわけではありません。クライアントがそのルート証明書を信頼している必要があります。
中間証明書は、ルート CA または別の中間 CA によって発行されます。中間 CA はサーバー証明書を発行でき、サーバー証明書を信頼済みのルートにつなぐ役割を果たします。
サーバー証明書は、エンドエンティティ証明書やリーフ証明書とも呼ばれ、サーバーが実際に使用する証明書です。対象のドメイン名、公開鍵、有効期間などが含まれます。対応する秘密鍵はサーバーが保持し、クライアントには送信しません。
検証時、クライアントはサーバー証明書から信頼済みのルートまで証明書チェーンを構築し、署名、有効期間、用途、ホスト名などを確認します。詳しくは OpenSSL の証明書検証に関するドキュメントを参照してください。
OpenSSL で自己署名証明書を作成する
次のコマンドは、ローカルテスト用に有効期間 365 日の自己署名サーバー証明書を作成します。事前に OpenSSL をインストールしてください。
1
2
3
4
5
6
7
openssl req -x509 -newkey rsa:2048 -sha256 -noenc \
-keyout localhost.key \
-out localhost.crt \
-days 365 \
-subj "/CN=localhost" \
-addext "subjectAltName=DNS:localhost" \
-addext "basicConstraints=critical,CA:FALSE"
実行すると、localhost.key(秘密鍵)と localhost.crt(証明書)が作成されます。-x509 は自己署名証明書を直接作成し、-days 365 は有効期間を設定します。subjectAltName は証明書の対象となる DNS 名です。別のホスト名で試す場合は subjectAltName と例の CN を変更してください。-noenc により秘密鍵は暗号化されません。ローカルテスト用の鍵でも適切に保管し、公開リポジトリにはコミットしないでください。各オプションは OpenSSL の req ドキュメントを参照してください。
この証明書はブラウザーが既に信頼しているルート証明書につながらないため、通常は信頼に関する警告が表示されます。有効期間内であることと、信頼されていることは別です。
証明書の情報と有効期限を確認する
サブジェクト、発行者、有効期間を表示します。
1
openssl x509 -in localhost.crt -noout -subject -issuer -dates
notBefore は有効期間の開始日時、notAfter は有効期限です。対象ドメイン、用途、その他の項目を詳しく確認するには、次を実行します。
1
openssl x509 -in localhost.crt -noout -text
今後 30 日以内に証明書の有効期限が切れるかを確認します。
1
openssl x509 -in localhost.crt -noout -checkend 2592000
2592000 は 30 日を秒で表した値です。終了ステータスが 0 なら 30 日後も有効で、0 以外ならそれまでに期限が切れます。これは 1 枚の証明書の有効期限を調べるもので、証明書チェーン全体の検証ではありません。詳しくは OpenSSL の x509 ドキュメントを参照してください。
証明書チェーンと稼働中のサーバーを確認する
サーバー証明書 server.crt、中間証明書 intermediate.crt、信頼するルート証明書 root.crt がある場合、次のコマンドで証明書チェーンとホスト名を検証できます。
1
2
3
4
openssl verify -CAfile root.crt \
-untrusted intermediate.crt \
-verify_hostname example.com \
server.crt
成功すると server.crt: OK と表示されます。-CAfile には信頼するルート証明書を指定し、-untrusted にはチェーンの構築に使う中間証明書を指定します。-untrusted で指定した中間証明書が、それだけで信頼の起点になるわけではありません。詳しくは OpenSSL の証明書検証オプションを参照してください。
稼働中のサーバーが実際に送信する証明書は、次のコマンドで確認できます。
1
2
3
4
5
openssl s_client -connect example.com:443 \
-servername example.com \
-showcerts \
-verify_hostname example.com \
-verify_return_error
-showcerts はサーバーから送信された証明書の一覧を表示しますが、一覧が表示されたこと自体は検証成功を意味しません。-servername は SNI で接続先のホスト名を指定します。同じ IP アドレスで複数の HTTPS サイトを公開している場合に重要です。詳しくは OpenSSL の s_client ドキュメントを参照してください。
よくある質問
自己署名証明書とルート証明書は同じものですか?
いいえ。ルート証明書は通常、自己署名されていますが、自分で署名した証明書がクライアントから自動的に信頼されるわけではありません。
Web サイトはルート証明書も送信する必要がありますか?
通常は不要です。クライアントは自身の信頼済み証明書ストアからルート証明書を取得します。サーバーはサーバー証明書と、チェーンの構築に必要な中間証明書を送信します。
中間証明書が不足するとどうなりますか?
API サーバーがサーバー証明書だけを送信し、必要な中間証明書を送信しない場合、クライアントは信頼済みのルートまで証明書チェーンを構築できないことがあります。この場合、HTTP リクエストを送信する前の TLS ハンドシェイクで証明書の検証が失敗し、unable to get local issuer certificate や certificate verify failed などのエラーが発生します。一見すると API 呼び出しの失敗ですが、原因は API の処理ではなく HTTPS の証明書チェーンにある可能性があります。
中間証明書をキャッシュしている、または別の方法で取得できるクライアントもあります。そのため、同じ API がブラウザーでは動作しても、アプリケーションやコンテナーなどからは失敗することがあります。API サーバーが必要な中間証明書を送信するように設定し、openssl s_client -showcerts で実際の送信内容を確認してください。クライアント側の証明書検証を無効にする方法は、適切な修正ではありません。
この問題は、証明書の更新時に新しいサーバー証明書だけをインポートし、中間証明書を含め忘れた場合などに起こります。
証明書の有効期限が切れていないのに、ブラウザーが警告を表示するのはなぜですか?
有効期限は検証項目の一つです。ホスト名の不一致、不完全な証明書チェーン、信頼されていないルート証明書、証明書の有効期間がまだ始まっていないことなども、検証失敗の原因になります。
