【証明書入門 第4回】X.509証明書の中身 ― SAN・拡張・ファイル形式と中身の読み方

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

「証明書のCNは正しいのに、ブラウザーで名前のエラーが出るんです」
「.crtファイルを渡したのに、製品に読み込めないと言われました」

どちらも、証明書の中身を読めれば、すぐに原因が分かる相談です。前者はSAN、後者はファイル形式の問題であることがほとんどです。

第3回では、opensslとPowerShellで自己署名証明書を作りました。第4回では、その証明書の中身(X.509)を読み解きます。どの項目が何を意味し、どの拡張を見ればよいか、ファイル形式をどう見分けるかまでを押さえましょう。


証明書の構造

証明書の形式はITU-TのX.509で決まっており、インターネットでの使い方はRFC 5280が定めています。現在使われるのはバージョン3(v3)です。

証明書がtbsCertificate(バージョン・シリアル番号・署名アルゴリズム・発行者・有効期間・主体・主体の公開鍵・拡張)、署名アルゴリズム、署名値の3つの部分からなり、CAがtbsCertificateのハッシュを秘密鍵で署名し、検証する側がCAの公開鍵で照合することを示す図

中身は3つの部分からなります。

  1. tbsCertificate(to be signed、署名される部分):発行者、主体(持ち主)、有効期間、公開鍵、拡張など、証明書として主張したい内容がすべて入る
  2. 署名アルゴリズム
  3. 署名値:発行者である認証局(CA)が、tbsCertificateを自分の秘密鍵で署名した結果

検証する側は、発行者の公開鍵で署名値を確かめ、内容が書き換えられていないことと、確かにそのCAが発行したことを確認します(第2回の署名と同じ仕組み)。

つまり証明書とは、「この公開鍵の持ち主はこの名前だ」という内容に、CAが署名した文書です。署名の対象は①の全体なので、名前も期間も拡張もCAが保証しています。1ビットでも書き換えれば、署名の検証に失敗します。


基本の項目

項目openssl の表示内容例
バージョンVersion拡張を持てるのは v3。現在はほぼすべて v33 (0x2)
シリアル番号Serial Number発行者ごとに一意の番号。失効もこの番号で指す61:ae:ba:e2:…(最大20バイト)
署名アルゴリズムSignature AlgorithmCA が署名に使った方式ecdsa-with-SHA256、sha256WithRSAEncryption
発行者Issuer署名した CA の名前(DN)CN=Example Issuing CA E1
有効期間Validity(Not Before/Not After)この期間だけ有効。UTC で記録Sep 26 2026 〜 Apr 13 2027 GMT
主体Subject持ち主の名前(DN)CN=www.example.jp
主体の公開鍵Subject Public Key Info公開鍵とそのアルゴリズムEC P-256、RSA 2048ビット
拡張X509v3 extensions名前(SAN)、用途、CA かどうか等Subject Alternative Name 等
  • 公開CAのシリアル番号は、推測されないよう64ビット以上の乱数を含めることが決められている(第5回のBaseline Requirements)
  • 有効期間はUTCで記録され、opensslはGMTで、Windowsの画面やPowerShellは現地時刻(日本では9時間進む)で表示する。期限を報告するときは、どちらの時刻かを添える

主体(DN)の書き方

主体(Subject)や発行者(Issuer)は、DN(識別名)という形式で書きます。

略号属性の正式名内容例
CcountryName国(ISO 3166 の2文字)JP
STstateOrProvinceName都道府県Tokyo
LlocalityName市区町村Chiyoda-ku
OorganizationName組織名。OV・EV で審査されるExample Corp.
OUorganizationalUnitName部署名。公開CAの証明書には2022年9月1日から入れられない(入れない)
CNcommonName慣習で FQDN を書く。ホスト名の検証には使われないwww.example.jp

同じ証明書でも、ツールによって見た目が変わります。

openssl の表示: C=JP, ST=Tokyo, L=Chiyoda-ku, O=Example Corp., CN=www.example.jp
Windows の表示: CN=www.example.jp, O=Example Corp., L=Chiyoda-ku, S=Tokyo, C=JP

opensslの-subjや設定ファイルにはST=と書きます(RFC 4514)。ただし、Windowsの画面とPowerShellは同じ属性をS=と表示し、並び順も逆(CNが先)です。また、DV証明書の主体はCN=だけのことが多く、C・ST・L・Oは入りません。


SAN:ホスト名の検証はSANだけ

冒頭の「CNは正しいのに名前のエラー」の答えがここです。

SAN(Subject Alternative Name、主体の別名)は、証明書を使えるホスト名やIPアドレスを列挙する拡張です。ブラウザーがアクセス先の名前と照合するのはSANだけで、CNは見ません。

