SSL証明書をちゃんと理解する

今までSSLの証明書の仕組みについて、なんとなく、上位の認証局を使ってサーバの証明をしてるんだな〜程度にしか理解できていなかった。そのため、自己証明書を作る手順などは適当にぐぐってきたものをそのまま使っていて、ちゃんと理解できていない。

今日は、SSL証明書について実践しながら仕組みをちゃんと理解したいと思う。

SSL証明書の基礎

SSL証明書には、サーバ証明書とクライアント証明書の2種類が存在するが、一般にWebサーバの通信を暗号化する際にはサーバ証明書が用いられる。今回の記事では、SSLサーバ証明書のことをSSL証明書と書くことにする。(クライアントのことは今回は書かないため)

サイトの実在証明

SSL証明書は、サイトの実在証明と通信の暗号化の2つの目的で利用される。下の画像のように、HTTPS通信の場合には証明書の情報を表示することができる。そこには発行元、発行先の情報が含まれており、「どの組織がどの組織の存在を証明している(認めている)か」がわかるようになっている。

ちなみに、証明書にはDV認証、OV認証、EV認証と3つのレベルがあり、書いた順番により信頼性の高い認証となる(EV認証が最も信頼性が高い)。

https://www.cybertrust.co.jp/blog/ssl/knowledge/ssl-basics.htmlより引用

この実在証明は階層構造になっており、一番上の組織はルート認証局と呼ばれる。証明書で証明できるのはあくまで、「〇〇がXXを信頼できると証明してる」ことのみなので、その〇〇(発行元)の信頼がなければその証明は成り立たない。そのため、一般的には権威のあるルート認証局がいくつかの中間認証局を信頼し、その中間認証局が個々のウェブサイトなどの調査をするという形になる。

ほとんどのOSやブラウザにはルート認証局の証明書がプリインストールされており、その証明書を使って中間認証局 → ウェブサイトというように連鎖して確認していく。

通信の暗号化

通信の暗号化は配布されたウェブサイトの公開鍵暗号方式と共通鍵暗号方式の双方を利用して行われる。目的は通信を盗聴されないようにすることだ。

手順としては、サーバ証明書に含まれる公開鍵を使ってクライアント側で共通鍵を生成し、公開鍵で暗号化する。そしてそれをサーバに送信する。その際にもし共通鍵を暗号化されたものを盗聴されても、秘密鍵はサーバしか持っていないので共通鍵はわからない。ここまでを鍵交換という。

その後は共通鍵を使って双方で暗号化・復号化ができるようになるため、他に盗聴されても問題ないようになっている。

ここまでは今までも知っていたが、具体的にどういう処理が行われているかなどが知らない。

SSLサーバ証明書の構造

SSLサーバ証明書はX.509という形式で定められており、発行元、公開鍵、サブジェクトなど様々な情報を含んでいる。その情報は以下のような定義がされている(一部のみ)。この定義はASN.1という記法で書かれ、その定義をさらにDER(Distinguished Encoding Rules)などでバイト列に変換し、さらにBase64によって文字列に変換する。そして、最終的に追加文字列をいれてpemファイルなどになる。

https://qiita.com/TakahikoKawasaki/items/4c35ac38c52978805c69より引用
https://qiita.com/TakahikoKawasaki/items/4c35ac38c52978805c69より引用

いままで少し勘違いをしていた。opensslコマンドなどで確認する情報(下)のすべてをサーバ証明書だと考えていたが、実際には「BEGIN CERTIFICATE」から「END CERTIFICATE」までが証明書の実態で、その中にBase64で変換されたバイト列として発行元などの情報が含まれている。

Connecting to 2606:4700::6812:25b
CONNECTED(00000003)
depth=2 OU=GlobalSign Root CA - R3, O=GlobalSign, CN=GlobalSign
verify return:1
depth=1 C=BE, O=GlobalSign nv-sa, CN=GlobalSign Extended Validation CA - SHA256 - G3
verify return:1
depth=0 businessCategory=Private Organization, serialNumber=578611, jurisdictionC=US, jurisdictionST=New Hampshire, C=US, ST=New Hampshire, L=Portsmouth, street=2 International Drive, Suite 150, O=GMO GlobalSign, Inc., CN=www.globalsign.com
verify return:1
---
Certificate chain
 0 s:businessCategory=Private Organization, serialNumber=578611, jurisdictionC=US, jurisdictionST=New Hampshire, C=US, ST=New Hampshire, L=Portsmouth, street=2 International Drive, Suite 150, O=GMO GlobalSign, Inc., CN=www.globalsign.com
   i:C=BE, O=GlobalSign nv-sa, CN=GlobalSign Extended Validation CA - SHA256 - G3
   a:PKEY: rsaEncryption, 2048 (bit); sigalg: RSA-SHA256
   v:NotBefore: Jun 20 15:02:03 2024 GMT; NotAfter: Nov  4 16:06:07 2024 GMT
