証明書
x.509
1122
どのような問題がありますか?

投稿日

更新日

図解 X.509 証明書

はじめに

X.509 証明書について解説します。(English version is here → "Illustrated X.509 Certificate")

※ この記事は 2020 年 7 月 1 日にオンラインで開催された Authlete 社主催の『OAuth/OIDC 勉強会【クライアント認証編】』の一部を文書化したものです。勉強会の動画は公開しており、X.509 証明書については『#4 X.509 証明書(1)』と『#5 X.509 証明書(2)』で解説しているので、動画解説のほうがお好みであればそちらをご参照ください。

1. デジタル署名(前提知識)

この記事を読んでいただくにあたり、デジタル署名に関する知識が必要となります。つまり、「秘密鍵を用いて生成された署名公開鍵検証することにより」、「対象データが改竄されていないこと」や「秘密鍵の保持者が確かに署名したこと」を確認できる、といった話を理解されていることが前提となります。

図にしますと、次のような流れをご存知であることが前提となります。

:one: 秘密鍵と公開鍵のペアを作る。
digital_signature_1.png

:two: 何らかの方法で相手に公開鍵を渡す。
digital_signature_2.png

:three: 相手に送りたいデータを用意する。
digital_signature_3.png

:four: データを入力とし、秘密鍵を用いて署名を作成する。
digital_signature_4.png

:five: データと署名を併せて相手に送る。
digital_signature_5.png

:six: 公開鍵を用いて署名を検証する。
digital_signature_6.png

2. 証明書チェーン

公開鍵暗号を活用するため、まず、秘密鍵と公開鍵のペアを作成します。
certificate_chain_01.png

そして、何らかの方法で公開鍵を相手に渡します。
certificate_chain_02.png

しかし、受け取り方にもよりますが、その公開鍵が本物かどうか確信が持てないとします。
certificate_chain_03.png

そこに第三者が現れ、当該公開鍵が正しいことを証明すると言います。
certificate_chain_04.png

その第三者は、証明するための書類、すなわち証明書の準備を始めます。
certificate_chain_05.png

まず、証明対象となる公開鍵を証明書に載せます。
certificate_chain_06.png

次に、その公開鍵に対応する秘密鍵を持っている人(以降主体者)の情報を載せます。
certificate_chain_07.png

そして、証明書の発行者(自分)の情報も載せます。
certificate_chain_08.png

最後に、この証明書の内容を保証するため、デジタル署名をつけることにします。
certificate_chain_09.png

そこで、デジタル署名用の秘密鍵と公開鍵のペアを作成します。
certificate_chain_10.png

それから、秘密鍵を用いてデジタル署名をおこないます。これで証明書の完成です。
certificate_chain_11.png

公開鍵を直接渡す代わりに、その公開鍵を含み、その出所を証明する証明書を渡します。
certificate_chain_12.png

これにより、元々欲しかった公開鍵(図中の「A の公開鍵」)は証明付きで入手できました。しかし今度は、証明書のデジタル署名を検証するための公開鍵(図中の「B の公開鍵」)が必要になってしまいます。
certificate_chain_13.png

そこに別の第三者が現れ、デジタル署名検証に必要な公開鍵のための証明書を提供すると言います。その第三者は証明書の準備を始めます。
certificate_chain_14.png

まず、証明対象となる公開鍵を証明書に載せます。
certificate_chain_15.png

次に、公開鍵の主体者の情報を載せます。
certificate_chain_16.png

そして、証明書の発行者(自分)の情報も載せます。
certificate_chain_17.png

最後に、この証明書の内容を保証するため、デジタル署名をつけることにします。
certificate_chain_18.png

そこで、デジタル署名用の秘密鍵と公開鍵のペアを作成します。
certificate_chain_19.png

それから、秘密鍵を用いてデジタル署名をおこないます。これで証明書の完成です。
certificate_chain_20.png

