10

I'm porting an application from PHP/cURL to Perl and LWP::UserAgent. I need to do a POST request to a web server and provide a client certificate and key file. The PHP code I'm trying to replicate is this:

curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0); 
curl_setopt($ch, CURLOPT_SSLCERT, "/path/to/certificate.pem"); 
curl_setopt($ch, CURLOPT_SSLKEY, "/path/to/private.key"); 
curl_setopt($ch, CURLOPT_SSLKEYPASSWD, "secretpassword");

And here's my Perl code:

my $ua = LWP::UserAgent->new();
$ua->ssl_opts(
   SSL_verify_mode => 0,
   SSL_cert_file   => '/path/to/certificate.pem',
   SSL_key_file    => "/path/to/private.key",
   SSL_passwd_cb   => sub { return "secretpassword"; }  
);

The PHP code successfully connects to the server but the Perl code fails with:

SSL read error error:14094410:SSL routines:SSL3_READ_BYTES:sslv3 alert handshake failure

I can't figure out what I'm missing.

kent
  • 195
  • 1
  • 3
  • 11
  • 1
    Is the `private.key` (PHP) and `private.pem` (Perl) a typo or part of the porting? – amon Oct 02 '12 at 20:24
  • 2
    Passing multiple arguments to [ssl_opts](https://metacpan.org/module/LWP::UserAgent#ATTRIBUTES) is not documented. To be safe, either call ssl_opts multiple times or pass them into the constructor. I think it happens to work, but better safe. You could also [report it as a bug/missing feature](https://rt.cpan.org/Public/Bug/Report.html?Queue=libwww-perl). – Schwern Oct 02 '12 at 23:40
  • That was just a typo when I obscured my code. It should be private.key for both. Thanks for noticing but that's not the problem. Still need help! :) – kent Oct 03 '12 at 17:19
  • I updated my code to call ssl_opts multiple times. Still no glory. Thanks for the suggestion. – kent Oct 03 '12 at 17:28
  • 3
    Have you tried `use Net::SSL ();`? That would force LWP to use `Net::SSL` (from `Crypt::SSLeay`) instead of `IO::Socket::SSL`. – emazep Oct 09 '12 at 01:58

3 Answers3

2
sub send_command(){
        my $command = shift;
        my $parser = XML::LibXML->new('1.0','utf-8');

        print color ("on_yellow"), "SEND: ", $command, color ("reset"), "\n";

        # Create a request
        my $req = HTTP::Request->new( GET => $Gateway.$command );

        # Pass request to the user agent and get a response back
        my $res;
        eval {
                 my $ua;
                 local $SIG{'__DIE__'};
                 $ua = LWP::UserAgent->new(); #  или 
                 $ua->ssl_opts( #$key => $value 
                    SSL_version         => 'SSLv3',
                    SSL_ca_file         => '/ca.pem',
                    #SSL_passwd_cb       => sub { return "xxxxx\n"; },
                    SSL_cert_file       => '/test_test_cert.pem',
                    SSL_key_file        => '/test_privkey_nopassword.pem',
                ); # ssl_opts => { verify_hostname => 0 }
                 $ua->agent("xxxxxx xxxx_tester.pl/0.1 ");
                 $res = $ua->request($req);

        };  
        warn $@ if $@;
        # Check the outcome of the response
        if ( $res->is_success ) {
                open  xxxLOG, ">> $dir/XXXX_tester.log";
                my $without_lf = $res->content;
                $without_lf =~ s/(\r|\n)//gm;
                print PAYLOG $without_lf,"\n";
                close PAYLOG;
        }
        else {
                 return $res->status_line;
        }       
        print  color ("on_blue"), "RESPONSE: ", color ("reset"), respcode_color($res->content), color ("reset"),"\n\n";
        return $res->content;
} 
nexoma
  • 275
  • 4
  • 10
2

The answer from emazep above solved my problem. I'm using the sample Perl code from UPS to connect to their Rate service via XML. From my tests, this will work any time LWP::UserAgent is being called without arguments that you can control directly, which makes it handy if you're using some other module which makes calls to LWP for you. Just use Net::SSL (in addition to whatever packages have already used LWP) and set a few environment variables:

...
use Net::SSL;
$ENV{HTTPS_VERSION} = 3;
$ENV{PERL_LWP_SSL_VERIFY_HOSTNAME} = 0;
my $browser = LWP::UserAgent->new();
...

That's it! You shouldn't even need to specify the path to your server's root certificate with $ENV{PERL_LWP_SSL_CA_FILE}.

goddogsrunning
  • 1,099
  • 8
  • 8
  • -1, sorry, for the recommendation to use `$ENV{PERL_LWP_SSL_VERIFY_HOSTNAME} = 0`. That setting disables the logic to make sure the server you're talking to is actually the desired endpoint; so although the URL will still start with "https", the connection doesn't really have the security properties of HTTPS. (There are sometimes valid uses for that setting, but you shouldn't recommend it without good reason, and even then you need to warn about the problems.) – ruakh May 22 '21 at 00:05
1

Indeed this is a messy bit. Depending on your setup LWP::UserAgent may use one of (at least) two SSL modules to handle the SSL connection.

  • IO::Socket::SSL
  • Net::SSL

The first one should be the default for newer versions of LWP::UserAgent. You can test which of these are installed by running the standard command in a terminal for each module:

perl -e 'use <module>;'

IO::socket::SSL requires the SSL configuration with the ssl_opts as in your example.

Net::SSL requires the SSL configuration in environment variables as in goddogsrunnings answer.

Personally I fall in the second category and had good inspiration from the Crypt::SSLeay page. Particularly the section named "CLIENT CERTIFICATE SUPPORT ".

Phluks
  • 906
  • 1
  • 9
  • 14