CLOVER🍀

That was when it all began.

openssl s_clientコマンドを使う

これは、なにをしたくて書いたもの?

最近の通信はSSLTLS上で行われることが多くなり、平文のものはあまり見かけなくなってきました。

平文の頃はtelnetコマンドやcurltelnet://プロトコルでのアクセスでいろいろやっていましたが、SSLTLSとなるとちょっと困ります。
こういう時はどうしたら?と思ったのですが、OpenSSLのクライアントコマンドを使うのが良さそうですね。

これからのことを考えると、openssl s_clientにもっと慣れ親しんだ方がいいのかもしれません。

openssl s_client

OpenSSLのオフィシャルサイトはこちら。

OpenSSL

openssl s_clientコマンドのmanページはこちら。

openssl s_client

openssl s_clientは、SSLTLSを使ってリモートホストに接続するための汎用的なSSLTLSクライアントを実装したコマンドです。
SSLサーバーの診断ツールとして便利だ、とされています。

This command implements a generic SSL/TLS client which connects to a remote host using SSL/TLS. It is a very useful diagnostic tool for SSL servers.

今回はこのコマンドを使っていろいろ試してみます。

環境

今回の環境は、こちら。Ubuntu Linux 22.04 LTSです。

$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 22.04.3 LTS
Release:        22.04
Codename:       jammy


$ uname -srvmpio
Linux 5.15.0-83-generic #92-Ubuntu SMP Mon Aug 14 09:30:42 UTC 2023 x86_64 x86_64 x86_64 GNU/Linux

OpenSSLのバージョン。

$ openssl version
OpenSSL 3.0.2 15 Mar 2022 (Library: OpenSSL 3.0.2 15 Mar 2022)

また、Ubuntu Linux 22.04 LTSをもう1台用意して、こちらにSSLTLSを有効化したApacheをインストールしておきます。

$ sudo apt install apache2
$ sudo systemctl enable apache2

SSLTLSを有効化。

$ sudo a2enmod ssl
$ sudo a2ensite default-ssl
$ sudo systemctl restart apache2

このApacheが稼働するサーバーのIPアドレスは、192.168.33.10とします。

ヘルプを見る

なにはともあれ、まずはヘルプから。

$ openssl s_client -help

SSLTLSサーバーに接続する

-connectの後に[host:port]形式で、接続先を指定します。

$ openssl s_client -connect [host:port]

HTTPSでアクセスしてみる

HTTPSでアクセスしてみましょう。用意したApacheにアクセスしてみます。

$ openssl s_client -connect 192.168.33.10:443 -crlf

-crlfオプションは、改行をCRLFで送信するものです。

証明書情報などがいろいろ出力されて、入力待ちになります。

CONNECTED(00000003)
Can't use SSL_get_servername
depth=0 CN = ubuntu2204.localdomain
verify error:num=18:self-signed certificate
verify return:1
depth=0 CN = ubuntu2204.localdomain
verify return:1
---
Certificate chain
 0 s:CN = ubuntu2204.localdomain
   i:CN = ubuntu2204.localdomain
   a:PKEY: rsaEncryption, 2048 (bit); sigalg: RSA-SHA256
   v:NotBefore: Sep  9 13:39:03 2023 GMT; NotAfter: Sep  6 13:39:03 2033 GMT