できあがった証明書を渡します。
certificate_chain_21.png

しかしここで、先ほどと同じ問題が発生します。新たに受け取った証明書のデジタル署名を検証するための公開鍵(図中の「C の公開鍵」)が必要になってしまいます。
certificate_chain_23.png

キリがないように思えますが、今度は、公開鍵の証明書を公開鍵の主体者本人(図中の C)が作成すると言い、証明書の準備を始めます。
certificate_chain_24.png

証明対象となる公開鍵を証明書に載せ、
certificate_chain_25.png

主体者情報を載せ、
certificate_chain_27.png

発行者(自分)の情報も載せます。
certificate_chain_28.png

最後にデジタル署名を付けますが、用いる秘密鍵は、証明対象の公開鍵とペアになっている秘密鍵です。このように、証明対象となる公開鍵とペアになっている秘密鍵を用いて署名することを、自己署名と言います。そして、自己署名によって作成された証明書を自己署名証明書と言います。自己署名証明書では、発行者と主体者が同じになります。
certificate_chain_29.png

自己署名証明書は起点となる証明書なので、信頼済みの証明書としてあらかじめ持っておく必要があります。
certificate_chain_30.png

ここまでの全体図は次のようになります。
certificate_chain.png

全体図の中から証明書の部分を抜き出すと次のようになります。
certificate_chain_31.png

これらの証明書は鎖のように繋がっています。このように連なった証明書群全体を証明書チェーン(certificate chain)と呼びます。
certificate_chain_32.png

そして、起点となる自己署名証明書をルート証明書(root certificate)、中間にある証明書を中間証明書(intermediate certificate)と呼びます。中間証明書の数は 2 以上になりえます。
certificate_chain_33.png

3. 証明書の構造

証明書の構造は RFC 5280 という技術文書で定義されています。X.509 証明書という名前は、同技術文書のタイトル「Internet X.509 Public Key Infrastructure Certificate and Certificate Revocation List (CRL) Profile」から来ています。

下記は、RFC 5280 の Section 4.1 から抜粋した、証明書構造定義の一部です。
asn1_certificate.png

この構造定義は ASN.1 (Abstract Syntax Notation One) という記法を用いて書かれています(ASN.1 関連仕様群は X.680 シリーズとして定義されています)。ASN.1 はデータ構造を抽象的に表現するための記法なので、ASN.1 による定義だけでは具体的にどのようなバイト配列にデータを落とし込めばいいのかは定まりません。
format_01.png

そのため、ASN.1 のデータ構造を具体的なバイト配列に落とし込むための技術仕様が別途必要になります。そのような技術仕様は、DER(Distinguished Encoding Rules)や XER(XML Encoding Rules)、JER(JSON Encoding Rules)など、数多く存在します。
format_02.png

具体的にバイト配列に落とし込んだあとに、データ形式を変換することもあるでしょう。例えば、DER はバイナリデータとなるので、これを BASE64RFC 4648)を用いてテキストデータに変換してもよいでしょう。
format_03.png

場合によっては、データを修飾することもあります。例えば、PEMRFC 7468)という規則を用いて、BASE64 データが何を表しているかという情報(例えば証明書を表しているという情報)を追加することができます。
format_04.png

ここまでに言及した ASN.1、DER、BASE64、PEM を X.509 証明書に適用してみます。

まず、証明書が持つべき情報(例えば主体者の情報など)を用意します。持つべき情報は RFC 5280 に ASN.1 で表現されています。
format_05.png

次に、これを DER を用いてバイト配列に落とし込みます。出来上がるデータはバイナリデータです。
format_06.png

バイナリデータを BASE64 でテキストデータに変換します。
format_07.png

PEM を用いて情報を追加します。この例では、BASE64 で表現されているデータが証明書なので、前後に「-----BEGIN CERTIFICATE-----」と「-----END CERTIFICATE-----」を追加しています。
format_08.png

