はじめに
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. デジタル署名(前提知識)
この記事を読んでいただくにあたり、デジタル署名に関する知識が必要となります。つまり、「秘密鍵を用いて生成された署名を公開鍵で検証することにより」、「対象データが改竄されていないこと」や「秘密鍵の保持者が確かに署名したこと」を確認できる、といった話を理解されていることが前提となります。
図にしますと、次のような流れをご存知であることが前提となります。
2. 証明書チェーン
公開鍵暗号を活用するため、まず、秘密鍵と公開鍵のペアを作成します。
しかし、受け取り方にもよりますが、その公開鍵が本物かどうか確信が持てないとします。
そこに第三者が現れ、当該公開鍵が正しいことを証明すると言います。
その第三者は、証明するための書類、すなわち証明書の準備を始めます。
次に、その公開鍵に対応する秘密鍵を持っている人(以降主体者)の情報を載せます。
最後に、この証明書の内容を保証するため、デジタル署名をつけることにします。
それから、秘密鍵を用いてデジタル署名をおこないます。これで証明書の完成です。
公開鍵を直接渡す代わりに、その公開鍵を含み、その出所を証明する証明書を渡します。
これにより、元々欲しかった公開鍵(図中の「A の公開鍵」)は証明付きで入手できました。しかし今度は、証明書のデジタル署名を検証するための公開鍵(図中の「B の公開鍵」)が必要になってしまいます。
そこに別の第三者が現れ、デジタル署名検証に必要な公開鍵のための証明書を提供すると言います。その第三者は証明書の準備を始めます。
最後に、この証明書の内容を保証するため、デジタル署名をつけることにします。
それから、秘密鍵を用いてデジタル署名をおこないます。これで証明書の完成です。
しかしここで、先ほどと同じ問題が発生します。新たに受け取った証明書のデジタル署名を検証するための公開鍵(図中の「C の公開鍵」)が必要になってしまいます。
キリがないように思えますが、今度は、公開鍵の証明書を公開鍵の主体者本人(図中の C)が作成すると言い、証明書の準備を始めます。
最後にデジタル署名を付けますが、用いる秘密鍵は、証明対象の公開鍵とペアになっている秘密鍵です。このように、証明対象となる公開鍵とペアになっている秘密鍵を用いて署名することを、自己署名と言います。そして、自己署名によって作成された証明書を自己署名証明書と言います。自己署名証明書では、発行者と主体者が同じになります。
自己署名証明書は起点となる証明書なので、信頼済みの証明書としてあらかじめ持っておく必要があります。
これらの証明書は鎖のように繋がっています。このように連なった証明書群全体を証明書チェーン(certificate chain)と呼びます。
そして、起点となる自己署名証明書をルート証明書(root certificate)、中間にある証明書を中間証明書(intermediate certificate)と呼びます。中間証明書の数は 2 以上になりえます。
3. 証明書の構造
証明書の構造は RFC 5280 という技術文書で定義されています。X.509 証明書という名前は、同技術文書のタイトル「Internet X.509 Public Key Infrastructure Certificate and Certificate Revocation List (CRL) Profile」から来ています。
下記は、RFC 5280 の Section 4.1 から抜粋した、証明書構造定義の一部です。
この構造定義は ASN.1 (Abstract Syntax Notation One) という記法を用いて書かれています(ASN.1 関連仕様群は X.680 シリーズとして定義されています)。ASN.1 はデータ構造を抽象的に表現するための記法なので、ASN.1 による定義だけでは具体的にどのようなバイト配列にデータを落とし込めばいいのかは定まりません。
そのため、ASN.1 のデータ構造を具体的なバイト配列に落とし込むための技術仕様が別途必要になります。そのような技術仕様は、DER(Distinguished Encoding Rules)や XER(XML Encoding Rules)、JER(JSON Encoding Rules)など、数多く存在します。
具体的にバイト配列に落とし込んだあとに、データ形式を変換することもあるでしょう。例えば、DER はバイナリデータとなるので、これを BASE64(RFC 4648)を用いてテキストデータに変換してもよいでしょう。
場合によっては、データを修飾することもあります。例えば、PEM(RFC 7468)という規則を用いて、BASE64 データが何を表しているかという情報(例えば証明書を表しているという情報)を追加することができます。
ここまでに言及した ASN.1、DER、BASE64、PEM を X.509 証明書に適用してみます。
まず、証明書が持つべき情報(例えば主体者の情報など)を用意します。持つべき情報は RFC 5280 に ASN.1 で表現されています。
次に、これを DER を用いてバイト配列に落とし込みます。出来上がるデータはバイナリデータです。
バイナリデータを BASE64 でテキストデータに変換します。
PEM を用いて情報を追加します。この例では、BASE64 で表現されているデータが証明書なので、前後に「-----BEGIN CERTIFICATE-----
」と「-----END CERTIFICATE-----
」を追加しています。
3.1. subject フィールド
証明書には幾つかフィールドがありますが、データ構造を理解するための例として、ここでは主体者を表す subject フィールドを取り上げます。
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
識別名は「属性=値」の組み合わせで表現されます。上記例の UID
や OU
、CN
などは属性名です。
そして伝統的に、Web サイト用の証明書には、主体者識別名の CN(common name)属性の値としてサーバーのホスト名が含まれます。
具体例を見てみましょう。下記は、Authlete 社の Web サイトの証明書から subject フィールドを抜き出したものです。
このバイナリデータを ASN.1 JavaScript decoder を用いて解析すると、次のような構造を持っていることが分かります。
これにより、subject フィールドが CN=*.authlete.com
という識別名を保持していることが分かります。
3.2. 主体者別名
主体者識別名の CN 属性を使う方法では、ホスト名しか指定できません。また、複数の主体者を含める方法も規定されていません。そこで登場するのが、主体者別名(Subject Alternative Name)拡張です。
ここに、主体者別名拡張を置くことで、ホスト名以外で主体者を指定したり、複数の主体者を並べることができるようになります。主体者別名拡張は、RFC 5280 の Section 4.2.1.6 で次のように定義されています。
具体例を見てみましょう。下記は、Authlete 社の Web サイトの証明書から主体者別名拡張データを抜き出したものです。
このバイナリデータを ASN.1 JavaScript decoder を用いて解析すると、次のような構造を持っていることが分かります。
これにより、主体者別名拡張に *.authlete.com
と authlete.com
という二つの DNS 名が列挙されていることが分かります。
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 の秘密鍵を作成するためのgenrsa
やgendsa
といったサブコマンドもありますが、現在は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
→ 入力ファイルを指定します。
出力例から、秘密鍵が公開鍵の情報を内包していることが分かりますが、これは、鍵を JWK(RFC 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"
}
ここで、d
、x
、y
は楕円曲線アルゴリズム特有のパラメーター群です。d
は秘密鍵だけに含まれ、x
と y
は秘密鍵と公開鍵の両方に含まれます。上記の 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 証明書を作成します。req
は CSR(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』、超お薦めです!