第24回 SSLサーバー証明書のデジタル署名の検証

公開日:2020-01-22 更新日:2020-01-22

1. 動画



2. 概要

SSLサーバー証明書のデジタル署名を公開鍵で復号して、復号したハッシュ値と計算したハッシュ値が一致するのかを検証します。
検証対象は、「第23回 SSLサーバー証明書の独自発行」で作成したファイルです。

3. CSRの検証


CSRの表示
openssl req -text -in web_server_pem.csr -noout

結果
Certificate Request:
    Data:
        Version: 0 (0x0)
        Subject: C=JP, ST=Tokyo, L=Minato-ku, O=TEST Co.Ltd., OU=Sales, CN=test.test
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (2048 bit)
                Modulus:
                    00:ca:2d:7b:ef:bb:3b:cb:07:34:a8:24:61:0d:cb:
                    33:e9:88:0f:2e:c8:07:03:13:e8:e7:74:2b:d1:36:
                    1d:02:62:0e:55:91:63:66:46:2b:11:6c:28:bc:70:
                    6c:01:6e:ec:ce:ac:4a:25:62:80:98:1f:e4:21:9a:
                    59:45:79:c8:1e:03:b7:39:90:d5:5c:6a:1a:19:63:
                    dd:6f:a6:14:2a:56:ec:20:30:97:3a:13:b1:a4:ff:
                    bf:f2:97:7c:90:19:96:77:77:e3:3e:79:1f:db:0a:
                    d2:7a:94:9b:12:a8:5c:55:8e:f2:be:f9:2d:fb:88:
                    a4:17:cc:e2:c5:0c:47:53:1c:e7:43:27:94:1f:1d:
                    9d:96:e8:eb:aa:da:b1:2b:4b:30:3f:0e:e5:86:9f:
                    ad:cf:26:75:78:da:89:41:79:88:50:3b:76:30:f2:
                    0b:db:82:b3:8e:80:97:5c:82:c7:19:a3:a2:b3:11:
                    d8:0d:42:db:cc:e7:31:7c:80:6b:ef:d1:d3:e9:49:
                    9e:e8:5f:54:ed:b8:3b:2e:bd:85:26:c3:0c:18:5f:
                    ae:a5:de:38:61:ee:89:f8:97:de:1e:5b:d6:9b:e8:
                    3f:8d:99:41:27:ab:19:c1:75:16:fa:c4:e1:30:dc:
                    cc:64:9e:ae:86:6c:94:29:b3:54:34:41:a8:5b:1d:
                    bc:b7
                Exponent: 65537 (0x10001)
        Attributes:
            a0:00
    Signature Algorithm: sha256WithRSAEncryption
         60:1d:8e:ed:2c:6a:d6:2b:de:6a:82:dd:dc:59:dd:50:1a:32:
         92:f7:30:06:b3:2b:06:d6:d6:b1:19:9e:cd:42:e2:96:de:30:
         ef:83:ea:9b:7f:7d:c6:ab:9a:31:15:a0:61:9a:23:1e:cd:f5:
         bb:bc:b4:7c:5b:a2:6b:18:8f:6c:6d:b6:67:6f:9f:36:f3:47:
         c7:18:82:43:76:1d:35:88:d0:dc:f7:1b:bd:1a:78:1c:16:16:
         98:bb:4a:0f:9b:ae:8c:a6:bd:cf:b3:65:18:6b:5a:da:18:b1:
         4c:9c:d8:2a:74:f1:17:1a:4a:32:a7:c0:86:a0:75:33:50:fd:
         a9:09:c8:95:d5:d8:47:a0:a5:63:6d:f1:eb:39:2d:cc:f4:e4:
         51:0a:44:42:a5:06:a5:44:0a:36:44:db:48:f9:52:e2:3d:58:
         c0:6e:30:04:2e:84:f9:b2:81:ae:85:ef:bf:19:db:e0:34:42:
         ca:eb:75:7e:95:71:17:32:fd:8a:a3:0e:71:82:b3:da:8d:34:
         12:49:d9:8f:60:0f:5e:cc:d6:4a:38:1b:14:cc:42:4a:67:e1:
         9b:11:b2:5f:71:93:37:a8:2d:a0:2a:ad:bc:a9:bd:35:82:51:
         3c:d2:05:7d:3d:86:d3:da:c4:da:79:b6:7a:d6:55:1f:9f:04:
         7a:a2:42:e8

一番下の Signature Algorithm の下がデジタル署名です。
この16進数の部分をバイナリーエディター等で web_server_csr.signature として保存します。

これは、CSR の情報(署名部分を除く)を sha256 で ハッシュ値にして、
Web サーバ側の秘密鍵でデジタル署名したものです。
そのため、Webサーバ側の公開鍵で復号することができます。

openssl rsautl -verify -in web_server_csr.signature -pubin -inkey web_public_pem.key -out web_server_csr.hash

復号結果(web_server_csr.hash)
30 31 30 0d 06 09 60 86 48 01 65 03 04 02 01 05
00 04 20 fc 7c 74 b5 0e be 64 6c 54 80 7a 69 bf
71 22 a1 db 8e f9 49 4b eb a9 7c 78 48 ab 2a 88
aa 72 c1

