はじめに
HTTPSの仕組みを勉強する過程でX.509証明書を勉強した際のメモです。
結構間違ってるかもしれないので、間違っていたら教えてください。
X.509の署名済み証明書
X.509の署名済み証明書は、すごい省くと次のもので構成される。
- 署名前証明書(公開鍵やドメイン名、上のCAのIDなどの情報=コンテンツ)
- 認証局(CA1とする)が作ってくれた署名(要するに署名前証明書をHash化したものをCA1の秘密鍵で暗号化したもの)
サーバ証明書の検証(chain of trust)
まず証明書の信頼チェーンがある。この信頼のチェーンを辿って行って、最後にクライアントが元々信頼しているところにたどり着けば信頼できるということになる。
今回は、Root=>CA2=>CA1=>サーバ証明書という信頼の鎖(chain of trust)があるとする。Rootというのはブラウザをインストールした時に元々持ってる証明書で、これは信頼済み。
さて、CA1の署名というのも単なる文字列で、署名前証明書のHash値を認証局(CA1)(の証明書)の秘密鍵で暗号化したものである。
そう、CA1、CA2、Rootと全て登場人物はあくまで証明書なのである。
さあ、サーバ証明書、CA1、CA2をもらったとして、サーバ証明書が間違っていないかを調べる手順を見て行こう。
間違っている可能性というのはいくつかあるが、ここでは「1.改ざんされていないか」「2.確かに現在やりとりしている相手はそのサーバ証明書の持ち主か?(他のサイトの証明書はいつでも簡単にとってこられる)」とする。
1. 署名を検証して、Root証明書までたどり着くか?
2. 本来の持ち主しか知り得ない秘密鍵をもっているか?
で判断できるみたい。
順番にみていく。
まず、CA1の証明書に入っている公開鍵で、サーバ証明書の署名を複合する。
ちゃんと復号できてサーバ証明書の署名前証明書のハッシュ値(クライアントが計算する)と一致したら、それはCA1の秘密鍵でないと作れない署名なので、CA1さえ信頼できればサーバ証明書は信頼できるということになる。
さて、CA1の証明書が正しい場合、CA2からの署名をもらっている。
つまり、CA1の署名前証明書の内容のHash値を、ちゃんとCA2の秘密鍵を利用して暗号化したものをもらっている。
もしCA1に書かれた(CA2からもらった)署名をCA2の証明書の公開鍵で複合してCA1の署名前証明書のHash値と一致すれば、CA2が信頼できればCA1も信頼できるということになる。
ここで、CA2もRoot証明書から署名をもらっている。
ブラウザはRoot証明書を最初から信頼している。そのRoot証明書の公開鍵でCA2がRootからもらった署名を複合でき、かつ確かにCA2の署名前証明書のHash値と一致すれば、CA2は信頼できるということになる。
結局、CA1、サーバ証明書と全て信頼できることがわかる。
補足1.証明書ってどんな感じ?
登場人物の見た目はこんな感じ(wikipediaより)。
署名前証明書のHash値
Signature Algorithm: md5WithRSAEncryption
93:5f:8f:5f:c5:af:bf:0a:ab:a5:6d:fb:24:5f:b6:59:5d:9d:
92:2e:4a:1b:8b:ac:7d:99:17:5d:cd:19:f6:ad:ef:63:2f:92:
ab:2f:4b:cf:0a:13:90:ee:2c:0e:43:03:be:f6:ea:8e:9c:67:
d0:a2:40:03:f7:ef:6a:15:09:79:a9:46:ed:b7:16:1b:41:72:
0d:19:aa:ad:dd:9a:df:ab:97:50:65:f5:5e:85:a6:ef:19:d1:
5a:de:9d:ea:63:cd:cb:cc:6d:5d:01:85:b5:6d:c8:f3:d9:f7:
8f:0e:fc:ba:1f:34:e9:96:6e:6c:cf:f2:ef:9b:bf:de:b5:22:
68:9f
公開鍵
RSA Public Key: (1024 bit)
Modulus (1024 bit):
00:b4:31:98:0a:c4:bc:62:c1:88:aa:dc:b0:c8:bb:
33:35:19:d5:0c:64:b9:3d:41:b2:96:fc:f3:31:e1:
66:36:d0:8e:56:12:44:ba:75:eb:e8:1c:9c:5b:66:
70:33:52:14:c9:ec:4f:91:51:70:39:de:53:85:17:
16:94:6e:ee:f4:d5:6f:d5:ca:b3:47:5e:1b:0c:7b:
c5:cc:2b:6b:c1:90:c3:16:31:0d:bf:7a:c7:47:77:
8f:a0:21:c7:4c:d0:16:65:00:c1:0f:d7:b8:80:e3:
d2:75:6b:c1:ea:9e:5c:5c:ea:7d:c1:a1:10:bc:b8:
e8:35:1c:9e:27:52:7e:41:8f
Exponent: 65537 (0x10001)
補足2. URLもあってるけどアクセスしてるサイトが間違ってたら?
説明していなかった証明書が間違っていないか?の2の部分。
たとえば https://facebook.com
にアクセスしたとき、証明書をもらえば相手がfacebookだということが分かる。それは良いのだけど、 https://facebook.com
に対してアクセスしてるつもりなのに、DNSが書き換えられて実は本来の https://facebook.com
にアクセスできていない(しかしURLは https://faceboo.com
)場合、 https://facebook.com
のSSL証明書を使えば、もしかして詐称できちゃうのでは?そう思っていたが、
https://security.stackexchange.com/questions/36363/can-attackers-steal-ssl-certificate-from-server-and-use-it-for-mitm-attacks を読んでわかった。つまり、秘密鍵は盗まれない前提なのでOK。確かに言われてみればSSL証明書のchain of trustの検証手順はうまく行くかもしれませんが、その後クライアントから送ってもらう(サーバ証明書の公開鍵で暗号化された)共通鍵(のタネ)を復号出来ないのでそのタイミングで嘘がばれそうですね。
補足3. 証明書を送ってもらう途中にどの部分でもすり替えられていたらやっぱり気づく?
考えて見たのですがやはりよく出来ており、気づかれます。例えば、署名前の証明書部分を書き換えてたら、Hash値と比較するとやはり異なるので気づきます。それではHash値部分も一緒に書き換えたら?それも無理です、なぜなら署名は署名前証明書をHash化した後にCAの秘密鍵で暗号化していますが、このCAの秘密鍵が手に入りません。
補足4. なんで署名ってHash化したものを暗号化したものなの?
あまり気にしていませんが。
Hash化しなくても良いんじゃないか。なんならHash化しなければCA1署名を復号したらもう署名前証明書のデータが入ってるわけだから証明書に署名とは別に署名前証明書入れなくて良い分(CA1にたくさん仕事させる分)簡単なのではないか。それに署名前証明書の部分がめちゃ長くなって、その結果として、「署名を複合してコンテンツが改ざんされていないか調べる」部分も複合した瞬間にもうコンテンツ入ってるから楽なんじゃないのかという疑問があります。大きなデータを暗号化/復号化するのは大変なのでしょうか。参考URLなどあれば教えていただければと思います。