4
votes

When I run the following code:

use strict;
use warnings;

use IO::Socket::SSL;
use LWP::UserAgent;

my $ua = LWP::UserAgent->new(ssl_opts => {
    verify_hostname => 0,
});

my $res = $ua->get('https://internal.foo.bar.baz:20002');
print $res->as_string;

I get an internal error from LWP:

500 Can't connect to internal.foo.bar.baz:20002 (Bad file descriptor)
Content-Type: text/plain
Client-Date: Fri, 29 Jun 2018 21:23:13 GMT
Client-Warning: Internal response

Can't connect to internal.foo.bar.baz:20002 (Bad file descriptor)

Bad file descriptor at D:/strawberry/perl/site/lib/LWP/Protocol/http.pm line 50.

Network traffic shows that it's a "client hello" followed by an immediate reset from the server, and that the protocol is TLSv1. The server only allows TLS 1.2 connections, so that makes sense.

Wireshark 1

When I change my code to specify that the client should only use TLS 1.2, I get the same response.

my $ua = LWP::UserAgent->new(ssl_opts => {
    verify_hostname => 0,
    SSL_version => 'TLSv1_2',
});

And, in fact, the network traffic looks identical:

Wireshark 2

When I explicitly use Net::SSL instead of IO::Socket::SSL:

use strict;
use warnings;

use Net::SSL;
use LWP::UserAgent;

my $ua = LWP::UserAgent->new(ssl_opts => {
    verify_hostname => 0,
});

my $res = $ua->get('https://internal.foo.bar.baz:20002');
print $res->as_string;

It works:

HTTP/1.1 401 Unauthorized
Date: Fri, 29 Jun 2018 21:33:35 GMT
Server: Kestrel
Client-Date: Fri, 29 Jun 2018 21:33:37 GMT
Client-Peer: ***.**.**.209:20002
Client-Response-Num: 1
Client-SSL-Cert-Issuer: *******************************************************
Client-SSL-Cert-Subject: **************************************************************************
Client-SSL-Cipher: ECDHE-RSA-AES256-SHA384
Client-SSL-Socket-Class: Net::SSL
Client-SSL-Warning: Peer certificate not verified
Client-Transfer-Encoding: chunked
Client-Warning: Missing Authenticate header
Strict-Transport-Security: max-age=2592000
X-Powered-By: ASP.NET

And the protocol is correctly set to TLSv1.2:

Wireshark 3

Oddly enough, analyze-ssl.pl negotiates TLS 1.2 with IO::Socket::SSL:

-- internal.foo.bar.baz port 20002
 ! using certificate verification (default) -> SSL connect attempt failed error:1416F086:SSL routines:tls_process_server_certificate:certificate verify failed
 * maximum SSL version  : TLSv1_2 (SSLv23)
 * supported SSL versions with handshake used and preferred cipher(s):
   * handshake protocols ciphers
   * SSLv23    TLSv1_2   ECDHE-RSA-AES256-SHA384
   * TLSv1_2   TLSv1_2   ECDHE-RSA-AES256-SHA384
   * TLSv1_1   FAILED: SSL connect attempt failed
   * TLSv1     FAILED: SSL connect attempt failed
 * cipher order by      : unknown
 * SNI supported        : certificate verify fails without SNI
 * certificate verified : FAIL: SSL connect attempt failed error:1416F086:SSL routines:tls_process_server_certificate:certificate verify failed
 * chain on ***.**.**.209
   * [0/0] bits=2048, ocsp_uri=, *******************************************************************************************************
   * [1/1] bits=2048, ocsp_uri=, *******************************************************
   * [2/2] bits=2048, ocsp_uri=, ****************************************************************

What can I do to prevent IO::Socket::SSL from attempting a TLS 1.0 connection from LWP?


  • Perl 5.26.2 MSWin32-x64-multi-thread (Strawberry)
  • OpenSSL 1.1.0h 27 Mar 2018
  • LWP 6.34
  • Net::HTTPS 6.18
  • IO::Socket::SSL 2.056
  • Net::SSL 2.86

Output from Steffen Ullrich's script:

openssl version compiled=0x1010008f linked=0x1010008f -- OpenSSL 1.1.0h  27 Mar 2018
IO::Socket::SSL version=2.056
LWP::UserAgent version=6.34
LWP::Protocol::https version=6.07
Net::HTTPS version=6.18
2

2 Answers

4
votes

It should not behave the way you describe and in fact I cannot reproduce your problem with a freshly installed latest Strawberry Perl on Win10, i.e. using the same Perl version you use. Your first code was taken unchanged apart from the destination and using an openssl s_server -www... as target. It perfectly connects with TLS 1.2 and a packet capture clearly shows TLS 1.2 too.

My guess is that something is messed up with your setup: maybe some older Perl installation still on the system interfering or something similar. This messed up setup is probably specific to how you run your script since running analyze.pl shows no such problems. Therefore I recommend to actually check inside your script what gets actually used, i.e.

use strict;
use warnings;
use IO::Socket::SSL;
use LWP::UserAgent;
use LWP::Protocol::https;

printf("openssl version compiled=0x%0x linked=0x%0x -- %s\n",
    Net::SSLeay::OPENSSL_VERSION_NUMBER(),
    Net::SSLeay::SSLeay(),
    Net::SSLeay::SSLeay_version(0));
printf("IO::Socket::SSL version=%s\n",$IO::Socket::SSL::VERSION);
printf("LWP::UserAgent version=%s\n",$LWP::UserAgent::VERSION);
printf("LWP::Protocol::https version=%s\n",$LWP::Protocol::https::VERSION);
printf("Net::HTTPS version=%s\n",$Net::HTTPS::VERSION);

my $ua = LWP::UserAgent->new(ssl_opts => {
    verify_hostname => 0,
});

my $res = $ua->get('https://internal.foo.bar.baz:20002');
print $res->as_string;

This gives for me on the fresh setup slightly different versions of LWP (6.33 vs. 6.34) and Net::HTTPS (6.17 vs. 6.18) but the rest fits with your version. But the important part is probably the version of OpenSSL actually loaded by the code. My guess is that in your specific script setup it uses not the OpenSSL 1.1.0 you expect but some old OpenSSL 1.0.0 or older which have no support for TLS 1.2.

3
votes

Since analyze-ssl.pl worked and my test script didn't when pointed at the same server, I started comparing them to find out what the differences were. One of the major differences is that analyze-ssl.pl attempts a connection with SSL_cipher_list => '', and it turns out that this was, in fact, the issue.

Changing my LWP::UserAgent instantiation solved the problem:

my $ua = LWP::UserAgent->new(ssl_opts => {
    verify_hostname => 0,
    SSL_cipher_list => '',
});