5

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
Matt Jacob
  • 6,503
  • 2
  • 24
  • 27

2 Answers2

4

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.

Noam Rathaus
  • 5,405
  • 2
  • 28
  • 37
Steffen Ullrich
  • 114,247
  • 10
  • 131
  • 172
4

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 => '',
});
Matt Jacob
  • 6,503
  • 2
  • 24
  • 27
  • 1
    Your change essentially makes IO::Socket::SSL offer a wide range of ciphers instead of only a small set of ciphers which is modeled after what the browsers do and which is done to work around some broken servers which cannot deal with larger cipher sets. It looks like your server is only supporting a very limited amount of ciphers. Because of this is not unlikely that you'll get problems with this server with other clients too. – Steffen Ullrich Jul 03 '18 at 05:34
  • @SteffenUllrich Since it's just for API calls between internal systems, I'm OK with that. The same request works by default in cURL and SoapUI, so they must be offering more ciphers too. Does that make it right? No, but like I said, this is internal use only. – Matt Jacob Jul 03 '18 at 17:18
  • @SteffenUllrich Turns out, the group policy on the server modified the cipher suite order and really made a mess of things (the basis for my original question). Reverting to Server 2016 defaults solved the underlying problem and made everything compliant with HTTP/2 and TLS 1.2. Hooray, enterprise! ◔_◔ – Matt Jacob Jul 03 '18 at 21:21
  • On second thought: there must be something else which has been changed apart from the change you are showing. Changes on the cipher list will not magically change the TLS version offered by the client. So you must have been previously running with either different code which somehow enforced TLS 1.0 with a TLS 1.2 capable OpenSSL. Or the code run with an old version of OpenSSL library which only supports up to TLS 1.0. – Steffen Ullrich Jul 06 '18 at 04:21
  • I got hit by this via [TLS 1.2 enforcement](https://datatracker.ietf.org/doc/html/rfc8996) just now, libs are from 2018 though - fix worked! For reference: `openssl version compiled=0x1000109f linked=0x1000210f -- OpenSSL 1.0.2p-fips 14 Aug 2018 | IO::Socket::SSL version=1.962 | LWP::UserAgent version=6.05 | LWP::Protocol::https version=6.04 | Net::HTTPS version=6.04` – zb226 Oct 25 '21 at 12:02