3.1. subject フィールド

証明書には幾つかフィールドがありますが、データ構造を理解するための例として、ここでは主体者を表す subject フィールドを取り上げます。
asn1_subject.png

subject フィールドには、公開鍵に紐づく組織の識別名(Distinguished Name)が含まれています。識別名を文字列として表現する方法は RFC 4514 で定義されており、同仕様書には識別名の例として次のものが挙げられています。

  • UID=jsmith,DC=example,DC=net
  • OU=Sales+CN=J. Smith,DC=example,DC=net
  • CN=James \"Jim\" Smith\, III,DC=example,DC=net
  • CN=Before\0dAfter,DC=example,DC=net

識別名は「属性=値」の組み合わせで表現されます。上記例の UIDOUCN などは属性名です。

そして伝統的に、Web サイト用の証明書には、主体者識別名の CN(common name)属性の値としてサーバーのホスト名が含まれます。

具体例を見てみましょう。下記は、Authlete 社の Web サイトの証明書から subject フィールドを抜き出したものです。
subject_der.png

このバイナリデータを ASN.1 JavaScript decoder を用いて解析すると、次のような構造を持っていることが分かります。
subject_asn1.png

これにより、subject フィールドが CN=*.authlete.com という識別名を保持していることが分かります。
subject_dn.png

3.2. 主体者別名

主体者識別名の CN 属性を使う方法では、ホスト名しか指定できません。また、複数の主体者を含める方法も規定されていません。そこで登場するのが、主体者別名(Subject Alternative Name)拡張です。

証明書には、拡張データ群を格納する場所があります。
asn1_extensions.png

ここに、主体者別名拡張を置くことで、ホスト名以外で主体者を指定したり、複数の主体者を並べることができるようになります。主体者別名拡張は、RFC 5280 の Section 4.2.1.6 で次のように定義されています。
asn1_san.png

具体例を見てみましょう。下記は、Authlete 社の Web サイトの証明書から主体者別名拡張データを抜き出したものです。
san_der.png

このバイナリデータを ASN.1 JavaScript decoder を用いて解析すると、次のような構造を持っていることが分かります。
san_asn1.png

これにより、主体者別名拡張に *.authlete.comauthlete.com という二つの DNS 名が列挙されていることが分かります。
san.png

4. 自己署名証明書作成

自己署名証明書を作成することにより、理解を深めることにしましょう。

4.1. openssl コマンド

まず、openssl コマンドのバージョンを確認します。

$ /usr/bin/openssl version -a
LibreSSL 2.6.5
built on: date not available
platform: information not available
options:  bn(64,64) rc4(16x,int) des(idx,cisc,16,int) blowfish(idx) 
compiler: information not available
OPENSSLDIR: "/private/etc/ssl"

Mac では macOS High Sierra 以降、OpenSSL の代わりに LibreSSL を採用しているので、Mac で openssl コマンドのバージョンを確認すると、上記の例のように LibreSSL ?.?.? と表示されることと思います。

LibreSSL 版の openssl コマンドは OpenSSL 版の openssl コマンドに追加された新しいコマンドラインオプションに対応していないので、OpenSSL をインストールし、以降は OpenSSL 版の openssl コマンドを使うことにします。

$ brew install openssl
$ /usr/local/opt/openssl/bin/openssl version -a
OpenSSL 1.1.1g  21 Apr 2020
built on: Tue Apr 21 13:28:37 2020 UTC
platform: darwin64-x86_64-cc
options:  bn(64,64) rc4(16x,int) des(int) idea(int) blowfish(ptr) 
compiler: clang -fPIC -arch x86_64 -O3 -Wall -DL_ENDIAN -DOPENSSL_PIC -DOPENSSL_CPUID_OBJ -DOPENSSL_IA32_SSE2 -DOPENSSL_BN_ASM_MONT -DOPENSSL_BN_ASM_MONT5 -DOPENSSL_BN_ASM_GF2m -DSHA1_ASM -DSHA256_ASM -DSHA512_ASM -DKECCAK1600_ASM -DRC4_ASM -DMD5_ASM -DAESNI_ASM -DVPAES_ASM -DGHASH_ASM -DECP_NISTZ256_ASM -DX25519_ASM -DPOLY1305_ASM -D_REENTRANT -DNDEBUG
OPENSSLDIR: "/usr/local/etc/openssl@1.1"
ENGINESDIR: "/usr/local/Cellar/openssl@1.1/1.1.1g/lib/engines-1.1"
Seeding source: os-specific

