【証明書入門 第10回】証明書トラブルシュート ― エラーの見え方と切り分けの手順、連載のまとめ

証明書・PKI
スポンサーリンク
スポンサーリンク

「証明書エラーが出るので、とりあえず『詳細設定』から進んでもらっています」
「curlでエラーになるので、-kを付けて回避しました」

どちらも、その場はしのげます。でも、回避した端末だけが中間者攻撃に無防備になるうえに、原因は何も解決していません。

第9回までで、証明書とTLSの仕組み、発行と自動化、社内PKIとクラウドを見てきました。第10回では、その知識を使って、証明書のエラーが起きたときに原因を切り分ける手順をまとめます。最後に、連載全体の早見表と持ち帰りも載せます。


証明書エラーの種類と見え方

同じ原因でも、ツールによって見え方が違います。まずは対応を押さえましょう。

NET::ERR_CERT_DATE_INVALIDは期限切れか時刻のずれ、COMMON_NAME_INVALIDは名前の不一致、AUTHORITY_INVALIDは信頼されない発行者、ブラウザーは正常でcurlだけ60になるのは中間証明書の入れ忘れ、REVOKEDは失効、VERSION_OR_CIPHER_MISMATCHはTLSの版・暗号の不一致、KEY_USAGE_INCOMPATIBLEは鍵の用途の不一致と、それぞれを確かめるコマンドを示す早見図
原因Chrome・Edgecurl(OpenSSL)PowerShell(SslStream)
期限切れNET::ERR_CERT_DATE_INVALIDcertificate has expired (10)RemoteCertificateChainErrors(NotTimeValid)
名前の不一致NET::ERR_CERT_COMMON_NAME_INVALIDno alternative certificate subject name matches target hostnameRemoteCertificateNameMismatch
信頼されない発行者(自己署名、ルートが未配布)NET::ERR_CERT_AUTHORITY_INVALIDself-signed certificate (18)、self-signed certificate in certificate chain (19)RemoteCertificateChainErrors(UntrustedRoot)
チェーンの不足(中間証明書が無い)多くは表示されない(AIA で補う)unable to get local issuer certificate (20)多くは表示されない(Windows も AIA で補う)
失効NET::ERR_CERT_REVOKED(CRLSets に載った場合など)既定では確認しない既定では確認しない
$ curl -sS https://expired.badssl.com/ -o /dev/null
curl: (60) SSL certificate OpenSSL verify result: certificate has expired (10)

badssl.comは、エラーの見え方を試すための公開のテスト用サイトです(expired・wrong.host・self-signed・untrusted-root・incomplete-chain・revokedなど)。curlの検証エラーは終了コード60です。失効は、ブラウザーもcurlも確認しないことが多い点に注意します(第5回)。

ほかのエラー

原因Chrome・Edgecurl・openssl確認のポイント
端末の時刻のずれNET::ERR_CERT_DATE_INVALID(「時計が遅れています」などの画面)certificate is not yet valid (9) または has expired (10)timedatectl、w32tm /query /status
古いアルゴリズム(SHA-1 の署名)NET::ERR_CERT_WEAK_SIGNATURE_ALGORITHMCA signature digest algorithm too weak (68)openssl x509 -noout -text の Signature Algorithm
鍵の用途の不一致(Key Usage・EKU)ERR_SSL_KEY_USAGE_INCOMPATIBLE などunsuitable certificate purpose(error 26)KU に digitalSignature、EKU に serverAuth(第4回)
有効期間が長すぎる(公開証明書)NET::ERR_CERT_VALIDITY_TOO_LONGエラーにならない公開CA の上限は現在200日(第6回)
CT の記録が無い(公開証明書)NET::ERR_CERTIFICATE_TRANSPARENCY_REQUIREDエラーにならない証明書の SCT(第5回)
TLS のバージョン・暗号スイートの不一致ERR_SSL_VERSION_OR_CIPHER_MISMATCHcurl: (35)、unsupported protocol後述

Edge(Chromium)は124版から、社内のルートにつながるRSAの証明書でもKey Usageを必ず確認するようになりました。古い機器の自己署名証明書で急に開けなくなる原因の1つです。PowerShellでは、用途の不一致がNotValidForUsageとして見えます。


中間証明書の入れ忘れ

第5回でも扱いましたが、いちばん多いトラブルなので改めて見ておきます。

サーバーがサーバー証明書だけを送ると、Chrome・EdgeはAIAのURLから取得して成功し、Firefoxは読み込み済みの中間証明書の束で公開CAなら成功するが社内CAでは失敗し、curl・Java・機器は補わずunable to get local issuer certificateになることと、正しい状態は2枚を送ることを示す図

