SSL/TLS 証明書
概要
SSL/TLS 証明書 は、ウェブサイトとブラウザー間の通信を暗号化し、接続先サーバーの身元を証明するデジタル証明書です。RFC 5280 で定義された X.509 v3 フォーマットに基づいており、Subject(証明書の対象)、Issuer(発行者)、有効期限、公開鍵、拡張フィールドなどの情報を ASN.1 形式で格納します。
正式名称は TLS(Transport Layer Security)証明書ですが、前身の SSL(Secure Sockets Layer)の呼称が業界で使われ続けています。現在の実装はすべて TLS です。SSL 3.0 は 2015 年に RFC 7568 で廃止済みであり、TLS 1.0 と 1.1 も IETF により 2021 年に非推奨化されました。
証明書がないか期限切れの場合、Chrome や Firefox はページをブロックし「安全でない接続」警告を表示します。HTTP サイトに対しても Chrome は 2023 年以降、アドレスバーに警告アイコンを常時表示しています。
仕組み
SSL/TLS 証明書は TLS ハンドシェイクで提示され、クライアントが有効期限・ドメイン一致・証明書チェーン・失効状態を検証します。
TLS ハンドシェイク
HTTPS 接続は TLS ハンドシェイクによって確立されます。TLS 1.3(RFC 8446、2018 年標準化)では次の流れになります。
- クライアントが ClientHello を送る。対応する暗号スイートの一覧と、鍵交換に使う Diffie-Hellman の鍵共有値を含める
- サーバーが ServerHello を返す。暗号スイートを選択し、自身の鍵共有値を送る。このタイミングで双方が共有鍵を導出できるため、以降のメッセージはすべて暗号化される
- サーバーが Certificate、CertificateVerify(秘密鍵でハンドシェイク全体に署名)、Finished を暗号化して送る
- クライアントが証明書を検証し、Finished を送る。これで 1-RTT でハンドシェイクが完了する
TLS 1.2 は同じ処理に 2-RTT 必要でした。TLS 1.3 はハンドシェイクを 1 往復に短縮しただけでなく、TLS 1.2 で任意だった Forward Secrecy(前方秘匿性)を必須にしました。具体的には RSA 鍵交換を廃止し、エフェメラル Diffie-Hellman のみを認めています。サーバーの秘密鍵が後に漏洩しても、各セッションの共有鍵は独立しているため過去の通信は復号できません。
TLS 1.3 ではセッション再開時に 0-RTT モードも利用できます。クライアントが前回のセッションから得た Pre-Shared Key(PSK)を使い、ClientHello と同時に暗号化データを送ります。ただし 0-RTT データはリプレイ攻撃に対する保護がないため、冪等な GET リクエストにのみ使うべきです。
証明書の検証
クライアントは受け取った証明書に対して次を確認します。
- 有効期限が切れていないこと
- Subject の CommonName または SAN(Subject Alternative Name)がアクセス先ドメインと一致すること
- 証明書チェーンをたどった先にブラウザー組み込みのルート CA があること
- 証明書が失効していないこと(CRL または OCSP で確認)
Chrome と Edge は OCSP をリアルタイムに問い合わせず、Google が独自に配布する CRLSets を使って失効確認します。Firefox は OCSP をデフォルト無効化しており、CRLite という独自の圧縮リスト方式に移行しています。
種類
証明書の検証レベルは DV、OV、EV の 3 種類に分類されます。CA/Browser Forum が各レベルの審査基準を定めています。
DV(Domain Validation) は、ドメインの管理権限だけを確認します。DNS レコードへの TXT 追加、または特定 URL へのファイル設置によって自動検証できます。Let’s Encrypt はこの方式で無料かつ即時発行を実現しています。証明書に組織名は含まれません。
OV(Organization Validation) は、ドメイン管理権限に加えて法人の実在を確認します。CA は登記簿や電話番号などで組織を調査します。証明書の Subject に組織名が含まれるため、openssl x509 -text で確認できます。発行に数日かかります。
EV(Extended Validation) は、CA/Browser Forum が定めた厳格な審査基準に基づき、法人の実在・所在地・事業内容まで確認します。かつては Chrome と Firefox がアドレスバーに組織名を緑色で表示していましたが、2019 年以降両ブラウザーとも廃止しました。現在は証明書の詳細を開いたときのみ組織情報が確認できます。
ワイルドカード証明書(*.example.com)は第 1 レベルのサブドメインをすべてカバーしますが、sub.sub.example.com のような 2 階層深いサブドメインはカバーしません。SAN 証明書は複数の異なるドメインを 1 枚の証明書に含められます。
有効期限と更新
2020 年 9 月 1 日以降に発行された証明書は最長 398 日です。Apple が Safari でこの上限を一方的に施行し、Google も同時期に追随しました。さらに 2025 年 4 月、CA/Browser Forum は段階的に有効期間を短縮する方針を可決しました(Apple、Google、Mozilla、Microsoft の 4 ブラウザーベンダーが賛成票を投じています)。具体的には 2026 年 3 月 15 日から最長 200 日、2027 年 3 月 15 日から最長 100 日、2029 年 3 月 15 日から最長 47 日へと段階的に移行します。
自動更新の設定だけで安心してはいけません。certbot や acme.sh が証明書ファイルを更新しても、nginx や Apache がリロードされなければ古い証明書を返し続けます。また、Let’s Encrypt の証明書は 90 日有効で、30 日前から更新可能です。
設定例
Let’s Encrypt での証明書取得から Nginx への配置までの手順を示します。
Let’s Encrypt での証明書取得
Let’s Encrypt で証明書を取得・更新する例です。
# 証明書の取得(webroot 方式)
certbot certonly --webroot -w /var/www/html -d example.com -d www.example.com
# 自動更新のテスト
certbot renew --dry-run
Nginx での設定
# Nginx での設定(fullchain.pem に中間証明書が含まれる)
server {
listen 443 ssl;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
# TLS 1.3 を優先
ssl_protocols TLSv1.2 TLSv1.3;
}
確認方法
openssl でローカルから証明書を確認するには次のコマンドを使います。
# 証明書の詳細を表示(Subject、SAN、有効期限など)
openssl s_client -connect example.com:443 -servername example.com </dev/null | openssl x509 -noout -text
# 有効期限だけを確認
openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null | openssl x509 -noout -dates
有効期限の出力例です。
notBefore=Jan 13 00:00:00 2026 GMT
notAfter=Apr 13 23:59:59 2026 GMT
外部の視点からも確認したい場合は、Labee Dev Toolbox の SSL Cert API を使うと、外部の視点から見た結果を取得できます。
curl "https://labee.dev/api/ssl-cert?hostname=example.com"
{
"success": true,
"data": {
"hostname": "example.com",
"port": 443,
"reachable": true,
"status": 200
},
"error": null,
"meta": { "responseTime": 123 }
}
data.reachable が true であれば、外部の環境から HTTPS 接続が成立しています。false の場合、レスポンスの error フィールドに接続失敗の原因が含まれます。証明書の期限切れ、ポートのファイアウォール、DNS 解決の失敗などが返ります。
ポートを指定するには port パラメーターを追加します。対応ポートは 443、2053、2083、2087、2096、4443、8443 です。
curl "https://labee.dev/api/ssl-cert?hostname=example.com&port=8443"
{
"success": true,
"data": {
"hostname": "example.com",
"port": 8443,
"reachable": true,
"status": 200
},
"error": null,
"meta": { "responseTime": 123 }
}
よくある問題
証明書関連のトラブルは、期限切れ、中間証明書の欠落、TLS バージョンの非互換が大半を占めます。
証明書の期限切れ
Let’s Encrypt の証明書は 90 日有効です。自動更新が動作していても、nginx の reload が実行されていないと古い証明書を返し続けます。certbot renew のフックに systemctl reload nginx を含めるのが確実です。
中間証明書の設定漏れ
Nginx で ssl_certificate に fullchain.pem ではなく cert.pem を指定すると、中間証明書が欠落します。Chrome は AIA fetching でカバーしますが、Android の旧バージョンや Firefox は AIA fetching を行わないため、SSL エラーになります。詳細は「証明書チェーン」の項目を参照してください。
ワイルドカード証明書の範囲誤認
*.example.com は sub.example.com は対象ですが deep.sub.example.com はカバーしません。2 階層以上のサブドメインが必要な場合は SAN 証明書を使います。
TLS バージョンの非互換
TLS 1.0、1.1 は 2021 年に非推奨化されています。PCI DSS 4.0 では TLS 1.2 以上が必須です。古いミドルウェアやモバイルアプリが TLS 1.2 非対応の場合、HTTPS 接続に失敗します。ssl_protocols TLSv1.2 TLSv1.3; で明示的に指定します。
自己署名証明書
開発環境でよく使われますが、ブラウザーは信頼できる CA の署名がないため警告を表示します。開発環境でも mkcert などで OS のトラストストアに登録した証明書を使うと、より本番に近い動作を確認できます。