-----BEGIN CERTIFICATE-----
...

実際にopensslコマンドを使用して適当なウェブサイトの証明書を確認し、PEM→DER→ASN.1というように変換してみる。対象とするウェブサイトはhttps://jp.globalsign.comとする。

$ openssl s_client -connect jp.globalsign.com:443 -showcerts

Connecting to 2606:4700::6812:25b
CONNECTED(00000003)
depth=2 OU=GlobalSign Root CA - R3, O=GlobalSign, CN=GlobalSign
verify return:1
depth=1 C=BE, O=GlobalSign nv-sa, CN=GlobalSign Extended Validation CA - SHA256 - G3
verify return:1
depth=0 businessCategory=Private Organization, serialNumber=578611, jurisdictionC=US, jurisdictionST=New Hampshire, C=US, ST=New Hampshire, L=Portsmouth, street=2 International Drive, Suite 150, O=GMO GlobalSign, Inc., CN=www.globalsign.com
verify return:1
---
Certificate chain
 0 s:businessCategory=Private Organization, serialNumber=578611, jurisdictionC=US, jurisdictionST=New Hampshire, C=US, ST=New Hampshire, L=Portsmouth, street=2 International Drive, Suite 150, O=GMO GlobalSign, Inc., CN=www.globalsign.com
   i:C=BE, O=GlobalSign nv-sa, CN=GlobalSign Extended Validation CA - SHA256 - G3
   a:PKEY: rsaEncryption, 2048 (bit); sigalg: RSA-SHA256
   v:NotBefore: Jun 20 15:02:03 2024 GMT; NotAfter: Nov  4 16:06:07 2024 GMT
-----BEGIN CERTIFICATE-----
MIIKZzCCCU+gAwIBAgIMSRcQy4RtaRlRu6XoMA0GCSqGSIb3DQEBCwUAMGIxCzAJ
BgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMTgwNgYDVQQDEy9H
bG9iYWxTaWduIEV4dGVuZGVkIFZhbGlkYXRpb24gQ0EgLSBTSEEyNTYgLSBHMzAe
Fw0yNDA2MjAxNTAyMDNaFw0yNDExMDQxNjA2MDdaMIIBBjEdMBsGA1UEDwwUUHJp
dmF0ZSBPcmdhbml6YXRpb24xDzANBgNVBAUTBjU3ODYxMTETMBEGCysGAQQBgjc8
AgEDEwJVUzEeMBwGCysGAQQBgjc8AgECEw1OZXcgSGFtcHNoaXJlMQswCQYDVQQG
...

上記の「-----BEGIN CERTIFICATE-----」からの文字列をPEMファイルとしてローカルの適当なファイルに保存する。そのファイルに対して、さらにopensslのコマンドでPEM→DER→ASN.1というように変換して中身を確認する。

$ openssl x509 -in test_cert.pem -outform DER -out test_cert.der
$ openssl asn1parse -in test_cert.der -inform DER
    0:d=0  hl=4 l=2663 cons: SEQUENCE
    4:d=1  hl=4 l=2383 cons: SEQUENCE
    8:d=2  hl=2 l=   3 cons: cont [ 0 ]
   10:d=3  hl=2 l=   1 prim: INTEGER           :02
   13:d=2  hl=2 l=  12 prim: INTEGER           :491710CB846D691951BBA5E8
   27:d=2  hl=2 l=  13 cons: SEQUENCE
   29:d=3  hl=2 l=   9 prim: OBJECT            :sha256WithRSAEncryption
   40:d=3  hl=2 l=   0 prim: NULL
   42:d=2  hl=2 l=  98 cons: SEQUENCE
   44:d=3  hl=2 l=  11 cons: SET
   46:d=4  hl=2 l=   9 cons: SEQUENCE
   48:d=5  hl=2 l=   3 prim: OBJECT            :countryName
   53:d=5  hl=2 l=   2 prim: PRINTABLESTRING   :BE
   57:d=3  hl=2 l=  25 cons: SET
   59:d=4  hl=2 l=  23 cons: SEQUENCE
   61:d=5  hl=2 l=   3 prim: OBJECT            :organizationName
   66:d=5  hl=2 l=  16 prim: PRINTABLESTRING   :GlobalSign nv-sa
   84:d=3  hl=2 l=  56 cons: SET
   86:d=4  hl=2 l=  54 cons: SEQUENCE
   88:d=5  hl=2 l=   3 prim: OBJECT            :commonName
   93:d=5  hl=2 l=  47 prim: PRINTABLESTRING   :GlobalSign Extended Validation CA - SHA256 - G3