ブラウザーがhttps://www.example.jp/を開くとき、SubjectのCNは照合に使わずSANだけを照合することと、2000年のRFC 2818から2023年のRFC 9525までCNが照合から外れていった経緯を示す図

SANが無いときにCNを使う逃げ道は、Chromeが2017年のChrome 58で廃止し、2023年のRFC 9525もCNを識別に使ってはならないと明記しました。公開CAの証明書には2012年からSANが必須です。

CNに正しい名前を書いても、SANに無ければNET::ERR_CERT_COMMON_NAME_INVALIDになります。

一方、opensslの-verify_hostnameなど一部のツールは、SANが無いと今もCNを見ます。「opensslでは通るのにブラウザーではエラー」なら、SANの書き忘れを疑いましょう。

ポイント
・名前の照合はSANだけ。ブラウザーもRFC 9525もCNを使わない
・名前が1つでもSANに書く。CNを入れるならSANの値と同じにする
・一部のツールはCNを見るため、ツールで通ってもブラウザーで失敗しうる


SANの書き方と一致のルール

SANに書いた値が、どの名前に一致するのかを整理します。

DNS:www.example.jp、DNS:example.jp、DNS:*.example.jp、IP:192.0.2.10のそれぞれについて、一致する名前と一致しない名前を示し、ワイルドカードは左端の1ラベルだけでドメイン自身は含まないことを示す図

特にワイルドカードは誤解が多いところです。

  • *.example.jpはwww.example.jpやmail.example.jpに一致する
  • しかしexample.jp自身には一致しない。必要ならDNS:example.jpも並べて書く
  • a.b.example.jpのような2階層下にも一致しない。ワイルドカードは左端の1ラベルだけを置き換える(RFC 9525)

実際にopensslで確かめると、2階層下の名前はエラーになります。

$ openssl x509 -in wc.crt -noout -ext subjectAltName
X509v3 Subject Alternative Name:
    DNS:*.example.jp, DNS:example.jp, IP Address:192.0.2.10

$ openssl verify -CAfile root.crt -untrusted inter.crt -verify_hostname a.b.example.jp wc.crt
CN=*.example.jp
error 62 at 0 depth lookup: hostname mismatch
error wc.crt: verification failed

IPアドレスで開く管理画面などは、IP:型で書きます。ホスト名でも開くならDNS:も並べて書きます(IP:だけではホスト名での接続に一致しません)。公開CAのIPアドレス証明書は少なく、Let’s Encryptは2026年1月から有効期間約6日の短期証明書として提供しています。SANの決め方とワイルドカードの使いどころは第6回で扱います。


主な拡張① 用途と制約

拡張意味サーバー証明書ではCA 証明書では
Basic ConstraintsCA かどうか(CA:TRUE/FALSE)と、下に置ける階層の数(pathlen)CA:FALSE(critical)CA:TRUE。中間CA は pathlen:0 が多い
Key Usage鍵で行ってよい操作Digital Signature(RSA では Key Encipherment も入ることがある)Certificate Sign、CRL Sign
Extended Key Usage(EKU)証明書の用途TLS Web Server Authentication(serverAuth)中間CA では発行できる用途を限定する
Name Constraints発行してよい名前の範囲入れない社内CA を corp.example.jp 配下に限る等
  • critical(重要)が付いた拡張を理解できない検証者は、その証明書を拒否しなければならない(RFC 5280)
  • Chrome Root Programは公開CAの階層をサーバー認証専用にする方針で、2027年3月15日以降に発行するサーバー証明書のEKUはserverAuthだけに限られる。クライアント証明書(mTLS、第7回)には社内CA(第9回)を使う

主な拡張② 識別子・所在・ポリシー・CT

拡張意味使われ方
Subject Key Identifier(SKI)自分の公開鍵の識別子CA 証明書では必須。チェーンを組む手がかり
Authority Key Identifier(AKI)発行者の公開鍵の識別子親の SKI と照合する。同じ名前の CA が複数あっても区別できる
Authority Information Access(AIA)発行者の証明書の URL(CA Issuers)、OCSP の URL中間証明書の自動取得、失効の確認(第5回)
CRL Distribution Points(CDP)失効リスト(CRL)の URL失効の確認(第5回)
Certificate Policies発行の方針を示す OID2.23.140.1.2.1=DV、2.23.140.1.2.2=OV、2.23.140.1.1=EV
CT Precertificate SCTsCT ログに登録した証拠(SCT)Chrome・Safari・Firefox は SCT の足りない公開証明書を拒否(第5回)

いまLet’s Encryptの証明書を読むと、AIAにはCA Issuersだけがあり、OCSPのURLがありません。2025年にOCSPを終了し、代わりにCDPを入れているためです(第5回)。

この表の6つと、前の表の4つが見分けられれば、実務で証明書を読むには十分です。


ファイル形式と拡張子

