如何获得LWP来验证SSL服务器证书?
如何获得LWP来验证所连接服务器的证书是否由受信任的机构签名并颁发给正确的主机?据我所知,它甚至不检查证书是否声称是我要连接的主机名。这似乎是一个主要的安全漏洞(尤其是最近的DNS漏洞)。
更新:原来我真正想要的是HTTPS_CA_DIR
,因为我没有ca-bundle.crt。但是HTTPS_CA_DIR = / usr / share / ca-certificates /
可以解决问题。无论如何,我都将答案标记为已接受,因为它已经足够接近了。
更新2:事实证明,只有在将Net :: SSL用作基础SSL库时,HTTPS_CA_DIR和HTTPS_CA_FILE才适用。但是LWP还可以与IO :: Socket :: SSL一起使用,无论它提供什么证书,它都会忽略那些环境变量并愉快地与任何服务器通信。有更通用的解决方案吗?
更新3:不幸的是,该解决方案仍未完成。 Net :: SSL或者IO :: Socket :: SSL都没有根据证书检查主机名。这意味着某人可以获取某个域的合法证书,然后假冒任何其他域而不会受到LWP的投诉。
更新4:LWP 6.00最终解决了该问题。有关详细信息,请参见我的答案。
解决方案
回答
有两种方法可以执行此操作,具体取决于我们安装的SSL模块。 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不会"死",但会返回" 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)
回答
如果直接使用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/
回答
我们也可以考虑使用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库的绑定。当我看着它的时候,似乎做得很好。