---
Server certificate
-----BEGIN CERTIFICATE-----
MIIDHzCCAgegAwIBAgIUMZMUSvCr6TT7hGfk9OoJGFmmBgAwDQYJKoZIhvcNAQEL
BQAwITEfMB0GA1UEAwwWdWJ1bnR1MjIwNC5sb2NhbGRvbWFpbjAeFw0yMzA5MDkx
MzM5MDNaFw0zMzA5MDYxMzM5MDNaMCExHzAdBgNVBAMMFnVidW50dTIyMDQubG9j
YWxkb21haW4wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCgsQH1T4yX
TpXXNQtC/KuSPpAGk+XpwSsdSQ21MrscFWojw7T+Hv/+tmKNvVI7/4i8w6sf56Jk
tQwFnF+BsaVUwMe/kFRF6A98uUjMWsOmpnP4BP4U43eqaBwQSQG+ylsqSyiwMnM1
qkrGafrc4FAUM5Oi2X+dH9TbzEUJdgijGYEc5L06IozyS/M6hul6jbGupnNxHuKG
EwWj07T77DhL/0oV5asu4S8CuYLn2IGcrqlU58Nqv0lt2n97OnSlcocnFlDcdVc7
4DQ/ThmVsXaZR2CghWdsgQaN5rTM/Fn3XJxnqOp/wBgSVrJAWtKMAaVFJPm0W1KJ
UcaFGJQzHsZ9AgMBAAGjTzBNMAkGA1UdEwQCMAAwIQYDVR0RBBowGIIWdWJ1bnR1
MjIwNC5sb2NhbGRvbWFpbjAdBgNVHQ4EFgQUtgGicseT4uY5IR/zyIkyAfwJJE8w
DQYJKoZIhvcNAQELBQADggEBAHG39cf00tFMNNInCY6Y39H3vjjL8zzvn085jaPs
PVceDWWxHNU1tPlHKgPQVJEvpbd8SX7AG66b0/vvlAOOAE0E8gxrHkbZBQXqMVSN
3ILtYQ6byDk3QazwnPBNLHLG08M5X/ySuBHxDsqx07E2Fm1jTZ/zZBxbMwWyZbKT
jIbMYe5GqiAW4mXwc0uxOQx559jhP/dpO9ncUyg7ScWuZEQeaMJn9q0YiJxSIlKD
Lb3brYuDNq0N4kfXLKKcfd4jcmZsLYo7a+WUO5Sg5ZUxsjOXZEqXxdVfRULnob7C
r/rE3QtTqdOH1hrclCMnKfkxnZju1tk/PSaLTlxtzxNYY/E=
-----END CERTIFICATE-----
subject=CN = ubuntu2204.localdomain
issuer=CN = ubuntu2204.localdomain

〜省略〜

---
read R BLOCK

ちなみに、Apacheの証明書は自己署名証明書なのですが、特にエラーにならないようです。

HTTPリクエストを入力。

GET / HTTP/1.1
Host: 192.168.33.10

レスポンス。

GET / HTTP/1.1
Host: 192.168.33.10

HTTP/1.1 200 OK
Date: Sat, 09 Sep 2023 13:49:48 GMT
Server: Apache/2.4.52 (Ubuntu)
Last-Modified: Sat, 09 Sep 2023 13:39:08 GMT
ETag: "29af-604ed37b097b8"
Accept-Ranges: bytes
Content-Length: 10671
Vary: Accept-Encoding
Content-Type: text/html

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">


〜省略〜

最初の証明書情報を表示させたくない場合は、-quietオプションを指定します。

$ openssl s_client -connect 192.168.33.10:443 -crlf -quiet

表示内容は、これくらいに少なくなります。

Can't use SSL_get_servername
depth=0 CN = ubuntu2204.localdomain
verify error:num=18:self-signed certificate
verify return:1
depth=0 CN = ubuntu2204.localdomain
verify return:1

この後は入力待ちになります。

ちなみに、アクセス先がApacheの場合、-crlfがないとBad Requestを返すようです。

GET / HTTP/1.1
HTTP/1.1 400 Bad Request
Date: Sat, 09 Sep 2023 13:52:23 GMT
Server: Apache/2.4.52 (Ubuntu)
Content-Length: 315
Connection: close
Content-Type: text/html; charset=iso-8859-1

<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>400 Bad Request</title>
</head><body>
<h1>Bad Request</h1>
<p>Your browser sent a request that this server could not understand.<br />
</p>
<hr>
<address>Apache/2.4.52 (Ubuntu) Server at ubuntu2204.localdomain Port 443</address>
</body></html>
closed

HTTPヘッダーを入力する前に弾かれてしまいます。

サーバーの証明書を取得する

サーバーの証明書を取得するには、以下のコマンドを実行します。

$ echo | openssl s_client -connect 192.168.33.10:443 2>&1 | \
    perl -wn -e 'print if /-BEGIN CERTIFICATE-/ .. /-END CERTIFICATE-/' > server.crt

後半はPerl One Linerで、証明書の部分だけを切り取っています。使わない場合は、別の方法(エディタなど)で証明書の部分を
切り出しましょう。

このようなファイルが取得できました。

server.crt

