X.509 の証明書の三大構成要素の 3 つ目は「電子署名」
「証明内容」から「署名アルゴリズム」を用いて「電子署名」が得られます
なので、「電子署名」の値は BIT 列になります
署名するには先ず「証明内容」をバイト列に落とし込まないといけません
ASN.1 という形式で定義されていた「証明内容」ですが
これを ASN.1 の符号化方法の 1 つである
DER というエンコード方法でコード化します
DER では BIT 列で「型・長さ・内容」が羅列されます
例えば Linux なんかで OpenSSL が使える場合に
ssl.seesaa.jp の証明書を hex に dump すると
$ openssl s_client -connect ssl.seesaa.jp:443 < /dev/null | openssl x509 -outform der | od -t x1
0000000 30 82 05 26 30 82 04 8f a0 03 02 01 02 02 10 09
0000020 68 23 a2 c6 64 0e 2b 9d 7f 23 15 08 fb db bf 30
0000040 0d 06 09 2a 86 48 86 f7 0d 01 01 05 05 00 30 81
0000060 b9 31 0b 30 09 06 03 55 04 06 13 02 55 53 31 17
.......
0002440 fe 7f a4 47 b1 8d d5 9d f1 dc
0002452
これをインデント付けて更に長さを表現する部分を [] で囲むと
30 82 [05 26]
30 82 [04 8f]
a0 [03]
02 [01] 02
02 [10] 09 68 23 a2 c6 64 0e 2b 9d 7f 23 15 08 fb db bf
30 [0d]
06 [09] 2a 86 48 86 f7 0d 01 01 05
05 [00]
30 81 [b9]
31 [0b]
30 [09]
06 [03] 55 04 06
13 [02] 55 53
31 [17] ........
となりますが
最初からちょっと見てみると
16 進で 30 は 2 進で 00110000 ですが 00 1 10000 と分けます
最初の 2 bits は以下で表現されるもののクラスを表現するタグです
- universal (00) は ASN.1 で一般に定義されてるもの
- application (01) X.500 など特定の application で定義されるもの
- context-specific (10) [0] [1] のように省略できる場合の識別子
- private (11) その他の拡張に用いる??
みたいな感じでしょうか
次の 1 bit はデータの型が 1 つの値か、値の集まりかを表現します
- 0 であれば simple types、整数や文字列など
- 1 であれば structured types、SEQUENCE や SET など中身が複数
次の 5 bits で型が表現されます
10 進で型の番号を幾つか書くと
- 2 -> 整数
- 5 -> Null
- 6 -> OBJECT IDENTIFIER
- 16 -> SEQUENCE
- 17 -> SET
というわけで 00110000 は SEQUENCE だということが分かりました
次の 1 バイトは長さの表現方法を表します
頭の 1 bit が
- 0 であれば続く 7 bit が長さ
- 1 であれば続く 7 bit が長さを表現するバイト列の長さを表現します
16 進の 82 は 2 進で 10000010 なので
頭が 1 ですから続く 0000010 -> 2 バイトが長さを表現することになります
16 進で 0526 は 10 進で 1318 ですから
最初に定義されてる SEQUENCE の中身は 1318 バイトなことが分かります
この SEQUENCE が
rfc 5280 の 4.1 で定義されていた証明書
Certificate ::= SEQUENCE {
tbsCertificate TBSCertificate,
signatureAlgorithm AlgorithmIdentifier,
signatureValue BIT STRING }
の SEQUENCE に対応しています
次が 30 82 [04 8f] ですが、これは TBSCertificate の SEQUENCE
TBSCertificate ::= SEQUENCE {
version [0] EXPLICIT Version DEFAULT v1,
serialNumber CertificateSerialNumber,
signature AlgorithmIdentifier,
issuer Name,
validity Validity,
subject Name,
subjectPublicKeyInfo SubjectPublicKeyInfo,
issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL,
-- If present, version MUST be v2 or v3
subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL,
-- If present, version MUST be v2 or v3
extensions [3] EXPLICIT Extensions OPTIONAL
-- If present, version MUST be v3
}
に対応していて、長さは [04 8f] -> 1167 バイトになります
次の a0 は 2 進で 10 1 00000 これは version の [0] に対応してます
最初の 10 が context-specific で、中身に version が入ってるはずです
実際次の行が 02 [01] 02 ですが
- 02 は 2 進で 00 0 00010 で整数型
- 01 は 2 進で 00000001 で頭が 0 なので 1 バイトの整数であることを
- 02 は中身なので version の数が 2
になりますが
Version ::= INTEGER { v1(0), v2(1), v3(2) }
だったので v3、つまり X.509 バージョン 3 の証明書ということです
次の行の 02 [10] は 上と同じで 16 バイトの整数という意味で
09 68 23 a2 c6 64 0e 2b 9d 7f 23 15 08 fb db bf がシリアルになります
次の行の 30 [0d] は 13 バイトの SEQUENCE で
中身の 1 つめは 06 [09] 2a 86 48 86 f7 0d 01 01 05
型番号 06 は Object Identifier を表現していました
中身を 2 進で書くと
00101010 10000110 01001000 10000110 11110111 00001101 00000001 00000001 00000101
最初の 1 バイトは 42 = 1 x 40 + 2 と読むそうで OID の最初は 1.2.
残りのバイトは
頭が 1 なら残りの 7 bit を続けていって
頭が 0 なら残りの 7 bit で終わりとなります
分かりづらいですね、残りが
10000110 01001000 10000110 11110111 00001101 00000001 00000001 00000101
ですが、これを 0 始まりのもので切っていって
{ 10000110 01001000 } { 10000110 11110111 00001101 } { 00000001 } { 00000001 } { 00000101 }
頭の数字を取っていって
{ 00001101001000 } { 000011011101110001101 } { 0000001 } { 0000001 } { 0000101 }
これを 10 進に直すと
{ 840 } { 113549 } { 1 } { 1 } { 5 }
で、最初のとくっつけて 1.2.840.113549.1.1.5
これは sha-1WithRSAEncryption を指す OID でした
次の行の 05 [00] が署名アルゴリズムの SEQUENCE の 2 成分目で
sha-1WithRSAEncryption は特にオプションが要らないので NULL
型が 05、つまり NULL になっています
という風にバイト列から ASN.1 による定義を見直してみましたが
このように ASN.1 による定義がバイト列に変換されます
そのバイト列を署名アルゴリズムで署名に変換したものが
署名として証明書の最後に記述されることになります