...

ASN.1の形式で表示することができた。

証明書の検証(信頼チェーン)

証明書の構造が確認できたところで、実際に証明書の信頼チェーンが動作しているかどうかを確認してみる。使うのはopensslのs_client -connectで、ルート認証局の証明書を指定して検証が成功するか確かめる。

$ openssl s_client -connect jp.globalsign.com:443 -CAfile /etc/ssl/certs/GlobalSign_Root_CA_-_R3.pem

Connecting to 104.18.3.91
CONNECTED(00000003)
depth=2 OU=GlobalSign Root CA - R3, O=GlobalSign, CN=GlobalSign
verify return:1
depth=1 C=BE, O=GlobalSign nv-sa, CN=GlobalSign Extended Validation CA - SHA256 - G3
verify return:1
depth=0 businessCategory=Private Organization, serialNumber=578611, jurisdictionC=US, jurisdictionST=New Hampshire, C=US, ST=New Hampshire, L=Portsmouth, street=2 International Drive, Suite 150, O=GMO GlobalSign, Inc., CN=www.globalsign.com
verify return:1
---
Certificate chain
 0 s:businessCategory=Private Organization, serialNumber=578611, jurisdictionC=US, jurisdictionST=New Hampshire, C=US, ST=New Hampshire, L=Portsmouth, street=2 International Drive, Suite 150, O=GMO GlobalSign, Inc., CN=www.globalsign.com
   i:C=BE, O=GlobalSign nv-sa, CN=GlobalSign Extended Validation CA - SHA256 - G3
   a:PKEY: rsaEncryption, 2048 (bit); sigalg: RSA-SHA256
   v:NotBefore: Jun 20 15:02:03 2024 GMT; NotAfter: Nov  4 16:06:07 2024 GMT
 1 s:C=BE, O=GlobalSign nv-sa, CN=GlobalSign Extended Validation CA - SHA256 - G3
   i:OU=GlobalSign Root CA - R3, O=GlobalSign, CN=GlobalSign
   a:PKEY: rsaEncryption, 2048 (bit); sigalg: RSA-SHA256
   v:NotBefore: Sep 21 00:00:00 2016 GMT; NotAfter: Sep 21 00:00:00 2026 GMT
 2 s:OU=GlobalSign Root CA - R3, O=GlobalSign, CN=GlobalSign
   i:C=BE, O=GlobalSign nv-sa, OU=Root CA, CN=GlobalSign Root CA
   a:PKEY: rsaEncryption, 2048 (bit); sigalg: RSA-SHA256
   v:NotBefore: Sep 19 00:00:00 2018 GMT; NotAfter: Jan 28 12:00:00 2028 GMT
....
SSL handshake has read 5402 bytes and written 405 bytes
Verification: OK
...

正しくGlobalSignのルート認証局の証明書を使うと、上記のように「Verification: OK」という結果が帰ってくる。これを別のルート認証局の証明書に変えてみるとこうなる。

$ openssl s_client -connect jp.globalsign.com:443 -CAfile /etc/ssl/certs/Amazon_Root_CA_1.pem

