ruby の openssl で接続している SSL/TLS のバージョンや cipher や接続先の証明書の情報の取り出し方を確認しました。

確認バージョン

  • ruby 2.5.5, 2.6.3, 2.7.0-Devi

not_after の取得は ruby 1.9.3 以前から使っているので、確認し直していませんが、古いバージョンでも証明書の取り出しは動くと思います。 ただし 1.9.2 より前のバージョンだと ssl.hostname= がないので、そこは動きません。 他の証明書取り出し以外の情報取得部分は古いと動かないかもしれません。

プログラム例

require 'socket'
require 'openssl'

host = ARGV.shift
port = ARGV.shift.to_i

TCPSocket.open(host, port) do |sock|
  ssl = OpenSSL::SSL::SSLSocket.new(sock)
  ssl.sync_close = true
  ssl.hostname = host
  ssl.connect

  p ssl.ssl_version
  p ssl.cipher

  cert = ssl.peer_cert

  ssl.close

  puts cert.to_text
  p cert.not_after
  p cert.issuer
  p cert.subject.to_a.assoc("CN")[1]
end

実行例

$ ruby s.rb blog.n-z.jp 443
"TLSv1.2"
["ECDHE-RSA-AES128-GCM-SHA256", "TLSv1/SSLv3", 128, 128]
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            03:4b:11:45:e5:2b:16:87:fc:6b:97:ee:11:00:d1:14:3e:73
    Signature Algorithm: sha256WithRSAEncryption
        Issuer: C=US, O=Let's Encrypt, CN=Let's Encrypt Authority X3
        Validity
            Not Before: Apr 20 04:10:16 2019 GMT
            Not After : Jul 19 04:10:16 2019 GMT
        Subject: CN=blog.n-z.jp
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (2048 bit)
                Modulus:
                    00:b3:d7:f3:8e:f4:69:9a:6a:80:14:e3:dc:f5:e6:
                    e4:56:9e:fc:db:72:c9:93:e9:49:0b:c7:46:b9:ca:
                    e7:9a:21:ba:e2:e0:b3:7d:5c:dd:7c:fd:22:c2:e1:
                    f7:69:f0:f2:bf:ce:5c:1b:ca:9a:5a:72:19:9d:25:
                    99:00:f6:0d:6b:ee:40:86:ff:82:01:d7:f2:1f:16:
                    36:2f:89:85:78:28:fc:15:41:f3:fb:e8:b5:2c:33:
                    5c:6e:60:4f:13:c7:bd:10:fe:16:21:7c:ff:2e:f9:
                    fe:5e:5f:73:43:a8:38:25:94:23:e0:fb:df:cb:4c:
                    ff:26:48:6a:51:3d:e0:b2:71:67:b5:cd:8d:de:f9:
                    cc:d9:31:d8:ee:43:c8:78:46:8f:11:1e:9e:72:84:
                    2d:78:43:a0:8a:37:5e:48:53:cd:e3:c9:ec:04:fd:
                    28:37:da:d4:2a:17:53:84:ed:17:ac:3e:49:b9:3a:
                    42:b9:fb:90:44:43:73:86:dd:0a:c0:8a:85:ae:67:
                    5c:fa:9e:e8:94:a4:fd:09:dd:d5:39:0d:06:3a:82:
                    e2:12:5a:d5:37:26:62:86:98:56:c9:1d:e7:64:16:
                    e6:43:82:3b:31:bc:10:bb:6f:b6:80:65:9c:46:74:
                    d6:c7:24:5b:02:41:7b:cd:84:1e:90:86:3a:32:c5:
                    ee:6b
                Exponent: 65537 (0x10001)
        X509v3 extensions:
            X509v3 Key Usage: critical
                Digital Signature, Key Encipherment
            X509v3 Extended Key Usage:
                TLS Web Server Authentication, TLS Web Client Authentication
            X509v3 Basic Constraints: critical
                CA:FALSE
            X509v3 Subject Key Identifier:
                47:17:D2:64:17:85:F4:76:13:13:43:FB:BC:E9:5B:1B:CC:6E:3C:FB
            X509v3 Authority Key Identifier:
                keyid:A8:4A:6A:63:04:7D:DD:BA:E6:D1:39:B7:A6:45:65:EF:F3:A8:EC:A1

            Authority Information Access:
                OCSP - URI:http://ocsp.int-x3.letsencrypt.org
                CA Issuers - URI:http://cert.int-x3.letsencrypt.org/

            X509v3 Subject Alternative Name:
                DNS:blog.n-z.jp
            X509v3 Certificate Policies:
                Policy: 2.23.140.1.2.1
                Policy: 1.3.6.1.4.1.44947.1.1.1
                  CPS: http://cps.letsencrypt.org

            CT Precertificate SCTs:
                Signed Certificate Timestamp:
                    Version   : v1(0)
                    Log ID    : 74:7E:DA:83:31:AD:33:10:91:21:9C:CE:25:4F:42:70:
                                C2:BF:FD:5E:42:20:08:C6:37:35:79:E6:10:7B:CC:56
                    Timestamp : Apr 20 05:10:16.095 2019 GMT
                    Extensions: none
                    Signature : ecdsa-with-SHA256
                                30:45:02:20:40:19:56:A0:EF:65:15:65:22:9F:32:95:
                                32:59:FA:87:F8:D3:44:8C:0E:09:E3:81:28:9B:5F:D0:
                                E4:16:ED:CF:02:21:00:AB:81:D9:7A:01:0F:21:38:31:
                                DD:12:37:E4:5E:49:9D:72:4B:FA:68:E3:16:07:69:D1:
                                EC:21:D5:E1:CC:6D:46
                Signed Certificate Timestamp:
                    Version   : v1(0)
                    Log ID    : 63:F2:DB:CD:E8:3B:CC:2C:CF:0B:72:84:27:57:6B:33:
                                A4:8D:61:77:8F:BD:75:A6:38:B1:C7:68:54:4B:D8:8D
                    Timestamp : Apr 20 05:10:16.186 2019 GMT
                    Extensions: none
                    Signature : ecdsa-with-SHA256
                                30:46:02:21:00:D3:D4:44:14:2D:CC:7D:86:B7:60:7E:
                                9B:B2:C5:EB:EA:23:13:DC:E7:61:3B:48:86:A2:38:F3:
                                B2:35:6A:53:C4:02:21:00:E6:03:30:CC:67:86:39:7B:
                                29:03:CC:94:4C:C5:25:4F:F0:C3:B8:36:1A:32:CD:BA:
                                F1:AC:7B:6E:A9:4E:2D:63
    Signature Algorithm: sha256WithRSAEncryption
         91:92:0c:c9:24:e5:cb:c8:73:c1:ce:6b:05:ce:56:e7:33:7a:
         e2:a0:af:3d:eb:e9:30:e6:75:42:0c:ed:3f:2a:97:27:7e:4b:
         61:91:a0:8d:de:d8:a4:9a:e2:0d:48:4e:53:38:8e:59:e3:a7:
         95:d0:71:46:47:10:a9:94:bb:8e:da:56:ba:47:a4:29:dd:47:
         3c:cd:f5:55:1e:fc:22:ea:37:da:94:ee:ca:37:c7:8a:db:f5:
         ba:04:28:17:c7:f2:e0:d3:f9:f4:0c:14:bc:48:05:52:ee:4a:
         21:a2:18:ca:e1:e2:53:9d:99:aa:34:0f:02:fd:9d:b5:d3:4b:
         41:9a:1c:96:88:4e:a8:37:d7:96:63:9d:64:c2:d2:3d:e7:4c:
         fc:fc:88:d1:53:ec:d3:77:d8:34:5a:48:9a:78:d0:66:a3:c9:
         6d:d5:13:e3:e1:d8:2b:66:bd:b0:66:75:c9:f1:f1:db:d6:41:
         88:e8:86:9b:2a:0a:d6:ef:9c:5b:a1:db:67:df:e7:16:4c:05:
         aa:17:4e:8b:40:d1:52:45:d2:ac:5b:e9:5f:55:52:98:70:d6:
         c4:3a:a2:89:84:cd:83:f8:c7:6d:11:db:81:74:d6:9e:40:a4:
         50:a7:ff:4c:80:7d:21:e9:ac:3f:ea:74:a1:a1:6c:87:f9:0b:
         c7:ab:da:de
