Как получить LWP для проверки сертификатов сервера SSL?

как я могу получить LWP чтобы убедиться, что сертификат сервера, к которому я подключаюсь, подписан доверенным органом и выдан правильному хосту? Насколько я могу судить, он даже не проверяет, что сертификат претендует на имя хоста, к которому я подключаюсь. Это похоже на серьезную дыру в безопасности (особенно с недавними уязвимостями DNS).

обновление: оказывается, то, что я действительно хотел, было HTTPS_CA_DIR, потому что у меня нет ca-bundle.ЭЛТ. Но!--1--> сделал свое дело. Я все равно отмечаю ответ как принятый, потому что он был достаточно близок.

обновление 2: получается, что HTTPS_CA_DIR и HTTPS_CA_FILE применяется, только если вы используете Net:: SSL в качестве базовой библиотеки SSL. Но LWP также работает с IO::Socket::SSL, который будет игнорировать эти переменные среды и счастливо разговаривать с любым сервером, независимо от того, какой сертификат он представляет. Есть ли более общее решение?

обновление 3: к сожалению, решение все еще не завершено. Ни Net::SSL, ни IO::Socket:: SSL не проверяет имя хоста против сертификата. Это означает, что кто-то может получить законный сертификат для некоторого домена, а затем олицетворять любой другой домен без жалоб LWP.

обновление 4: LWP 6.00 наконец-то решает проблему. См.мой ответ: для сведения.

8 ответов


это давнее отверстие безопасности, наконец, было исправлено в версии 6.00 libwww-perl. Начиная с этой версии, по умолчанию LWP:: UserAgent проверяет, что серверы HTTPS представляют действительный сертификат, соответствующий ожидаемому имени хоста (если $ENV{PERL_LWP_SSL_VERIFY_HOSTNAME} имеет значение false или, для обратной совместимости, Если эта переменная не установлена вообще, либо $ENV{HTTPS_CA_FILE} или $ENV{HTTPS_CA_DIR} установить).

это может быть проконтролировано новым ssl_opts опция LWP:: UserAgent. См. эту ссылку для получения сведений о расположении сертификатов Центра сертификации. Но!--17-->будьте осторожны, способ LWP::UserAgent используется для работы, если вы предоставляете ssl_opts хэш-конструктора, затем verify_hostname по умолчанию 0 вместо 1. (эта ошибка исправлено в LWP 6.03.) Для безопасности всегда указывайте verify_hostname => 1 в своем ssl_opts.

так use LWP::UserAgent 6; должно быть достаточно для сервера сертификаты подтверждены.


есть два способа сделать это в зависимости от того, какой модуль SSL вы установили. The документы LWP рекомендуют установить Crypt:: SSLeay. Если это то, что вы сделали, задание HTTPS_CA_FILE переменная окружения, указывающая на ваш ca-bundle.crt должен сделать трюк. (the Crypt:: SSLeay docs упоминает об этом, но немного света на детали). Кроме того, в зависимости от вашей настройки вам может потребоваться установить переменные среды.

пример для 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 когда вы выполняете его (а также кучу отладки, если вы импортируете символы debug* от IO:: Socket:: SSL):


% 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), вы можете проверить имя хоста в сертификате, добавив заголовок " If-SSL-Cert-Subject HTTP::Request объект. Значение заголовка рассматривается как регулярное выражение, применяемое к субъекту сертификата, и если оно не совпадает, запрос завершается ошибкой. Например:

#!/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/

все представленные здесь решения содержат основной недостаток безопасности в том, что они проверяют только действительность цепочки доверия сертификата, но не сравнивают общее имя сертификата с именем хоста, к которому вы подключаетесь. Таким образом, человек в середине может представить вам произвольный сертификат, и LWP с радостью примет его, пока он подписан CA, которому Вы доверяете. Общее имя фиктивного сертификата не имеет значения, поскольку оно никогда не проверялось LWP.

Если вы используете IO::Socket::SSL в качестве бэкэнда LWP вы можете включить проверку общего имени, установив


вы также можете рассмотреть Net:: SSLGlue ( http://search.cpan.org/dist/Net-SSLGlue/lib/Net/SSLGlue.pm) но, будьте осторожны, это зависит от последних версий IO::Socket::SSL и Net::SSLeay.


вы правы, что беспокоитесь об этом. К сожалению, я не думаю, что это возможно сделать на 100% безопасно под любым из Привязок SSL/TLS низкого уровня, которые я смотрел для Perl.

по существу вам нужно передать имя хоста сервера, который вы хотите подключить к библиотеке SSL, прежде чем начнется рукопожатие. Кроме того, вы можете организовать обратный вызов в нужный момент и прервать рукопожатие изнутри обратного вызова, если он не проверит. Люди написание Привязок Perl к OpenSSL, казалось, имело проблемы с последовательным выполнением интерфейса обратного вызова.

метод проверки имени хоста по сертификату сервера также зависит от протокола. Так что это должно быть параметром для любой идеальной функции.

возможно, вы захотите узнать, есть ли привязки к библиотеке Netscape/Mozilla NSS. Когда я посмотрел на него, мне показалось, что он неплохо это делает.


просто выполните следующую команду в терминале: sudo cpan установить Mozilla:: CA

Это должно решить его.