LWP で SSL サーバー証明書を検証するにはどうすればよいですか?
質問
どうすれば入手することができますか LWP 接続しているサーバーの証明書が信頼できる機関によって署名され、正しいホストに発行されたことを確認するには?私の知る限り、証明書が接続先のホスト名のものであるかどうかさえチェックしません。これは重大なセキュリティ ホールのようです (特に最近の DNS 脆弱性)。
アップデート: 私が本当に欲しかったものは HTTPS_CA_DIR
, 、ca-bundle.crtを持っていないためです。しかし HTTPS_CA_DIR=/usr/share/ca-certificates/
トリックを実行しました。とにかく、十分に近かったので、回答を受け入れ済みとしてマークします。
更新 2: 判明したのは、 HTTPS_CA_DIR
そして HTTPS_CA_FILE
Net::SSL を基盤となる SSL ライブラリとして使用している場合にのみ適用されます。ただし、LWP は IO::Socket::SSL とも連携し、これらの環境変数を無視し、どのような証明書が提示されているかに関係なく、任意のサーバーと問題なく通信します。より一般的な解決策はありますか?
アップデート 3: 残念ながら、解決策はまだ完全ではありません。Net::SSL も IO::Socket::SSL も、ホスト名を証明書と照合してチェックしません。これは、誰かが特定のドメインの正規の証明書を取得し、LWP に文句を言わずに他のドメインになりすますことができることを意味します。
解決
この長年にわたるセキュリティ ホールは、バージョン 6.00 でついに修正されました。 libwww-perl. 。そのバージョン以降、デフォルトで LWP::ユーザーエージェント HTTPS サーバーが、予想されるホスト名と一致する有効な証明書を提示していることを検証します (ただし、 $ENV{PERL_LWP_SSL_VERIFY_HOSTNAME}
false 値に設定されるか、その変数がまったく設定されていない場合は下位互換性のために、次のいずれかになります。 $ENV{HTTPS_CA_FILE}
または $ENV{HTTPS_CA_DIR}
が設定されています)。
これは新しいもので制御できます ssl_opts LWP::UserAgent のオプション。認証局の証明書の場所の詳細については、そのリンクを参照してください。しかし 気をつけて, 、LWP::UserAgent が以前に動作していた方法です。 ssl_opts
ハッシュをコンストラクターに渡してから、 verify_hostname
デフォルトは 0 1の代わりに。(このバグ LWP 6.03 で修正されました。)安全のため、常に指定してください。 verify_hostname => 1
あなたの中で ssl_opts
.
それで use LWP::UserAgent 6;
サーバー証明書を検証するには十分なはずです。
他のヒント
これを行うには、インストールした SSL モジュールに応じて 2 つの方法があります。の LWP ドキュメントでは Crypt::SSLeay のインストールを推奨しています. 。それを行った場合は、 HTTPS_CA_FILE
ca-bundle.crt を指す環境変数を指定するとうまくいくはずです。( Crypt::SSLeay ドキュメント これについては言及していますが、詳細については少し説明が足りません)。また、設定によっては、 HTTPS_CA_DIR
代わりに環境変数を使用します。
Crypt::SSLeay の例:
use LWP::Simple qw(get);
$ENV{HTTPS_CA_FILE} = "/path/to/your/ca/file/ca-bundle";
$ENV{HTTPS_DEBUG} = 1;
print get("https://some-server-with-bad-certificate.com");
__END__
SSL_connect:before/connect initialization
SSL_connect:SSLv2/v3 write client hello A
SSL_connect:SSLv3 read server hello A
SSL3 alert write:fatal:unknown CA
SSL_connect:error in SSLv3 read server certificate B
SSL_connect:error in SSLv3 read server certificate B
SSL_connect:before/connect initialization
SSL_connect:SSLv3 write client hello A
SSL_connect:SSLv3 read server hello A
SSL3 alert write:fatal:bad certificate
SSL_connect:error in SSLv3 read server certificate B
SSL_connect:before/connect initialization
SSL_connect:SSLv2 write client hello A
SSL_connect:error in SSLv2 read server hello B
get はそうではないことに注意してください die
, 、しかし、それは返します undef
.
あるいは、 IO::Socket::SSL
モジュール (CPAN からも入手可能)。これをサーバー証明書を検証するには、SSL コンテキストのデフォルトを変更する必要があります。
use IO::Socket::SSL qw(debug3);
use Net::SSLeay;
BEGIN {
IO::Socket::SSL::set_ctx_defaults(
verify_mode => Net::SSLeay->VERIFY_PEER(),
ca_file => "/path/to/ca-bundle.crt",
# ca_path => "/alternate/path/to/cert/authority/directory"
);
}
use LWP::Simple qw(get);
warn get("https:://some-server-with-bad-certificate.com");
このバージョンではまた、 get()
undef を返しますが、警告を出力します STDERR
これを実行すると (IO::Socket::SSL から debug* シンボルをインポートした場合は、大量のデバッグも実行されます):
% perl ssl_test.pl
DEBUG: .../IO/Socket/SSL.pm:1387: new ctx 139403496
DEBUG: .../IO/Socket/SSL.pm:269: socket not yet connected
DEBUG: .../IO/Socket/SSL.pm:271: socket connected
DEBUG: .../IO/Socket/SSL.pm:284: ssl handshake not started
DEBUG: .../IO/Socket/SSL.pm:327: Net::SSLeay::connect -> -1
DEBUG: .../IO/Socket/SSL.pm:1135: SSL connect attempt failed with unknown errorerror:14090086:SSL routines:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed
DEBUG: .../IO/Socket/SSL.pm:333: fatal SSL error: SSL connect attempt failed with unknown errorerror:14090086:SSL routines:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed
DEBUG: .../IO/Socket/SSL.pm:1422: free ctx 139403496 open=139403496
DEBUG: .../IO/Socket/SSL.pm:1425: OK free ctx 139403496
DEBUG: .../IO/Socket/SSL.pm:1135: IO::Socket::INET configuration failederror:00000000:lib(0):func(0):reason(0)
500 Can't connect to some-server-with-bad-certificate.com:443 (SSL connect attempt failed with unknown errorerror:14090086:SSL routines:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed)
SSL 検証をバイパスする方法を探してこのページにたどり着きましたが、すべての回答は依然として非常に役に立ちました。これが私の発見です。SSL 検証をバイパスしたいと考えている人のために (推奨されませんが、絶対にバイパスする必要がある場合もあります)、私は lwp 6.05 を使用していますが、これは私にとってはうまくいきました。
use strict;
use warnings;
use LWP::UserAgent;
use HTTP::Request::Common qw(GET);
use Net::SSL;
my $ua = LWP::UserAgent->new( ssl_opts => { verify_hostname => 0 }, );
my $req = GET 'https://github.com';
my $res = $ua->request($req);
if ($res->is_success) {
print $res->content;
} else {
print $res->status_line . "\n";
}
POSTを使用したページでもテストしましたが、それも機能しました。重要なのは、Net::SSL を verify_hostname = 0 とともに使用することです。
LWP::UserAgent を (LWP::Simple 経由ではなく) 直接使用する場合は、HTTP::Request オブジェクトに「If-SSL-Cert-Subject」ヘッダーを追加することで、証明書内のホスト名を検証できます。ヘッダーの値は、証明書のサブジェクトに適用される正規表現として扱われ、一致しない場合、要求は失敗します。例えば:
#!/usr/bin/perl
use LWP::UserAgent;
my $ua = LWP::UserAgent->new();
my $req = HTTP::Request->new(GET => 'https://yourdomain.tld/whatever');
$req->header('If-SSL-Cert-Subject' => '/CN=make-it-fail.tld');
my $res = $ua->request( $req );
print "Status: " . $res->status_line . "\n"
印刷します
Status: 500 Bad SSL certificate subject: '/C=CA/ST=Ontario/L=Ottawa/O=Your Org/CN=yourdomain.tld' !~ //CN=make-it-fail.tld/
ここで紹介するすべてのソリューションには、証明書の信頼チェーンの有効性のみを検証し、証明書の共通名と接続先のホスト名を比較しないという重大なセキュリティ上の欠陥が含まれています。したがって、中間者が任意の証明書を提示しても、信頼できる CA によって署名されている限り、LWP はそれを喜んで受け入れます。偽の証明書の共通名は、LWP によってチェックされないため、無関係です。
使用している場合 IO::Socket::SSL
LWP のバックエンドとして、 verifycn_scheme
このようなパラメータ:
use IO::Socket::SSL;
use Net::SSLeay;
BEGIN {
IO::Socket::SSL::set_ctx_defaults(
verify_mode => Net::SSLeay->VERIFY_PEER(),
verifycn_scheme => 'http',
ca_path => "/etc/ssl/certs"
);
}
Net::SSLGlue ( http://search.cpan.org/dist/Net-SSLGlue/lib/Net/SSLGlue.pm ) ただし、最新の IO::Socket::SSL および Net::SSLeay のバージョンに依存することに注意してください。
あなたがこのことを心配するのは当然です。残念ながら、私が Perl について調べた低レベルの SSL/TLS バインディングの下では、これを 100% 安全に行うことは不可能だと思います。
基本的に、ハンドシェイクを開始する前に、SSL ライブラリに接続するサーバーのホスト名を渡す必要があります。あるいは、適切なタイミングでコールバックが発生するように手配し、チェックアウトしない場合はコールバック内からハンドシェイクを中止することもできます。OpenSSL への Perl バインディングを作成している人は、コールバック インターフェイスを一貫して作成するのに苦労しているようです。
ホスト名をサーバーの証明書と照合する方法もプロトコルによって異なります。したがって、これは完全な関数のパラメータでなければなりません。
Netscape/Mozilla NSS ライブラリへのバインディングがあるかどうかを確認したい場合があります。見ているとかなり上手にできているようでした。
ターミナルで次のコマンドを実行するだけです。sudo cpan インストール Mozilla::CA
それは解決するはずです。