<p>How can I get <a href="http://search.cpan.org/perldoc?LWP" rel="noreferrer">LWP</a> to verify that the certificate of the server I'm connecting to is signed by a trusted authority and issued to the correct host?  As far as I can tell, it doesn't even check that the certificate claims to be for the hostname I'm connecting to.  That seems like a major security hole (especially with the recent DNS vulnerabilities).</p>

<p><strong>Update:</strong>  It turns out what I really wanted was <code>HTTPS_CA_DIR</code>, because I don't have a ca-bundle.crt.  But <code>HTTPS_CA_DIR=/usr/share/ca-certificates/</code> did the trick.  I'm marking the answer as accepted anyway, because it was close enough.</p>

<p><strong>Update 2:</strong> It turns out that <code>HTTPS_CA_DIR</code> and <code>HTTPS_CA_FILE</code> only apply if you're using Net::SSL as the underlying SSL library.  But LWP also works with IO::Socket::SSL, which will ignore those environment variables and happily talk to any server, no matter what certificate it presents.  Is there a more general solution?</p>

<p><strong>Update 3:</strong> Unfortunately, the solution still isn't complete.  Neither Net::SSL nor IO::Socket::SSL is checking the host name against the certificate.  This means that someone can get a legitimate certificate for some domain, and then impersonate any other domain without LWP complaining.</p>

<p><strong>Update 4:</strong> <a href="http://search.cpan.org/dist/libwww-perl/" rel="noreferrer">LWP 6.00</a> finally solves the problem.  See <a href="https://stackoverflow.com/questions/74358/how-can-i-get-lwp-to-validate-ssl-server-certificates#5329129">my answer</a> for details.</p>