復号すると、sha256 のハッシュ値の 32バイト(256ビット) となるはずですが、51バイトあります。
これは、ASN.1 と言う形式で保存されているためです。
ASN.1 形式のデータを表示できる ツール(aatool2) を使って表示すると、
ハッシュ値は以下であることがわかります。これは、復号結果のラスト32バイトと同じです。

ハッシュ値
FC 7C 74 B5 0E BE 64 6C 54 80 7A 69 BF 71 22 A1
DB 8E F9 49 4B EB A9 7C 78 48 AB 2A 88 AA 72 C1

次に、CSR の情報(署名部分を除く)を sha256 でハッシュ値を取得して、
上記の値と一致することを確認します。

まず、前回作成した CSR は、バイナリを Base64 で文字列に変換した PEM と言う形式になっているため、
これを DER と言うバイナリに変換します。

openssl req -inform PEM -in web_server_pem.csr -outform DER -out web_server_der.csr

または、Base64 をデコード
cat web_server_pem.csr | openssl base64 -d > web_server_der.csr

この DER も ASN.1 形式で、先ほどのツールで内容が確認できます。
また、バイナリエディターで開いても確認できますが、DER の最後に署名部分が含まれていることがわかります。
そのため、DER の署名部分の256バイトを削除して、sha256 のハッシュ値を取得して、
上記のハッシュ値となることを確認します。

比較したところ、単純に 256バイトだけ削除しても、上記ハッシュ値にはなりませんでした。
おそらく ASN.1 の構造用データも含まれているためです。
ハッシュの対象を構造用データを除いた純粋なデータのみにしている可能性もありますが、
ダメ元で、先頭と末尾を少しずつ削ってハッシュ値を求めてみます。
手作業でやると大変なため、python を使って調べます。

import hashlib
import os

def check_hash(path, hash):
    with open(path, mode = "rb") as f:
        data = f.read()

    for i in range(0, 500):
        # 先頭の削除
        data2 = data[i:]
        data2_size = len(data2)

        for j in range(0, 500):
            # 末尾の削除
            data3 = data2[:data2_size - j]

            sha256 = hashlib.sha256(data3).hexdigest()

            # ログ出力
            log = '{0} {1} {2} {3}'.format(i, j, sha256, len(data2))
            print(log)

            if (sha256 == hash):
                print('見つかったよ') 
                return


# 比較するDER
path = 'C:/SSL/web_server_der.csr'

# デジタル署名を公開鍵で復号して後半32バイトを取得したもの
hash = 'fc7c74b50ebe646c54807a69bf7122a1db8ef9494beba97c7848ab2a88aa72c1'

# ハッシュ値のチェック
check_hash(path, hash)

実行したところ、一致するハッシュ値が見つかりました。
DER の前半4バイト、後半276バイトを削除してハッシュ値を求めると一致します。

DER から手動で削除して、ハッシュ値を求めたところ一致しました。
cat web_server_der2.csr | openssl sha256

結果
(stdin)= fc7c74b50ebe646c54807a69bf7122a1db8ef9494beba97c7848ab2a88aa72c1

4. SSLサーバー証明書

認証局から返されたSSLサーバー証明書も同様に検証してみます。

SSLサーバー証明書の表示
openssl x509 -in web_server_pem.crt -text -noout

