サーバーをシミュレートするためにopensslを使用してHTTPSクライアントをテストする
この記事では、opensslを使用してHTTPSクライアントまたはブラウザをテストする方法を説明します。HTTPSクライアントをテストするには、IIS、apache、nginx、またはopensslなどのHTTPSサーバーまたはWebサーバーが必要です。また、いくつかのテストケースも必要です。SSL/TLSには3つの一般的な失敗モードがあります。
- クライアントは、接続してはいけない時に接続を行います。
- 接続は成功すべき時に失敗し、
- 接続は適切に確立されていますが、データは送信中に破損しています。
- 第四の失敗モードがあります: データが安全に転送されない可能性があります。その失敗モードはこの記事の範囲外です。
テストで明らかになった問題があなたのHTTPSクライアントに起因することを確認するために、“良好なことが知られている”HTTPSサーバーを使用したいと考えています。また、“厳格な”または“容赦のない”サーバーが必要です。opensslはこれらの要件に正確に適合しています。
この記事では、openssl s_serverコマンドを使用してHTTPSサーバーになる方法を説明します。適切に設定しなければならない構成項目がたくさんあるため、正しく行う方法だけでなく、何が間違っていたのか、そしてそれを診断し修正する方法についても共有します。
「クライアント」とは、「サーバー」への接続を開始するコンピュータまたはコンピュータプログラムのことです。「サーバー」とは、「クライアント」からの接続を待機するコンピュータプログラムのことです。HTTPおよびHTTPSの場合、「ブラウザ」と「クライアント」があります。ブラウザは人間とのインタラクションのために設計されており、通常はグラフィカルユーザーインターフェースを持っています。すべてのブラウザはHTTP/HTTPSクライアントです。
しかし、ブラウザではないHTTP/HTTPSクライアントも存在します。これらのクライアントは、自動化システムとして使用するために設計されています。賢明なサーバーデザイナーは、システムがブラウザであるHTTPSクライアントとブラウザでないHTTPSクライアントの両方で効果的に使用できるようにするでしょう。
このチュートリアルでは、あなたは学びます:
- 良いHTTPSクライアントまたはブラウザを選ぶ方法
- OpenSSLをHTTPSサーバーとして使用する方法
- HTTPSクライアントをテストするためのHTTPSサーバーの使用方法
ソフトウェアの要件と使用される規約
HTTPSクライアントをテストする方法:ステップバイステップの手順
私は、テストが適切に機能した場合には「正当な」という形容詞を使用し、テストが間違った場合には「誤りのある」という形容詞を使用します。テストが正しく失敗した場合、それは正当な失敗です。テストが失敗すべき時に合格した場合、それは誤りのある合格です。
私は、思い通りに壊したり修理したりできるHTTPSクライアントを使いたかったのですが、それを見つけました:それがhttpコマンドです(これはgithubにhttpieとしてあります)。-verify=noオプションを使用すると、クライアントは壊れてしまいます:誤ってテストに合格してしまいます。私は誤った失敗を作成できませんでしたが、それは良いことです。なぜなら、もしクライアントが失敗した場合、何かが間違っていることを意味するからです。
SSL/TLSプロトコルの中心には、「証明書」(略して「cert」)と秘密の「キー」の2つのファイルがあります。このプロトコル全体を通じて、一方の接続がもう一方に証明書を要求します。最初の端は、証明書に含まれる情報の一部を使用して、秘密のキーを持っているものだけが解ける数学的なパズルを作成します。秘密のキーはその機械から決して離れません。その問題を解くことは、近くの端が遠くの端がキーを持っていることを知ることを意味しますが、そのキーが何であるかは分かりません。
openssl コマンドは、本質的には libssl へのコマンドラインインターフェースです。s_server サブコマンドを使用して起動される粗雑なサーバーが含まれています。openssl には公開証明書/秘密鍵ペアが必要です。私の場合、すでに本番ウェブサーバー用のものを持っていました。それを Let’s Encrypt から無料で取得しました。
サーバーが正常に動作していることの概念実証として、証明書と鍵を私の開発マシンにコピーし、openssl HTTPSサーバーを起動しました。
サーバー側では:
openssl s_server -status_verbose -HTTP -cert fullchain.pem -key privkey.pem
Using default temp DH parameters
ACCEPT
私の最初の試みは失敗しました!
http --verify=yes jeffs-desktop:4433/index.html
http: error: ConnectionError: (Connection aborted., RemoteDisconnected(Remote end closed connection without response)) while doing GET request to URL: http://jeffs-desktop:4433/index.html
最初の仮説:キーと証明書が一致しません。私はそれを確認しました:
openssl x509 -noout -modulus -in fullchain.pemls | openssl md5
(stdin)= b9dbd040d9a0c3b5d3d50af46bc87784
openssl rsa -noout -modulus -in privkey.pem | openssl md5
(stdin)= b9dbd040d9a0c3b5d3d50af46bc87784
彼らは一致しています。では、なぜこれは失敗しているのでしょうか?私の証明書は linuxconfig.dns.net 用ですが、ホスト名に jeffs-desktop を使用しています。
jeffs@jeffs-desktop:~/documents$ openssl x509 -text -noout -in fullchain.pem | fgrep CN
Issuer: C = US, O = Let’s Encrypt, CN = R3
Subject: CN = linuxconfig.ddns.net
これは正当な失敗です:サーバーが誤って設定されており、私のクライアントはそれを検知しました。
もし私が-verify=noオプションを使用していれば、壊れたクライアントができあがり、問題を検知することはなかったでしょう。送信されたデータは盗聴者に対して依然として安全であることに注意してください。この問題は、私の自分のIPv4およびIPv6アドレスで/etc/hostsファイルを修正することで解決できます。
192.168.1.149 linuxconfig.ddns.net
2601:602:8500:b65:155a:7b81:65c:21fa linuxconfig.ddns.net
(ちなみに、IPアドレスを偽装するのがどれほど容易であるかは、SSL/TLSの主な動機の一つです)。
再試行してください。サーバー側で:
openssl s_server -status_verbose -HTTP -cert fullchain.pem -key privkey.pem
Using default temp DH parameters
ACCEPT
クライアント側では:
http --verify=yes https://linuxconfig.ddns.net:4433/index.html
On the server side, I get the error message:
140101997737280:error:14094418:SSL routines:ssl3_read_bytes:tlsv1 alert unknown ca:../ssl/record/rec_layer_s3.c:1543:SSL alert number 48
On the client side, I get the error message:
http: error: SSLError: HTTPSConnectionPool(host='linuxconfig.ddns.net', port=4433): Max retries exceeded with url: / (Caused by SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1131)'))) while doing GET request to URL: https://linuxconfig.ddns.net:4433/
そのエラーメッセージCERTIFICATE_VERIFY_FAILEDは重要な手がかりです:これは、証明書の証明機関(CA)を確認できなかったことを意味します。クライアントが証明書を確認できなかったため、接続に失敗しました。これはもう一つの正当な失敗です。
その証明書自体は偽造されている可能性があり、クライアントにはそれを知る手段がありません。しかし、その証明書は認証局(CA)を参照しています。CAは証明書が有効であることを知っているか、または検証を拒否します。では、CAが信頼できるかどうかはどうやってわかるのでしょうか?
CA自体は証明書、中間証明書を持っており、その証明書は別のCAを参照しています。最終的に、この証明書のチェーンはルート証明書に到達します。ルート証明書は自分自身に署名しているため、定義上、信頼できるものです。この場合、この証明書のチェーン、信頼のチェーンに何か問題が生じています。
openssl s_client -showcerts -connect linuxconfig.ddns.net:4433
CONNECTED(00000003)
depth=0 CN = linuxconfigan.ddns.net
verify error:num=20:unable to get local issuer certificate
verify return:1
depth=0 CN = linuxconfigan.ddns.net
verify error:num=21:unable to verify the first certificate
verify return:1
---
Certificate chain
0 s:CN = linuxconfigan.ddns.net
i:C = US, O = Let's Encrypt, CN = R3
-----BEGIN CERTIFICATE-----
私は自分のプロダクションサーバーが正常に動作していることを知っています。これがチェーンがどのように見えるべきかです(ポート番号は443であり、4433ではありません):
openssl s_client -showcerts -connect linuxconfig.ddns.net:443
CONNECTED(00000003)
depth=2 C = US, O = Internet Security Research Group, CN = ISRG Root X1
verify return:1
depth=1 C = US, O = Let's Encrypt, CN = R3
verify return:1
depth=0 CN = linuxconfig.ddns.net
verify return:1
---
Certificate chain
0 s:CN = linuxconfig.ddns.net
i:C = US, O = Let's Encrypt, CN = R3
-----BEGIN CERTIFICATE-----
MIIFYjCCBEqgAwIBAgISA0MTOSmISSsIyRls8O/2XpAaMA0GCSqGSIb3DQEBCwUA
...
-----END CERTIFICATE-----
1 s:C = US, O = Let's Encrypt, CN = R3
i:C = US, O = Internet Security Research Group, CN = ISRG Root X1
-----BEGIN CERTIFICATE----
...
-----END CERTIFICATE-----
2 s:C = US, O = Internet Security Research Group, CN = ISRG Root X1
i:O = Digital Signature Trust Co., CN = DST Root CA X3
-----BEGIN CERTIFICATE-----
…
ここから進む方法は2つあります:証明書の検証を無効にするか、Let’s Encryptの証明書を既知のCAのリストに追加することができます。検証を無効にするのは迅速で安全です。CAを既知のCAのリストに追加するのは、より複雑です。両方を行いましょう。サーバー側では何も触れていません。クライアント側では検証を無効にすると、次のようになります:
http –verify=no https://linuxconfig.ddns.net:4433/index.html
http: error: ConnectionError: ('Connection aborted.', BadStatusLine('\n')) while doing GET request to URL: https://linuxconfig.ddns.net:4433/index.html
echo $?
1
このエラーメッセージは、HTTP(HTTPSではなく)プロトコルに違反があることを示しています。サーバーは、HTTP返却ヘッダーブロックを返すべきところで、ファイルの最初の行であるindex.htmlを提供しました。これはサーバー側の欠陥であり、すべてのHTTPクライアントに影響を及ぼすでしょう。文書を注意深く見ると、-HTTPオプションの代わりに、-WWW(小文字の-wwwではなく)オプションをopensslとともに使用するように指示されています。私はそうしました:
openssl s_server -status_verbose -WWW -cert fullchain.pem -key privkey.pem は正常に動作しますが、まだ証明書の検証が機能していないという注意点があります。
http -verify=no https://linuxconfig.ddns.net:4433/helloworld.c
HTTP/1.0 200 ok
Content-type: text/plain
#include
int main (int argc, char *argv[]) {
printf("Hello, world\n\n");
}
私が-verify=noを使用したので、これは実際には誤った合格です。
証明書チェーンが有効であることを確認するために、openssl verifyコマンドを使用できます:
openssl verify -purpose sslserver fullchain.pem
CN = linuxconfig.ddns.net
error 20 at 0 depth lookup: unable to get local issuer certificate
error cert.pem: verification failed
迅速な解決策は、プロダクション構成ファイルを使用して、プロダクションウェブサーバーでopenssl s_serverコマンドを試すことでした。これは、opensslサーバーがポート4433で実行されている間、プロダクションサーバーがポート443で実行されているため(比較的)安全です。
openssl s_server -status_verbose -WWW \
-cert /etc/letsencrypt/live/linuxconfig.ddns.net/fullchain.pem \
-key /etc/letsencrypt/live/linuxconfig.ddns.net/privkey.pem -accept 4433
うーん。Nginxは素晴らしく動作していますが、opensslはそうではありません。これが、opensslがnginxよりも優れたテストベッドとなる理由です:nginxの設定が間違っている場合、nginxは何とかしようとしますが、opensslの設定が間違っている場合は、あなたに知らせます。opensslの設定は/etc/ssl/openssl.cnfに保存されています。
CA証明書は/etc/ssl/certsにあると書かれています。インターネットサービスリサーチグループ(ISRG)のルート証明書はそこにあります。しかし、Let’s Encryptの中間証明書はありません。それには一理あります:Let’s Encryptには、私が実行したときにnginxについてすべてを知っている素晴らしいcertbotがありましたが、私はopensslでcertbotを実行しなかったので、Let’s Encryptの証明書は/etc/ssl/certs/にありませんでした。私は次のようにしてLet’s Encryptの証明書を取得しました。
wget https://letsencrypt.org/certs/lets-encrypt-r3.pem
上記のコマンドは、ファイル lets_encrypt_r3.pem を /etc/ssl/certs/ にコピーし、c_rehash プログラムを実行しました。そして、出来上がり:
openssl verify -CApath /etc/ssl/certs/ \ /etc/letsencrypt/live/linuxconfig.ddns.net/fullchain.pem
/etc/letsencrypt/live/linuxconfig.ddns.net/fullchain.pem: OK
それは素敵ですが、テストは、helloworld.cを見ることができますか?
http --verify=yes https://linuxconfig.ddns.net:4433/helloworld.c
HTTP/1.0 200 ok
Content-type: text/plain
#include
int main (int argc, char *argv[]) {
printf("Hello, world\n\n");
}
はい。私は現在、私の作業しているHTTPSクライアントが、少なくとも私が扱ったテストケースにおいて、正しく通過し、正しく失敗することを確認しました。SSL/TLSには証明書失効リスト(CRL)など、他にも問題があることがありますが、あなたが良いアイデアを得ることを願っています。
次に、openssl HTTPSサーバーと私のHTTPSクライアント間で送信されるファイルが一切損傷しないことを確認したいと思います。すべてのファイルがエラーなしに送信されるかどうかは確認できませんが、大きなバイナリファイルを送信し、それが正しく伝送されたことを確認することで、大きなファイルも損傷しないだろうと推測することができます。
私は大きなファイルを見つけるためにls -lorSコマンドを使用し、そのSHA256ハッシュを計算し、サーバーとしてopensslを使用してそれを送信し、受信したファイルを保存し、そのファイルのSHA256ハッシュを計算しました。SHA256ハッシュは一致するはずです。
サーバー側では:
ls -lorS | tail -1
-rw-rw-r-- 1 jeffs 121329853 May 23 2020 CybersecurityEssentials.pdf
sha256sum CybersecurityEssentials.pdf
49a49c8e525a3d6830fce1c1ee0bfce2d3dd4b000eeff5925b074802e62024e0 CybersecurityEssentials.pdf
クライアント側で:
http --verify=no https://linuxconfig.ddns.net:4433/CybersecurityEssentials.pdf -o /tmp/CybersecurityEssentials.pdf
sha256sum /tmp/CybersecurityEssentials.pdf
49a49c8e525a3d6830fce1c1ee0bfce2d3dd4b000eeff5925b074802e62024e0 /tmp/CybersecurityEssentials.pdf
そのPDFファイルは121MBで、私の目的には十分に大きいです。SHA256のチェックサムは一致しているので、ファイルは正しく送信されました。
結論
この記事では、HTTPSプロトコルの一般的な失敗モードについて説明しました。HTTPSクライアントをテストするためのHTTPSサーバーを選択するためのいくつかの基準を使用し、opensslを選択しました。使いやすいHTTPSクライアントを選びました。いくつかの一般的な失敗モードを示し、クライアントがそれらの失敗を検出したことを観察しました。
難しい部分は、opensslを適切に設定することでした。そのため、何が問題になるかとその解決方法を示しました。最後に、opensslをサーバーとして使用し、私のHTTPSクライアントを使って、データの破損なしにファイルを送信できることを実演しました。