[{'answer_id': 74432, 'author': 'Brian Phillips', 'author_id': 7230, 'author_profile': 'https://Stackoverflow.com/users/7230', 'pm_score': 3, 'selected': False, 'text': '<p>There are two means of doing this depending on which SSL module you have installed.  The <a href="http://search.cpan.org/src/GAAS/libwww-perl-5.814/README.SSL" rel="nofollow noreferrer">LWP docs recommend installing Crypt::SSLeay</a>.  If that\'s what you\'ve done, setting the <code>HTTPS_CA_FILE</code> environment variable to point to your ca-bundle.crt should do the trick. (the <a href="http://search.cpan.org/perldoc?Crypt::SSLeay" rel="nofollow noreferrer">Crypt::SSLeay docs</a> mentions this but is a bit light on details).  Also, depending on your setup, you may need to set the <code>HTTPS_CA_DIR</code> environment variable instead.</p>\n\n<p>Example for Crypt::SSLeay:</p>\n\n<pre>\n<code>\nuse LWP::Simple qw(get);\n$ENV{HTTPS_CA_FILE} = "/path/to/your/ca/file/ca-bundle";\n$ENV{HTTPS_DEBUG} = 1;\n\nprint get("https://some-server-with-bad-certificate.com");\n\n__END__\nSSL_connect:before/connect initialization\nSSL_connect:SSLv2/v3 write client hello A\nSSL_connect:SSLv3 read server hello A\nSSL3 alert write:fatal:unknown CA\nSSL_connect:error in SSLv3 read server certificate B\nSSL_connect:error in SSLv3 read server certificate B\nSSL_connect:before/connect initialization\nSSL_connect:SSLv3 write client hello A\nSSL_connect:SSLv3 read server hello A\nSSL3 alert write:fatal:bad certificate\nSSL_connect:error in SSLv3 read server certificate B\nSSL_connect:before/connect initialization\nSSL_connect:SSLv2 write client hello A\nSSL_connect:error in SSLv2 read server hello B\n</code>\n</pre>\n\n<p>Note that get doesn\'t <code>die</code>, but it does return an <code>undef</code>.</p>\n\n<p>Alternatively, you can use the <code>IO::Socket::SSL</code> module (also available from the CPAN).  To make this verify the server certificate you need to modify the SSL context defaults:</p>\n\n<pre>\n<code>\nuse IO::Socket::SSL qw(debug3);\nuse Net::SSLeay;\nBEGIN {\n    IO::Socket::SSL::set_ctx_defaults(\n        verify_mode => Net::SSLeay->VERIFY_PEER(),\n        ca_file => "/path/to/ca-bundle.crt",\n      # ca_path => "/alternate/path/to/cert/authority/directory"\n    );\n}\nuse LWP::Simple qw(get);\n\nwarn get("https:://some-server-with-bad-certificate.com");\n</code>\n</pre>\n\n<p>This version also causes <code>get()</code> to return undef but prints a warning to <code>STDERR</code> when you execute it (as well as a bunch of debugging if you import the debug* symbols from IO::Socket::SSL):</p>\n\n<pre>\n<code>\n% perl ssl_test.pl\nDEBUG: .../IO/Socket/SSL.pm:1387: new ctx 139403496\nDEBUG: .../IO/Socket/SSL.pm:269: socket not yet connected\nDEBUG: .../IO/Socket/SSL.pm:271: socket connected\nDEBUG: .../IO/Socket/SSL.pm:284: ssl handshake not started\nDEBUG: .../IO/Socket/SSL.pm:327: Net::SSLeay::connect -> -1\nDEBUG: .../IO/Socket/SSL.pm:1135: SSL connect attempt failed with unknown errorerror:14090086:SSL routines:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed\n\nDEBUG: .../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\nDEBUG: .../IO/Socket/SSL.pm:1422: free ctx 139403496 open=139403496\nDEBUG: .../IO/Socket/SSL.pm:1425: OK free ctx 139403496\nDEBUG: .../IO/Socket/SSL.pm:1135: IO::Socket::INET configuration failederror:00000000:lib(0):func(0):reason(0)\n500 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) \n</code>\n</pre> \n'}, {'answer_id': 612238, 'author': 'dave0', 'author_id': 73886, 'author_profile': 'https://Stackoverflow.com/users/73886', 'pm_score': 2, 'selected': False, 'text': '<p>If you use LWP::UserAgent directly (not via LWP::Simple) you can validate the hostname in the certificate by adding the "If-SSL-Cert-Subject" header to your HTTP::Request object.  The value of the header is treated as a regular expression to be applied on the certificate subject, and if it does not match, the request fails.  For example:</p>\n\n<pre><code>#!/usr/bin/perl \nuse LWP::UserAgent;\nmy $ua = LWP::UserAgent-&gt;new();\nmy $req = HTTP::Request-&gt;new(GET =&gt; \'https://yourdomain.tld/whatever\');\n$req-&gt;header(\'If-SSL-Cert-Subject\' =&gt; \'/CN=make-it-fail.tld\');\n\nmy $res = $ua-&gt;request( $req );\n\nprint "Status: " . $res-&gt;status_line . "\\n"\n</code></pre>\n\n<p>will print</p>\n\n<pre><code>Status: 500 Bad SSL certificate subject: \'/C=CA/ST=Ontario/L=Ottawa/O=Your Org/CN=yourdomain.tld\' !~ //CN=make-it-fail.tld/\n</code></pre>\n'}, {'answer_id': 2318320, 'author': 'goneri', 'author_id': 2710608, 'author_profile': 'https://Stackoverflow.com/users/2710608', 'pm_score': 1, 'selected': False, 'text': '<p>You may also consider Net::SSLGlue ( <a href="http://search.cpan.org/dist/Net-SSLGlue/lib/Net/SSLGlue.pm" rel="nofollow noreferrer">http://search.cpan.org/dist/Net-SSLGlue/lib/Net/SSLGlue.pm</a> ) But, take care, it depends on recent IO::Socket::SSL and Net::SSLeay versions.</p>\n'}, {'answer_id': 2322654, 'author': 'Marsh Ray', 'author_id': 116270, 'author_profile': 'https://Stackoverflow.com/users/116270', 'pm_score': 1, 'selected': False, 'text': "<p>You are right to be concerned about this. Unfortunately, I don't think it's possible to do it 100% securely under any of the low-level SSL/TLS bindings I looked at for Perl.</p>\n\n<p>Essentially you need to pass in the hostname of the server you want to connect to the SSL library before the handshaking gets underway. Alternatively, you could arrange for a callback to occur at the right moment and abort the handshake from inside the callback if it doesn't check out. People writing Perl bindings to OpenSSL seemed to have troubles making the callback interface consistently.</p>\n\n<p>The method to check the hostname against the server's cert is dependent on the protocol, too. So that would have to be a parameter to any perfect function.</p>\n\n<p>You might want to see if there are any bindings to the Netscape/Mozilla NSS library. It seemed pretty good at doing this when I looked at it.</p>\n"}, {'answer_id': 5329129, 'author': 'cjm', 'author_id': 8355, 'author_profile': 'https://Stackoverflow.com/users/8355', 'pm_score': 6, 'selected': True, 'text': '<p>This long-standing security hole has finally been fixed in version 6.00 of <a href="https://metacpan.org/release/libwww-perl" rel="noreferrer">libwww-perl</a>.  Starting with that version, by default <a href="https://metacpan.org/pod/LWP::UserAgent" rel="noreferrer">LWP::UserAgent</a> verifies that HTTPS servers present a valid certificate matching the expected hostname (unless <code>$ENV{PERL_LWP_SSL_VERIFY_HOSTNAME}</code> is set to a false value or, for backwards compatibility if that variable is not set at all, either <code>$ENV{HTTPS_CA_FILE}</code> or <code>$ENV{HTTPS_CA_DIR}</code> is set).</p>\n\n<p>This can be controlled by the new <a href="https://metacpan.org/pod/LWP::UserAgent#ssl_opts" rel="noreferrer">ssl_opts</a> option of LWP::UserAgent.  See that link for details on how the Certificate Authority certificates are located.  But <strong>be careful</strong>, the way LWP::UserAgent used to work, if you provide a <code>ssl_opts</code> hash to the constructor, then <strong><code>verify_hostname</code> defaulted to 0</strong> instead of 1.  (<a href="https://rt.cpan.org/Public/Bug/Display.html?id=67947" rel="noreferrer">This bug</a> was fixed in LWP 6.03.)  To be safe, always specify <code>verify_hostname =&gt; 1</code> in your <code>ssl_opts</code>.</p>\n\n<p>So <code>use LWP::UserAgent 6;</code> should be sufficient to have server certificates validated.</p>\n'}, {'answer_id': 7722012, 'author': 'blumentopf', 'author_id': 988281, 'author_profile': 'https://Stackoverflow.com/users/988281', 'pm_score': 2, 'selected': False, 'text': '<p>All the solutions presented here contain a major security flaw in that they only verify the validity of the certificate\'s trust chain, but don\'t compare the certificate\'s Common Name to the hostname you\'re connecting to. Thus, a man in the middle may present an arbitrary certificate to you and LWP will happily accept it as long as it\'s signed by a CA you trust. The bogus certificate\'s Common Name is irrelevant because it\'s never checked by LWP.</p>\n\n<p>If you\'re using <code>IO::Socket::SSL</code> as LWP\'s backend, you can enable verification of the Common Name by setting the <code>verifycn_scheme</code> parameter like this:</p>\n\n<pre><code>use IO::Socket::SSL;\nuse Net::SSLeay;\nBEGIN {\n    IO::Socket::SSL::set_ctx_defaults(\n        verify_mode =&gt; Net::SSLeay-&gt;VERIFY_PEER(),\n        verifycn_scheme =&gt; \'http\',\n        ca_path =&gt; "/etc/ssl/certs"\n    );\n}\n</code></pre>\n'}, {'answer_id': 23570769, 'author': 'bshok', 'author_id': 1544311, 'author_profile': 'https://Stackoverflow.com/users/1544311', 'pm_score': 3, 'selected': False, 'text': '<p>I landed on this page looking for a way to bypass SSL validation but all answers were still very helpful. Here are my findings. For those looking to bypass SSL validation (not recommended but there may be cases where you will absolutely have to), I\'m on lwp 6.05 and this worked for me:</p>\n\n<pre><code>use strict;\nuse warnings;\nuse LWP::UserAgent;\nuse HTTP::Request::Common qw(GET);\nuse Net::SSL;\n\nmy $ua = LWP::UserAgent-&gt;new( ssl_opts =&gt; { verify_hostname =&gt; 0 }, );\nmy $req = GET \'https://github.com\';\nmy $res = $ua-&gt;request($req);\nif ($res-&gt;is_success) {\n    print $res-&gt;content;\n} else {\n    print $res-&gt;status_line . "\\n";\n}\n</code></pre>\n\n<p>I also tested on a page with POST and it also worked. The key is to use Net::SSL along with verify_hostname = 0.</p>\n'}, {'answer_id': 35243146, 'author': 'Bojoer', 'author_id': 3586145, 'author_profile': 'https://Stackoverflow.com/users/3586145', 'pm_score': 0, 'selected': False, 'text': '<p>Just perform execute the following command in Terminal:\n<strong>sudo cpan install Mozilla::CA</strong></p>\n\n<p>It should solve it.</p>\n'}]
['https://Stackoverflow.com/questions/74358', 'https://Stackoverflow.com', 'https://Stackoverflow.com/users/8355/']