サーバーは、自分の証明書に加えて中間証明書(ルートを除くチェーン)も送る必要があります。

  • 入れ忘れても、Chrome・EdgeやWindowsは、証明書のAIAのURLから中間証明書を取りに行って補うため、ブラウザーでは正常に見える
  • FirefoxはAIAを使わないが、公開CAの中間証明書をあらかじめ読み込んでいるため、やはり気づきにくい。社内CAの証明書では、Firefoxも失敗する
  • curl・openssl・Java・Pythonや多くの機器は補わないため、「ブラウザーでは開けるのにAPI連携だけ失敗する」という形で見つかる

確認は、サーバーが送った本数を見ることです。openssl s_client -showcertsのCertificate chainに0番しか無ければ不足しています。ブラウザーで開けても、チェーンが正しいとは限りません。


TLSバージョン・暗号スイートの不一致

証明書は正しくても、交渉の段階で失敗するケースです。症状は、ChromeのERR_SSL_VERSION_OR_CIPHER_MISMATCH、curlの終了コード35、古いアプリの「ハンドシェイクに失敗しました」です。

TLS 1.0/1.1・RSAのみの古いクライアントとTLS 1.2/1.3・ECDSAのサーバーの間に共通の項目が無くERR_SSL_VERSION_OR_CIPHER_MISMATCHになることと、中継装置がX25519MLKEM768の大きなClientHelloを落とすこと、SNIなしで既定の別の証明書が返り名前の不一致に見えることを示す図

よくある原因は次の5つです。

  1. 古い機器・Java・.NET FrameworkがTLS 1.2以上を話せない(TLS 1.0/1.1はRFC 8996で禁止、第7回)
  2. サーバーがECDSAの証明書だけで、クライアントがRSAしか扱えない
  3. 暗号スイートを絞りすぎた
  4. SNIを送らない古いクライアントに、既定の別の証明書が返る
  5. 通信を検査する中継装置が新しい方式(大きなClientHelloになるPQCの鍵交換など)に対応していない
$ openssl s_client -connect www.example.jp:443 -servername www.example.jp -tls1_2 </dev/null | grep -E "^(New|Protocol)"
$ openssl s_client -connect www.example.jp:443 -servername www.example.jp </dev/null | grep "Negotiated"
Negotiated TLS1.3 group: X25519MLKEM768
$ curl -sv https://www.example.jp/ -o /dev/null 2>&1 | grep "SSL connection"
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384 / X25519MLKEM768 / id-ecPublicKey

Windowsでは、バージョンを指定して接続を試します(Tls12をTls13に変えて比べる)。サーバー側で有効な暗号スイートはGet-TlsCipherSuiteで確認します。

PS> $t=New-Object Net.Sockets.TcpClient('www.example.jp',443); $s=New-Object Net.Security.SslStream($t.GetStream())
PS> $s.AuthenticateAsClient('www.example.jp',$null,[Security.Authentication.SslProtocols]::Tls12,$false); $s.SslProtocol

切り分けの手順

証明書エラーの切り分けは、症状を固める → 名前と経路 → サーバーが送る証明書 → 端末での検証 → TLSのパラメーターの順に確かめます。図は、各段階をどこで確かめるかを示しています(番号は表の段階)。

端末(信頼ストア・時計)、DNS・経路(LB・プロキシ)、サーバー(証明書+中間証明書)、CAの公開場所(AIA・CRLのWeb)の各段と、それぞれを確かめるコマンドの対応を示し、全員に出るならサーバー側、一部だけなら端末側から疑うことを示す図
段階確認することコマンドの例分かること
0 症状を固める誰が・どの端末とブラウザーで・どの URL・いつから・エラーコード画面のエラーコード(NET::ERR_…)を控える全員か一部か。名前・期限・信頼のどれか
1 名前と経路名前がどの IP になり、どの機器(LB・プロキシ)に届くかdig、Resolve-DnsName、Test-NetConnection 名前 -Port 443意図しないサーバーや、検査用のプロキシに届いていないか
2 サーバーが送る証明書主体・SAN・期限・発行者、送られたチェーンの本数openssl s_client -showcerts、PowerShell の SslStream期限切れ、名前、中間証明書の不足
3 端末での検証信頼ストアにルートがあるか、時刻、CRL・AIA に届くかcertutil -verify -urlfetch、Get-ChildItem Cert:\LocalMachine\Root、w32tm /query /statusルートの未配布、時刻のずれ、失効の確認先に届かない
4 TLS のパラメーターバージョン、暗号スイート、SNI、クライアント証明書openssl s_client -tls1_2、curl -v交渉の不一致、SNI が無く別の証明書が返る