-----BEGIN CERTIFICATE-----
MIIDHzCCAgegAwIBAgIUMZMUSvCr6TT7hGfk9OoJGFmmBgAwDQYJKoZIhvcNAQEL
BQAwITEfMB0GA1UEAwwWdWJ1bnR1MjIwNC5sb2NhbGRvbWFpbjAeFw0yMzA5MDkx
MzM5MDNaFw0zMzA5MDYxMzM5MDNaMCExHzAdBgNVBAMMFnVidW50dTIyMDQubG9j
YWxkb21haW4wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCgsQH1T4yX
TpXXNQtC/KuSPpAGk+XpwSsdSQ21MrscFWojw7T+Hv/+tmKNvVI7/4i8w6sf56Jk
tQwFnF+BsaVUwMe/kFRF6A98uUjMWsOmpnP4BP4U43eqaBwQSQG+ylsqSyiwMnM1
qkrGafrc4FAUM5Oi2X+dH9TbzEUJdgijGYEc5L06IozyS/M6hul6jbGupnNxHuKG
EwWj07T77DhL/0oV5asu4S8CuYLn2IGcrqlU58Nqv0lt2n97OnSlcocnFlDcdVc7
4DQ/ThmVsXaZR2CghWdsgQaN5rTM/Fn3XJxnqOp/wBgSVrJAWtKMAaVFJPm0W1KJ
UcaFGJQzHsZ9AgMBAAGjTzBNMAkGA1UdEwQCMAAwIQYDVR0RBBowGIIWdWJ1bnR1
MjIwNC5sb2NhbGRvbWFpbjAdBgNVHQ4EFgQUtgGicseT4uY5IR/zyIkyAfwJJE8w
DQYJKoZIhvcNAQELBQADggEBAHG39cf00tFMNNInCY6Y39H3vjjL8zzvn085jaPs
PVceDWWxHNU1tPlHKgPQVJEvpbd8SX7AG66b0/vvlAOOAE0E8gxrHkbZBQXqMVSN
3ILtYQ6byDk3QazwnPBNLHLG08M5X/ySuBHxDsqx07E2Fm1jTZ/zZBxbMwWyZbKT
jIbMYe5GqiAW4mXwc0uxOQx559jhP/dpO9ncUyg7ScWuZEQeaMJn9q0YiJxSIlKD
Lb3brYuDNq0N4kfXLKKcfd4jcmZsLYo7a+WUO5Sg5ZUxsjOXZEqXxdVfRULnob7C
r/rE3QtTqdOH1hrclCMnKfkxnZju1tk/PSaLTlxtzxNYY/E=
-----END CERTIFICATE-----

SSLTLS証明書を指定してアクセスする

今回のApacheは、自己署名証明書を使っていたので

$ openssl s_client -connect 192.168.33.10:443 -crlf

よく見るとアクセス時にverify errorが出力されていました。

CONNECTED(00000003)
Can't use SSL_get_servername
depth=0 CN = ubuntu2204.localdomain
verify error:num=18:self-signed certificate
verify return:1
depth=0 CN = ubuntu2204.localdomain
verify return:1

証明書の検証にも失敗しています。

---
SSL handshake has read 1363 bytes and written 404 bytes
Verification error: self-signed certificate
---

これでコマンドが止まったりはしないのですが。

ここで、先程取得したサーバー証明書-CAfileオプションで指定することで、このエラーが出ないようにすることができます。

$ openssl s_client -connect 192.168.33.10:443  -crlf -CAfile server.crt

今度は、証明書のエラーが出なくなりました。

CONNECTED(00000003)
Can't use SSL_get_servername
depth=0 CN = ubuntu2204.localdomain
verify return:1

こちらもOKです。

---
SSL handshake has read 1359 bytes and written 373 bytes
Verification: OK
---

証明書エラーになる場合に停止する

-verify_return_errorオプションを指定すると、証明書エラーになると処理を停止します。

$ openssl s_client -connect 192.168.33.10:443 -crlf -verify_return_error

ここで止まります。

CONNECTED(00000003)
Can't use SSL_get_servername
depth=0 CN = ubuntu2204.localdomain
verify error:num=18:self-signed certificate
40671ED1CC7F0000:error:0A000086:SSL routines:tls_post_process_server_certificate:certificate verify failed:../ssl/statem/statem_clnt.c:1883:
---
Certificate chain
 0 s:CN = ubuntu2204.localdomain
   i:CN = ubuntu2204.localdomain
   a:PKEY: rsaEncryption, 2048 (bit); sigalg: RSA-SHA256
   v:NotBefore: Sep  9 13:39:03 2023 GMT; NotAfter: Sep  6 13:39:03 2033 GMT