結果
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            a9:74:ce:d7:ca:d1:90:67
    Signature Algorithm: sha256WithRSAEncryption
        Issuer: C=JP, ST=Tokyo, L=Chiyoda-ky, O=TEST CA Co.,Ltd., OU=IT Dept., CN=ca.test
        Validity
            Not Before: Jan 13 01:10:43 2020 GMT
            Not After : Jan 12 01:10:43 2021 GMT
        Subject: C=JP, ST=Tokyo, L=Minato-ku, O=TEST Co.Ltd., OU=Sales, CN=test.test
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (2048 bit)
                Modulus:
                    00:ca:2d:7b:ef:bb:3b:cb:07:34:a8:24:61:0d:cb:
                    33:e9:88:0f:2e:c8:07:03:13:e8:e7:74:2b:d1:36:
                    1d:02:62:0e:55:91:63:66:46:2b:11:6c:28:bc:70:
                    6c:01:6e:ec:ce:ac:4a:25:62:80:98:1f:e4:21:9a:
                    59:45:79:c8:1e:03:b7:39:90:d5:5c:6a:1a:19:63:
                    dd:6f:a6:14:2a:56:ec:20:30:97:3a:13:b1:a4:ff:
                    bf:f2:97:7c:90:19:96:77:77:e3:3e:79:1f:db:0a:
                    d2:7a:94:9b:12:a8:5c:55:8e:f2:be:f9:2d:fb:88:
                    a4:17:cc:e2:c5:0c:47:53:1c:e7:43:27:94:1f:1d:
                    9d:96:e8:eb:aa:da:b1:2b:4b:30:3f:0e:e5:86:9f:
                    ad:cf:26:75:78:da:89:41:79:88:50:3b:76:30:f2:
                    0b:db:82:b3:8e:80:97:5c:82:c7:19:a3:a2:b3:11:
                    d8:0d:42:db:cc:e7:31:7c:80:6b:ef:d1:d3:e9:49:
                    9e:e8:5f:54:ed:b8:3b:2e:bd:85:26:c3:0c:18:5f:
                    ae:a5:de:38:61:ee:89:f8:97:de:1e:5b:d6:9b:e8:
                    3f:8d:99:41:27:ab:19:c1:75:16:fa:c4:e1:30:dc:
                    cc:64:9e:ae:86:6c:94:29:b3:54:34:41:a8:5b:1d:
                    bc:b7
                Exponent: 65537 (0x10001)
        X509v3 extensions:
            X509v3 Authority Key Identifier:
                keyid:DA:A8:44:CC:E6:36:AC:54:13:4D:AA:5E:18:F0:B5:65:7E:A4:42:E1

            X509v3 Basic Constraints:
                CA:FALSE
            X509v3 Key Usage:
                Digital Signature, Non Repudiation, Key Encipherment
            X509v3 Subject Alternative Name:
                DNS:test.test, DNS:www.test.test
    Signature Algorithm: sha256WithRSAEncryption
         1a:e9:eb:79:3c:77:cb:6b:1b:85:b0:97:0d:58:ad:c4:d9:27:
         b3:77:33:be:67:23:47:53:38:36:e8:17:09:c4:8a:bd:e9:bc:
         0c:f0:32:22:d2:2c:83:0b:6d:db:aa:ee:6f:5d:c0:e2:b8:3a:
         bb:2f:4c:af:1f:8c:cf:2f:da:1d:32:8f:20:51:c4:d7:24:5c:
         9e:0f:00:52:91:37:f7:44:76:32:75:25:1c:7a:6a:8e:65:ec:
         04:75:46:f1:e7:76:ea:b7:72:dc:e8:25:76:37:89:65:48:9c:
         fd:11:a2:13:dd:92:14:7c:c4:c2:53:92:0e:12:50:d9:6c:43:
         70:d6:9b:65:92:db:67:19:e0:f0:f7:8c:eb:20:a3:5e:7c:e7:
         4f:b7:5a:1d:24:be:80:05:81:16:16:72:c6:d0:eb:c9:f7:e2:
         b8:95:ec:27:8a:1a:61:b9:c5:10:2c:0a:e0:9e:df:02:df:37:
         ec:92:9d:7d:8a:e1:86:1c:0c:53:fc:60:aa:35:7d:00:bc:92:
         37:4f:f3:1d:b9:5c:35:33:47:21:3f:65:a4:f2:48:b6:b4:c7:
         58:cd:b7:64:bb:c7:be:3d:b6:a7:44:9f:9a:ed:21:08:fc:5c:
         88:99:83:61:84:3b:28:50:f1:3a:29:51:d8:e1:aa:00:d9:af:
         78:57:cf:46

最後のバイナリ(署名)を web_server_pem_crt.signature として保存します。
この署名は認証局によって行われているため、認証局の公開鍵を使って復号します。

openssl rsautl -verify -in web_server_pem_crt.signature -pubin -inkey ca_public_pem.key -out web_server_crt.hash

復号結果
30 31 30 0d 06 09 60 86 48 01 65 03 04 02 01 05
00 04 20 ce 38 54 b4 01 ea ba 6a bb c9 1b 43 01
77 90 0e 27 9b d8 ed 1d 2a b3 ae c1 fe f1 30 a5
0d 81 4c

ASN.1形式のため、最後の32バイトがハッシュ値になります。

ハッシュ値
ce 38 54 b4 01 ea ba 6a bb c9 1b 43 01 77 90 0e 
27 9b d8 ed 1d 2a b3 ae c1 fe f1 30 a5 0d 81 4c

次に、SSLサーバー証明書を PEM から DER に変換します。

openssl x509 -in web_server_pem.crt -inform PEM -out web_server_der.crt -outform DER

そして、先ほどの python のスクリプトで DER(web_server_der.crt)の前後を削りながらハッシュ値を求めると、
復号したハッシュ値と一致することがわかります。

5. 拇印の検証

https://test.test/ にアクセスしてSSLサーバ証明書を表示すると、拇印がありますが、
これがSSLサーバ証明書から求めたハッシュ値と一致するか検証します。

Chrome で確認した拇印
08c77daf72fcc11d1f42ccb0b42208aa037007e5

SSLサーバ証明書をエクスポートします。
Chrome で証明書を表示 -> 詳細タブ -> ファイルにコピーをクリック。
形式は DER encoded binary X.509 にします。
Edge でもエクスポートできます。

cat export.cer | openssl sha256
cat export.cer | openssl sha1

結果
(stdin)= 8558c59adaf386d5caaa9dc4c732829a58e2a28b8172547f32620805fd159bfc
(stdin)= 08c77daf72fcc11d1f42ccb0b42208aa037007e5

sha1 の方と一致しました。
Edge では sha1 と shar256 の両方で確認できます。