4.2. 秘密鍵生成

$ openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 > private_key.pem
  • genpkey → 秘密鍵を作成するためのサブコマンドです。RSA や DSA の秘密鍵を作成するための genrsagendsa といったサブコマンドもありますが、現在は genpkey に取って代わられています。

  • -algorithm EC → 楕円曲線(Elliptic Curve)アルゴリズムを使います。-algorithm オプションを使うときは、-pkeyopt オプションより前に置かなければならないことに注意してください。

  • -pkeyopt ec_paramgen_curve:P-256 → 楕円曲線アルゴリズム固有のパラメーターである curve の値として P-256 曲線を使います。これは NIST(National Institute of Standards and Technology;アメリカ国立標準技術研究所)による推奨値の一つです。P-256 の定義については、Digital Signature Standard (DSS) の「D.2.3. Curve P-256」を参照してください。

上記のコマンドを実行することにより、次のような内容を持つファイルが作成されます。形式は PEM で、BASE64 データが秘密鍵を表していることを示すため、先頭行と最終行にそれぞれ「-----BEGIN PRIVATE KEY-----」と「-----END PRIVATE KEY-----」が置かれています。

-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgwSmbZ2gTKFbyy+q6
jXhuGXFuz8mCqjUhRYvQ/EEgqqihRANCAAS4QckKxSuC89ZfQyTWP+M01QUcJRRS
HG/oYhFEU5FOoSJGgxZgJeUFUglE3HiT/NCsdj8COWDCTqP9Iz2oEDnz
-----END PRIVATE KEY-----

4.3. 秘密鍵の内容確認

$ openssl pkey -text -noout -in private_key.pem 
Private-Key: (256 bit)
priv:
    c1:29:9b:67:68:13:28:56:f2:cb:ea:ba:8d:78:6e:
    19:71:6e:cf:c9:82:aa:35:21:45:8b:d0:fc:41:20:
    aa:a8
pub:
    04:b8:41:c9:0a:c5:2b:82:f3:d6:5f:43:24:d6:3f:
    e3:34:d5:05:1c:25:14:52:1c:6f:e8:62:11:44:53:
    91:4e:a1:22:46:83:16:60:25:e5:05:52:09:44:dc:
    78:93:fc:d0:ac:76:3f:02:39:60:c2:4e:a3:fd:23:
    3d:a8:10:39:f3
ASN1 OID: prime256v1
NIST CURVE: P-256
  • pkey → 鍵に関する処理をおこなうためのサブコマンドです。

  • -text → プレインテキストで情報を表示します。

  • -noout → エンコードされた情報の表示を抑制します。

  • -in private_key.pem → 入力ファイルを指定します。

出力例から、秘密鍵が公開鍵の情報を内包していることが分かりますが、これは、鍵を JWKRFC 7517)形式で表現すると分かりやすくなります。

$ npm install -g eckles
$ eckles private_key.pem > private_key.jwk
$ cat private_key.jwk
{
  "kty": "EC",
  "crv": "P-256",
  "d": "wSmbZ2gTKFbyy-q6jXhuGXFuz8mCqjUhRYvQ_EEgqqg",
  "x": "uEHJCsUrgvPWX0Mk1j_jNNUFHCUUUhxv6GIRRFORTqE",
  "y": "IkaDFmAl5QVSCUTceJP80Kx2PwI5YMJOo_0jPagQOfM"
}