---
no peer certificate available
---
No client certificate CA names sent
Server Temp Key: X25519, 253 bits
---
SSL handshake has read 999 bytes and written 300 bytes
Verification error: self-signed certificate
---
New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384
Secure Renegotiation IS NOT supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
Early data was not sent
Verify return code: 18 (self-signed certificate)
---

有効な証明書を指定すると、動作するようになります。

$ openssl s_client -connect 192.168.33.10:443 -crlf -CAfile server.crt -verify_return_error

使用するプロトコルを指定する

-tlsXXXオプションを使用します。

$ openssl s_client -connect 192.168.33.10:443 -crlf -tls1_3

指定可能なTLSプロトコルはこちら。

$ openssl s_client -help 2>&1 | grep '\-tls1'
 -tls1                      Just use TLSv1
 -tls1_1                    Just use TLSv1.1
 -tls1_2                    Just use TLSv1.2
 -tls1_3                    Just use TLSv1.3

サーバーがサポートしていないプロトコルを指定すると、エラーになります。

$ openssl s_client -connect 192.168.33.10:443 -crlf -tls1
CONNECTED(00000003)
40A7D6A9477F0000:error:0A0000BF:SSL routines:tls_setup_handshake:no protocols available:../ssl/statem/statem_lib.c:104:
---
no peer certificate available
---
No client certificate CA names sent
---
SSL handshake has read 0 bytes and written 7 bytes
Verification: OK
---
New, (NONE), Cipher is (NONE)
Secure Renegotiation IS NOT supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
Early data was not sent
Verify return code: 0 (ok)
---

このあたりは、以前こちらで確認しました。

サーバーが対応しているSSL/TLSプロトコルを確認する(openssl s_client、nmap) - CLOVER🍀

使いたくないプロトコル-no_tlsXXXで指定することもできます。

$ openssl s_client -connect 192.168.33.10:443 -crlf -no_tls1

このあたりが指定できますね。

$ openssl s_client -help 2>&1 | grep '\-no_tls1'
 -no_tls1                   Just disable TLSv1
 -no_tls1_1                 Just disable TLSv1.1
 -no_tls1_2                 Just disable TLSv1.2
 -no_tls1_3                 Just disable TLSv1.3

暗号スイートを指定する

-ciphersuitesオプションで、TLSv1.3で使用する暗号スイートを指定できます。

$ openssl s_client -connect 192.168.33.10:443 -crlf -ciphersuites TLS_AES_128_GCM_SHA256

複数指定する場合は、:で区切ります。

$ openssl s_client -connect 192.168.33.10:443 -crlf -ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384

使用できる暗号スイートは、以下のようなコマンドでプロトコルごとに確認するとよいでしょう。

$ openssl ciphers -v -s -tls1_3

TLSv1.2以下の場合は、-cipherで指定します。

$ openssl s_client -connect 192.168.33.10:443 -crlf -cipher 'HIGH:!aNULL:!MD5' -tls1_2

これについては、こちらにも書きました。

OpenSSLでの暗号スイートと指定方法を確認する(+Apache、nginxでのIPAガイド設定例含む)) - CLOVER🍀

プロキシサーバーを指定する

-proxyオプションを指定することで、プロキシサーバーを介してアクセスします。

$ openssl s_client -connect [host]:[port] -proxy [proxy-host]:[proxy-port]

-connectと合わせて使うことで、指定されたプロキシサーバーにHTTP CONNECTでアクセスします。

サーバー名を指定する

デフォルトでは、-connectで指定された名前がサーバー名としてClientHelloメッセージで使われます。

これと異なる名前を指定する場合は-servernameオプションで指定します。

$ openssl s_client -connect 192.168.33.10:443 -crlf -servername ubuntu2204.localdomain

トラフィックをダンプする(デバッグする)

あまり使うことはないかもしれませんが、-debugオプションを指定します。

$ openssl s_client -connect 192.168.33.10:443 -crlf -debug

おわりに

OpenSSLのs_clientコマンドについて、いろいろ調べてみました。

軽く試すくらいにするつもりが、オプションを眺めていたらけっこう増えてしまいましたが。

これからちゃんと使えていけたらいいなと思います。