冒頭の「.crtを渡したのに読み込めない」の話です。

PEM(テキスト、証明書・チェーンも連結可・秘密鍵のことも)、DER(バイナリ、証明書1枚)、PKCS#12(パスワードで暗号化、証明書・チェーン・秘密鍵)、PKCS#7(証明書とチェーン、秘密鍵は入らない)の4つの形式と、拡張子は中身を保証しないことを示す図
形式中身見分け方主な使われ方
PEMDER を Base64 にした文字列(RFC 7468)—–BEGIN CERTIFICATE—– で始まるApache、nginx、Linux 全般。複数の証明書を連結できる
DERASN.1 の構造をそのままバイナリにしたもの先頭が 16進で 30 82。テキストで開くと文字化けJava、Windows の .cer(DER の場合)
PKCS#12(.pfx/.p12)証明書+チェーン+秘密鍵を1つにし、パスワードで暗号化バイナリ。開くとパスワードを聞かれるWindows(IIS)、Azure、Java のキーストア
PKCS#7(.p7b)証明書とチェーンだけ。秘密鍵は入らない—–BEGIN PKCS7—– またはバイナリ中間証明書の配布、Windows へのチェーンの取り込み

拡張子は中身を保証しません。.crt・.cerはPEMのこともDERのこともあり、.pemに秘密鍵が入っていることもあります。先頭の1行(head -1、PowerShellではGet-Content -TotalCount 1)のBEGINの後ろの語(CERTIFICATE、PRIVATE KEY、PKCS7等)で判断します。


形式を変換する

$ openssl x509 -in server.crt -outform DER -out server.der            # PEM → DER
$ openssl x509 -in server.der -inform DER -out server.pem             # DER → PEM
$ openssl pkcs12 -export -inkey server.key -in server.crt \
    -certfile inter.crt -out server.pfx                               # → PKCS#12