2019-07-19 04:10:16 UTC
#<OpenSSL::X509::Name CN=Let's Encrypt Authority X3,O=Let's Encrypt,C=US>
"blog.n-z.jp"

解説

接続

まず TCPSocket を接続して OpenSSL::SSL::SSLSocket.new を作成しています。

SSLSocket を閉じた時に TCPSocket も閉じるように sync_close= で設定しています。 証明書の情報を表示するタイミングではソケットは不要なので、 peer_cert で取得した直後に ssl.close して閉じています。

hostname= で TLS の Server Name Indication(SNI) 拡張のためにホスト名の設定をしています。 昔は設定しなくても大丈夫なことが多かったですが、今はちゃんと設定しないと繋ぎたいホストの証明書にならないことが多いと思います。

ssl_version

ssl_versionSSL_get_version に相当し、 今だとほとんど "TLSv1.2" という文字列が返ってきます。

https://badssl.com/ を使って ruby s.rb tls-v1-0.badssl.com 1010ruby s.rb tls-v1-1.badssl.com 1011 だと "TLSv1""TLSv1.1" になるのを確認できました。

cipher

cipherSSL_get_current_cipher に相当し、 nil または実際に使われている cipher を表す配列が返ってきます。

["ECDHE-RSA-AES128-GCM-SHA256", "TLSv1/SSLv3", 128, 128]"TLSv1/SSLv3" は該当する cipher に最初に対応した SSL/TLS のバージョンで、今接続に使われている SSL/TLS のバージョンではないようです。