ここで、dxy は楕円曲線アルゴリズム特有のパラメーター群です。d は秘密鍵だけに含まれ、xy は秘密鍵と公開鍵の両方に含まれます。上記の JWK から d を削除すると、公開鍵となります。

試しに、PEM 形式の秘密鍵から公開鍵を抜き出し、

$ openssl pkey -pubout -in private_key.pem > public_key.pem
$ cat public_key.pem
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEuEHJCsUrgvPWX0Mk1j/jNNUFHCUU
Uhxv6GIRRFORTqEiRoMWYCXlBVIJRNx4k/zQrHY/Ajlgwk6j/SM9qBA58w==
-----END PUBLIC KEY-----

JWK 形式に変換すると、

$ eckles public_key.pem > public_key.jwk
$ cat public_key.jwk
{
  "kty": "EC",
  "crv": "P-256",
  "x": "uEHJCsUrgvPWX0Mk1j_jNNUFHCUUUhxv6GIRRFORTqE",
  "y": "IkaDFmAl5QVSCUTceJP80Kx2PwI5YMJOo_0jPagQOfM"
}

d が含まれていないことを確認できます。

4.4. 証明書生成

$ openssl req -x509 -key private_key.pem -subj /CN=client.example.com > certificate.pem
  • req -x509 → X.509 証明書を作成します。reqCSR(Certificate Signing Request)を生成するためのサブコマンドですが、-x509 オプションをつけると、CSR ではなく自己署名証明書を生成します。

  • -key private_key.pem → 署名に使う秘密鍵・証明の対象となる公開鍵を指定します。

  • -subj /CN=client.example.com → 主体者識別名を指定します。-subj オプションが指定されていない場合は、主体者識別名を入力するためのプロンプトが表示されます。

  • 有効期限を指定しない場合は、デフォルトの 30 日となります。-days オプションで有効期間の長さを日単位で指定することができます。

上記のコマンドを実行することにより、次のような内容を持つファイルが作成されます。形式は PEM で、BASE64 データが証明書を表していることを示すため、先頭行と最終行にそれぞれ「-----BEGIN CERTIFICATE-----」と「-----END CERTIFICATE-----」が置かれています。

-----BEGIN CERTIFICATE-----
MIIBjzCCATWgAwIBAgIUdRbH+ElI8iLjnR9eJwCpIU8e8xYwCgYIKoZIzj0EAwIw
HTEbMBkGA1UEAwwSY2xpZW50LmV4YW1wbGUuY29tMB4XDTIwMDYyNzExMzgwM1oX
DTIwMDcyNzExMzgwM1owHTEbMBkGA1UEAwwSY2xpZW50LmV4YW1wbGUuY29tMFkw
EwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEuEHJCsUrgvPWX0Mk1j/jNNUFHCUUUhxv
6GIRRFORTqEiRoMWYCXlBVIJRNx4k/zQrHY/Ajlgwk6j/SM9qBA586NTMFEwHQYD
VR0OBBYEFJaMKA22eKiMXGvSojeoLGChcABcMB8GA1UdIwQYMBaAFJaMKA22eKiM
XGvSojeoLGChcABcMA8GA1UdEwEB/wQFMAMBAf8wCgYIKoZIzj0EAwIDSAAwRQIg
N+a6RbvOz+32qOOgKnbQB8sSVeD0gvRoRK13ZuducX4CIQDkgzc1BHoQJ6Pby3aO
byBmhjJS/LhhROgzecP/JMDxqA==
-----END CERTIFICATE-----

4.5. 証明書の内容確認