$ openssl pkcs12 -in server.pfx -noenc -out all.pem                   # PKCS#12 → PEM
$ openssl crl2pkcs7 -nocrl -certfile fullchain.pem -out chain.p7b     # → PKCS#7
PS> certutil -encode server.der server.pem                            # DER → PEM(-decode で逆)
PS> Export-PfxCertificate -Cert Cert:\LocalMachine\My\<拇印> -FilePath server.pfx `
      -Password (Read-Host -AsSecureString)                           # ストア → PKCS#12

変換の現場でよく引っかかるのが、次の3点です。

  • 証明書と秘密鍵が組かどうかは、openssl x509 -in server.crt -noout -pubkey | openssl sha256とopenssl pkey -in server.key -pubout | openssl sha256の値が一致するかで確かめる
  • OpenSSL 3のPKCS#12はAES-256で暗号化される。Windows Server 2016以前は読み込めず「パスワードが正しくない」と表示されるので、-legacyを付けて作り直す
  • Windows標準の機能では、PEMの秘密鍵から.pfxを作れない。Windows上でCSRを作る(certreq、第6回)か、PowerShell 7の[X509Certificate2]::CreateFromPemFile()を使う

中身を読む① openssl

$ openssl x509 -in server.crt -noout -text
        Serial Number:
            61:ae:ba:e2:5c:18:e5:03:b7:d2:6a:d8:87:49:26:83:dd:3c:00:e5
        Signature Algorithm: ecdsa-with-SHA256
        Issuer: C=JP, O=Example CA Inc., CN=Example Issuing CA E1
            Not Before: Sep 26 00:55:58 2026 GMT
            Not After : Apr 13 00:55:58 2027 GMT
        Subject: C=JP, ST=Tokyo, L=Chiyoda-ku, O=Example Corp., CN=www.example.jp
                Public-Key: (256 bit)
            X509v3 Basic Constraints: critical
                CA:FALSE
            X509v3 Extended Key Usage:
                TLS Web Server Authentication
            X509v3 Subject Alternative Name:
                DNS:www.example.jp, DNS:example.jp

(出力は要所のみの抜粋です)

読む順番は、次のとおりです。

  1. SANとSubject(誰の証明書か)
  2. Issuer(誰が発行したか)
  3. Not After(いつまでか、GMT)
  4. Basic ConstraintsとEKU(何に使えるか)

IssuerとSubjectが同じなら、自己署名(ルート証明書か、自分で作った証明書)です。

要点だけならopenssl x509 -in server.crt -noout -subject -issuer -dates -ext subjectAltName。-checkend 2592000を付けると、30日以内に期限が切れる場合に終了コード1を返すので、監視に使えます(第8回)。


中身を読む② Windows(PowerShell・certutil)

PS> $c = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new("C:\work\server.crt")
PS> $c | Format-List Subject, Issuer, NotBefore, NotAfter, Thumbprint

Subject    : CN=www.example.jp, O=Example Corp., L=Chiyoda-ku, S=Tokyo, C=JP
Issuer     : CN=Example Issuing CA E1, O=Example CA Inc., C=JP
NotBefore  : 2026/09/26 9:55:58
NotAfter   : 2027/04/13 9:55:58
Thumbprint : 1B2958866A114AC0EA9D7000EF51F137DBDA0614

PS> $c.Extensions | ForEach-Object { $_.Oid.FriendlyName; $_.Format($false) }
PS> Get-ChildItem Cert:\LocalMachine\My | Format-List Subject, DnsNameList, NotAfter, Thumbprint
PS> certutil -dump server.crt
  • X509Certificate2はWindows PowerShell 5.1でもPowerShell 7でも使える。時刻は現地時刻(JST)で、opensslのGMTより9時間進んで見える
  • 拡張はExtensionsのFormat()で文字にできる。日本語版ではSANが「サブジェクト代替名」、EKUが「拡張キー使用法」と表示される
  • サーバーに入っている証明書はCert:\ドライブで一覧でき、DnsNameListにSANのDNS名が出る。certutil -dumpはopensslの-textに近い全項目の表示
  • 拇印(Thumbprint)は証明書全体のSHA-1の指紋で、証明書を指し示すための値。署名アルゴリズムとは関係なく、SHA-1でも問題ない

サーバーから証明書を取ってくる

最後に、動いているサーバーから証明書を取ってくる方法です。

$ openssl s_client -connect www.example.jp:443 -servername www.example.jp -showcerts </dev/null
Certificate chain
 0 s:C=JP, ST=Tokyo, L=Chiyoda-ku, O=Example Corp., CN=www.example.jp
   i:C=JP, O=Example CA Inc., CN=Example Issuing CA E1
   v:NotBefore: Sep 26 00:55:58 2026 GMT; NotAfter: Apr 13 00:55:58 2027 GMT
 1 s:C=JP, O=Example CA Inc., CN=Example Issuing CA E1
   i:C=JP, O=Example CA Inc., CN=Example Root CA R1
Verify return code: 0 (ok)
s_client -showcertsの出力で、0番のサーバー証明書のi:(発行者)が1番の中間証明書のs:(主体)と一致すればつながっていること、ルートは端末の信頼ストアにあること、中間証明書が無いとVerify return code: 21になることを示す図
  • 0がサーバー証明書、1以降がサーバーの送った中間証明書。各番号のi:(発行者)が次の番号のs:(主体)と一致すれば、つながっている
  • -servername(SNI、第7回)を付けないと、別のサイトの証明書が返ることがある
  • 中間証明書が送られていないとVerify return code: 21 (unable to verify the first certificate)になる(第5回・第10回)

Windowsでは、PowerShellで次のように取れます。

PS> $tcp = [System.Net.Sockets.TcpClient]::new("www.example.jp", 443)
PS> $ssl = [System.Net.Security.SslStream]::new($tcp.GetStream(), $false, { $true })
PS> $ssl.AuthenticateAsClient("www.example.jp")
PS> $c = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new($ssl.RemoteCertificate)
PS> $c | Format-List Subject, Issuer, NotAfter
PS> [IO.File]::WriteAllBytes("C:\work\www.cer", $c.Export("Cert")); $ssl.Dispose(); $tcp.Dispose()

{ $true }は、検証を省いて証明書を受け取る指定です。中身を調べる目的に限って使いましょう。保存した.cerは、前の節の方法で読めます。


考えてみよう

  1. 社内で使っているサーバー証明書を1つ選び、openssl x509 -textかcertutil -dumpで読んでください。SANには、利用者が実際に入力する名前(短い名前、別名、IPアドレス)がすべて入っていますか。
  2. 「証明書ファイル(.crt)を渡したのに、製品に読み込めない」と言われました。考えられる原因は何ですか。最初に何を確かめますか。
  3. 自作の監視スクリプトや古い機器が、SANではなくCNでホスト名を確かめていないでしょうか。どうやって確かめますか。

ヒント:この記事の「SANとCN」「ファイル形式と先頭の1行」「鍵と証明書の組の確認」が手がかりです。


まとめ

  • 証明書は「公開鍵と名前の組」にCAが署名した文書。署名の対象は名前も期間も拡張も含む全体
  • ホスト名の照合はSANだけ。CNに書いても、SANに無ければエラー
  • ワイルドカードは左端の1ラベルだけ。ドメイン自身も2階層下も含まない
  • 見るべき拡張はBasic Constraints・Key Usage・EKU・AIA・CDPなど10個ほど
  • 拡張子は中身を保証しない。先頭の1行で形式を見分ける
  • 期限はUTC(GMT)か現地時刻かを添えて報告する

次回は、証明書の発行者をどこまでさかのぼって信頼するのか、PKIと信頼の仕組みを扱います。


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

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

コメント

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