全員に出るならサーバー側、一部の端末だけなら端末側(ルート・時刻・経路)から疑うのが基本です。

そして大事なことを1つ。端末で「詳細設定 → 進む」を押して回避したり、curl -kを設定に残したりしないでください。回避した端末だけが中間者攻撃に無防備になります(第1回)。なお、検査用のプロキシを通る端末では、発行者がプロキシのCAに置き換わって見えます。


コマンドで確かめる

Linux・Macでは、サーバーが送ったチェーンの本数と、証明書の期限・名前・用途を見ます。

$ openssl s_client -connect www.example.jp:443 -servername www.example.jp -showcerts </dev/null
Certificate chain
 0 s:CN=www.example.jp
   i:C=JP, O=Example Corp, CN=Example Issuing CA 01
   a:PKEY: RSA, 2048 (bit); sigalg: sha256WithRSAEncryption
   v:NotBefore: Jul 28 20:03:02 2026 GMT; NotAfter: Feb 13 20:03:01 2027 GMT
    Verify return code: 21 (unable to verify the first certificate)
(抜粋。1 番の中間証明書が無い=入れ忘れ)

$ openssl x509 -in server.pem -noout -subject -issuer -enddate -ext subjectAltName,keyUsage,extendedKeyUsage
$ openssl x509 -in server.pem -noout -checkend 2592000   # 30日以内に切れるなら終了コード 1
$ openssl verify -crl_check -crl_download -untrusted chain.pem server.pem
error 23 at 0 depth lookup: certificate revoked

Windows(PowerShell)では、接続して証明書と検証の結果を表示します。失効とAIAの取得は、certutil -verify -urlfetchで1つずつ確かめます。

PS> $h='www.example.jp'; $t=New-Object Net.Sockets.TcpClient($h,443)
PS> $s=New-Object Net.Security.SslStream($t.GetStream(),$false,{param($a,$c,$ch,$e) $global:pe=$e; $global:st=$ch.ChainStatus.Status -join ','; $true})
PS> $s.AuthenticateAsClient($h); $c=[Security.Cryptography.X509Certificates.X509Certificate2]$s.RemoteCertificate
PS> $c | Format-List Subject, Issuer, NotAfter, @{n='SAN';e={$c.Extensions['2.5.29.17'].Format($false)}}
PS> "$($s.SslProtocol) $pe $st"          # 例:Tls13 RemoteCertificateChainErrors NotTimeValid
PS> certutil -verify -urlfetch server.cer

原因と確認コマンドの対応表

ここまでを1枚の表にまとめます。障害対応のときに手元に置いておくと便利です。

原因見える症状確認コマンド(Linux・Mac/Windows)詳しくは
期限切れDATE_INVALID、curl (60) has expiredopenssl x509 -noout -enddate/PowerShell の NotAfter、certutil -dump第8回
名前の不一致COMMON_NAME_INVALIDopenssl x509 -noout -ext subjectAltName/$c.Extensions[‘2.5.29.17’]第4回
中間証明書の不足ブラウザーは正常、curl・Java だけ (60)openssl s_client -showcerts の本数/サーバーで Get-ChildItem Cert:\LocalMachine\CA第5回
ルートが未配布一部の端末だけ AUTHORITY_INVALIDopenssl verify -CAfile/Get-ChildItem Cert:\LocalMachine\Root、certutil -verify第5回
失効、CRL に届かないREVOKED、または接続が遅い・失敗openssl verify -crl_check -crl_download/certutil -verify -urlfetch第5回
時刻のずれ一部の端末だけ DATE_INVALIDtimedatectl/w32tm /query /status—
SHA-1・用途の不一致WEAK_SIGNATURE_ALGORITHM、KEY_USAGE_INCOMPATIBLEopenssl x509 -noout -text(Signature Algorithm、Key Usage)/certutil -dump第4回
TLS のバージョン・暗号VERSION_OR_CIPHER_MISMATCH、curl (35)openssl s_client -tls1_2、curl -v/Get-TlsCipherSuite(サーバー側)第7回

連載のまとめ:回ごとの要点の早見表

ここで、連載全体を振り返っておきます。困ったときは、この表から該当する回に戻ってください。