$ openssl x509 -text -noout -in certificate.pem
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            75:16:c7:f8:49:48:f2:22:e3:9d:1f:5e:27:00:a9:21:4f:1e:f3:16
        Signature Algorithm: ecdsa-with-SHA256
        Issuer: CN = client.example.com
        Validity
            Not Before: Jun 27 11:38:03 2020 GMT
            Not After : Jul 27 11:38:03 2020 GMT
        Subject: CN = client.example.com
        Subject Public Key Info:
            Public Key Algorithm: id-ecPublicKey
                Public-Key: (256 bit)
                pub:
                    04:b8:41:c9:0a:c5:2b:82:f3:d6:5f:43:24:d6:3f:
                    e3:34:d5:05:1c:25:14:52:1c:6f:e8:62:11:44:53:
                    91:4e:a1:22:46:83:16:60:25:e5:05:52:09:44:dc:
                    78:93:fc:d0:ac:76:3f:02:39:60:c2:4e:a3:fd:23:
                    3d:a8:10:39:f3
                ASN1 OID: prime256v1
                NIST CURVE: P-256
        X509v3 extensions:
            X509v3 Subject Key Identifier: 
                96:8C:28:0D:B6:78:A8:8C:5C:6B:D2:A2:37:A8:2C:60:A1:70:00:5C
            X509v3 Authority Key Identifier: 
                keyid:96:8C:28:0D:B6:78:A8:8C:5C:6B:D2:A2:37:A8:2C:60:A1:70:00:5C

            X509v3 Basic Constraints: critical
                CA:TRUE
    Signature Algorithm: ecdsa-with-SHA256
         30:45:02:20:37:e6:ba:45:bb:ce:cf:ed:f6:a8:e3:a0:2a:76:
         d0:07:cb:12:55:e0:f4:82:f4:68:44:ad:77:66:e7:6e:71:7e:
         02:21:00:e4:83:37:35:04:7a:10:27:a3:db:cb:76:8e:6f:20:
         66:86:32:52:fc:b8:61:44:e8:33:79:c3:ff:24:c0:f1:a8
  • x509 → X.509 証明書に関する処理をおこなうためのサブコマンドです。

  • -text → プレインテキストで情報を表示します。

  • -noout → エンコードされた情報の表示を抑制します。

  • -in certificate.pem → 入力ファイルを指定します。

自己署名証明書なので、発行者(Issuer)と主体者(Subject)が同じ値になっています。また、肝心の公開鍵情報は、出力内の Subject Public Key Info に含まれています。

おわりに

X.509 証明書の主な用途は TLS(Transport Layer Security)ですが、他の場所でも使用されます。例えば OAuth の世界では、RFC 8705 により、クライアント認証アクセストークンの PoP(Proof of Possession)の手段として X.509 証明書を利用する方法が定義されています。Authlete 社の OAuth/OIDC 勉強会で X.509 証明書を説明したのは、これが理由です。

技術者の基本として X.509 証明書に関する知識を身につけましょう。『プロフェッショナル SSL/TLS』、超お薦めです!

新規登録して、もっと便利にQiitaを使ってみよう

  1. あなたにマッチした記事をお届けします
  2. 便利な情報をあとで効率的に読み返せます
ログインすると使える機能について
TakahikoKawasaki
株式会社 Authlete の共同創業者。プログラマー兼代表取締役社長。

コメント

この記事にコメントはありません。
あなたもコメントしてみませんか :)
新規登録
すでにアカウントを持っている方はログイン
記事投稿イベント開催中
フロントエンドの開発効率を向上するヒントを教え合おう!
~
PHP強化月間~開発する上で知っておくべき知見を共有しよう~
~
1122
どのような問題がありますか?
新規登録して、Qiitaをもっと便利に使ってみませんか

この機能を利用するにはログインする必要があります。ログインするとさらに下記の機能が使えます。

  1. ユーザーやタグのフォロー機能であなたにマッチした記事をお届け
  2. ストック機能で便利な情報を後から効率的に読み返せる
新規登録ログイン
ストックするカテゴリー