Connecting to 2606:4700::6812:35b
CONNECTED(00000003)
depth=2 OU=GlobalSign Root CA - R3, O=GlobalSign, CN=GlobalSign
verify error:num=20:unable to get local issuer certificate
verify return:1
depth=1 C=BE, O=GlobalSign nv-sa, CN=GlobalSign Extended Validation CA - SHA256 - G3
verify return:1
depth=0 businessCategory=Private Organization, serialNumber=578611, jurisdictionC=US, jurisdictionST=New Hampshire, C=US, ST=New Hampshire, L=Portsmouth, street=2 International Drive, Suite 150, O=GMO GlobalSign, Inc., CN=www.globalsign.com
verify return:1
---
Certificate chain
 0 s:businessCategory=Private Organization, serialNumber=578611, jurisdictionC=US, jurisdictionST=New Hampshire, C=US, ST=New Hampshire, L=Portsmouth, street=2 International Drive, Suite 150, O=GMO GlobalSign, Inc., CN=www.globalsign.com
   i:C=BE, O=GlobalSign nv-sa, CN=GlobalSign Extended Validation CA - SHA256 - G3
   a:PKEY: rsaEncryption, 2048 (bit); sigalg: RSA-SHA256
   v:NotBefore: Jun 20 15:02:03 2024 GMT; NotAfter: Nov  4 16:06:07 2024 GMT
 1 s:C=BE, O=GlobalSign nv-sa, CN=GlobalSign Extended Validation CA - SHA256 - G3
   i:OU=GlobalSign Root CA - R3, O=GlobalSign, CN=GlobalSign
   a:PKEY: rsaEncryption, 2048 (bit); sigalg: RSA-SHA256
   v:NotBefore: Sep 21 00:00:00 2016 GMT; NotAfter: Sep 21 00:00:00 2026 GMT
 2 s:OU=GlobalSign Root CA - R3, O=GlobalSign, CN=GlobalSign
   i:C=BE, O=GlobalSign nv-sa, OU=Root CA, CN=GlobalSign Root CA
   a:PKEY: rsaEncryption, 2048 (bit); sigalg: RSA-SHA256
   v:NotBefore: Sep 19 00:00:00 2018 GMT; NotAfter: Jan 28 12:00:00 2028 GMT
...
SSL handshake has read 5402 bytes and written 405 bytes
Verification error: unable to get local issuer certificate
...

そうすると「Verification error: unable to get local issuer certificate」というエラーが出てくる。これは、信頼性を確認するためのルート証明書が見つからなかったというもので、-CAfileによって指定している証明書が正しいものではないからだ。

自己証明書

では、自己証明書はどういう仕組みになっているのか、ハンズオン形式で自己証明書を作りながら知って行こうと思う。

まずは証明書を作るもととなる秘密鍵を作っていく。ファイル名は適当にmyserver.keyとして、RSA暗号で2048ビットのものにする。

openssl genrsa -out myserver.key 2048

次に、秘密鍵を元にして証明書著名要求(CSR:Certificate Signing Request)を作成する。この証明書著名要求は本来であれば認証局へ要求を出し、その認証局が著名をしてくれる。

openssl req -new -key myserver.key -out myserver.csr

上記コマンドを実行するといくつか質問をされるが、適当に答えてみる。

  • Country Name:JP

  • State or Province Name:Tokyo

  • Locality Name:Hoge

  • Organization Name:Test Ltd

  • Organization Unit Name:Fuga

  • Common Name:example.com

  • Email Address:testdayo@gmail.com

次に、自己証明書なので自身でこの証明書署名要求を受けて著名していく。署名には証明書を作ったのと同じ秘密鍵を利用して行うため、実質的になんの証明にもなっていないが、とりあえずHTTPSを有効化したい場合は重宝する。

$ openssl x509 -req -days 365 -in myserver.csr -signkey myserver.key -out myserver.crt
Certificate request self-signature ok
subject=C=JP, ST=Tokyo, L=Hoge, O=Test Ltd, OU=Fuga, CN=example.com, emailAddress=testdayo@gmail.com

最後に証明書の中身を確認してみると、以下のようになっている。

$ openssl x509 -in myserver.crt -noout -text
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            36:14:92:92:66:1e:15:b5:8f:01:05:f5:f6:04:4c:78:d1:b2:f9:bd
        Signature Algorithm: sha256WithRSAEncryption
        Issuer: C=JP, ST=Tokyo, L=Hoge, O=Test Ltd, OU=Fuga, CN=example.com, emailAddress=testdayo@gmail.com
        Validity
            Not Before: Sep 26 10:18:54 2024 GMT
            Not After : Sep 26 10:18:54 2025 GMT
        Subject: C=JP, ST=Tokyo, L=Hoge, O=Test Ltd, OU=Fuga, CN=example.com, emailAddress=testdayo@gmail.com
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (2048 bit)
                Modulus:
...
                    c1:f1
                Exponent: 65537 (0x10001)
        X509v3 extensions:
            X509v3 Subject Key Identifier:
                E9:C4:50:D2:73:72:E2:5F:F9:90:51:99:7C:BC:CD:2F:D0:89:C3:8C
    Signature Algorithm: sha256WithRSAEncryption
    Signature Value:
...

自己証明書の作成のため、発行者(Issuer)と発行先(Subject)が同一のものになっている。本来であれば、発行者は上位の認証局(中間・ルート)になる。

参考文献

  1. SSL/TLS サーバー証明書の基礎知識 - cybertrust

  2. SSLサーバ証明書とは - GlobalSign

  3. 図解 X.509 証明書 - Qiita


この記事が気に入ったらサポートをしてみませんか?