回テーマ押さえること使う場面
第1〜2回脅威と暗号の基礎盗聴・改ざん・なりすまし、ECDHE と前方秘匿性、HMAC と署名の違い仕組みの説明、設計の前提
第3〜4回試す、証明書の中身openssl・PowerShell で作って読む。検証は SAN、EKU・AIA・CDP証明書の確認(第11回 問4)
第5回PKI と信頼チェーンと信頼ストア、検証の手順、CRL は必須・OCSP は任意、CT・CAA信頼されない理由の調査
第6回種類と発行DV・OV・EV、ワイルドカードは1階層、CSR、200日→100日→47日証明書の手配(第11回 問1・問3)
第7回TLS の仕組みTLS 1.3、1.0/1.1 は禁止、SNI・mTLS、PQC のハイブリッド鍵交換設定の見直し、接続の調査
第8回ライフサイクルと自動化棚卸し、ACME と ARI、期限と CT の監視47日への備え(第11回 問2)
第9回社内PKI とクラウドAD CS の2層と ESC の対策、ACM・Private CA、LB の終端、VMCA社内の証明書、vSphere(第11回 問3・問5)
第10回トラブルシュートエラーコードの読み方、中間証明書の入れ忘れ、切り分けの順序障害対応(第11回 問4)

どの回も、「誰が、どの名前の、どの鍵を、誰の署名で信じているか」を意識すると理解しやすくなります。


持ち帰ってほしい3つのこと

1. 名前と鍵の結び付きを確かめる(SAN・チェーン・期間・用途・時刻をopenssl s_client -showcertsやcertutil -verifyで)、2. 名前で数え置き場所を減らす(名前の数=証明書の枚数、秘密鍵は終端する1か所だけ)、3. 更新は人から仕組みへ(200日→100日→47日、ACMEと監視)の3つの持ち帰りを示す図

1. 証明書は、名前と公開鍵の結び付き
エラーは回避せず、名前(SAN)・チェーン・期間・用途・時刻の順に、openssl s_client(WindowsはPowerShell・certutil)で確かめる。

2. 証明書は台数ではなく名前で数え、TLSをどこで終端するかで置き場所を決める
秘密鍵の置き場所は少ないほど安全で、更新も楽になる。

3. 更新は人から仕組みへ
有効期間は2029年に47日になる。棚卸し・ACMEによる自動化・期限とCTの監視を今から整え、社内CAは2層で作り、Tier 0として守る。

まずは自分が管理するサイトにopenssl s_client -showcertsを実行し、送られているチェーンと期限を確かめてみてください。

参考資料

  • RFC:5280(X.509・CRL)、9525(名前の検証)、9846(TLS 1.3、2026年に8446を置き換え)、8996(TLS 1.0/1.1の廃止)、9325(TLSの推奨)、8555(ACME)、9773(ARI)、6962・9162(CT)、8659(CAA)、6960(OCSP)、2104(HMAC)
  • CA/B Forum・ルートプログラム:Baseline RequirementsとBallot SC-081(有効期間の短縮)、Chrome Root Program、Mozilla Root Store Policy、Microsoft Trusted Root Program、Apple Root Certificate Program
  • 公式ドキュメント:Microsoft Learn(AD CS、KB5014754)、AWS Certificate Manager・AWS Private CAユーザーガイド、Azure Key Vaultのドキュメント、Broadcom TechDocs(vSphere Authentication)、OpenSSLのマニュアル、Let’s Encryptのドキュメント

まとめ

  • エラーは回避しない。-kや「進む」は、その端末だけを中間者攻撃に無防備にする
  • 同じ原因でもツールで見え方が違う。ブラウザーのエラーコードから原因の見当をつける
  • 中間証明書の入れ忘れは、ブラウザーでは見えずcurl・Java・機器だけで失敗する
  • 証明書が正しくても、TLSの版・暗号スイート・SNI・中継装置で失敗することがある
  • 切り分けは症状→名前と経路→サーバーの証明書→端末での検証→TLSのパラメーター。全員ならサーバー側、一部なら端末側

次回はいよいよ最終回。ここまでの知識を使って、5つの設計演習に取り組みます。


連載「証明書入門」全11回

  1. なぜ証明書が必要か
  2. 暗号の基礎
  3. openssl・PowerShellで試す
  4. X.509証明書の中身
  5. PKIと信頼の仕組み
  6. 証明書の種類と発行
  7. TLSの仕組み
  8. ライフサイクルと自動化
  9. 社内PKIとクラウド
  10. 証明書トラブルシュート(この記事)
  11. 証明書の設計演習

コメント

タイトルとURLをコピーしました