cipher の詳細は openssl ciphers -v で表示できます。

$ openssl ciphers -v | grep ECDHE-RSA-AES128-GCM-SHA256
ECDHE-RSA-AES128-GCM-SHA256 TLSv1.2 Kx=ECDH     Au=RSA  Enc=AESGCM(128) Mac=AEAD

peer_cert

サーバーの証明書は peer_cert で取得できます。 中間証明書もほしいときは peer_cert_chain で取得できます。 知らないと間違えて使ってしまいそうな cert は、クライアント証明書を取得するメソッドで、クライアント証明書は使わないことが多いので、 nil が返ってくることがほとんどだと思います。

証明書の openssl crt -in some.pem -noout -text 相当のまとまった情報は to_text で取得できて、各種情報は情報から連想できるメソッド名で取得できます。

最後に

OpenSSL::SSL::SSLSocket から SSL/TLS の接続情報を取得する方法を紹介しました。

接続だけして切断すると攻撃とみなされてブロックされる可能性があるので、実際には他の通信のソケットを調べたいことが多いと思います。

たとえば Net::HTTP を直接使っていたり、 open-uri のように下回りに Net::HTTP を使っていたりする場合は、

Net::HTTP.prepend Module.new { def do_finish; p @socket.io.ssl_version; super; end }

のように適当なところに割り込ませることで情報を取得できるので、必要に応じてやってみると良いと思います。 (上の例は http も混ざると ssl_versionNoMethodError になるので、あくまでも割り込み場所の例です。またバージョンによっても変わる可能性があります。)

Disqus Comments

Kazuhiro NISHIYAMA

Ruby のコミッターとかやってます。 フルスタックエンジニア(って何?)かもしれません。 About znzに主なアカウントをまとめました。

znz znz


Published