2011/6/28 Gonéri Le Bouder <[email protected]>: > 2011/6/28 Guillaume Rousse <[email protected]> >> Le 28/06/2011 11:57, Gonéri Le Bouder a écrit : >> > 2011/6/28 Guillaume Rousse <[email protected]
> This means we need to rebuilt the perl tree on the =~ 60 arch we already > have. > This will be long, say 1 year. That's the reason why I prefer a soft > transition. The attached patch restore Net::SSL. The big difference is IO::Socket::SSL check hostname directly within OpenSSL whereas Net::SSL don't. We have to do this ourself which is error-prone. An interesting point with this patch is OpenSSL stuff are loaded only when needed. This is more than 10MB memory saved here on my system. Guillaume, If you have no objection, I will apply it and then backport it in 2.1.x branch. tosh-r630:~/fusioninventory/agent (2.2.x+netssl)$perl -Ilib ./t/components/client/ssl.t 1..14 ok 1 - trusted certificate, correct hostname: connection success (IO::Socket::SSL) ok 2 - trusted certificate, correct hostname: connection success (Net::SSL) ok 3 - trusted certificate, alternate hostname: connection success (IO::Socket::SSL) ok 4 # skip Alternate hostname is broken with Net::SSL/Crypt::SSLeay ok 5 - trusted certificate, joker: connection succes (IO::Socket::SSL) ok 6 - trusted certificate, joker: connection success (Net::SSL) ok 7 - trusted certificate, wrong hostname: connection failure (IO::Socket::SSL) ok 8 - trusted certificate, wrong hostname: connection failure (Net::SSL) ok 9 - trusted certificate, wrong hostname, no check: connection success (IO::Socket::SSL) ok 10 - trusted certificate, wrong hostname, no check: connection success (Net::SSL) ok 11 - untrusted certificate, correct hostname: connection failure (IO::Socket::SSL) ok 12 - untrusted certificate, correct hostname: connection failure (Net::SSL) ok 13 - untrusted certificate, correct hostname, no check: connection success (IO::Socket::SSL) ok 14 - untrusted certificate, correct hostname, no check: connection success (Net::SSL) Best regards, -- Gonéri Le Bouder
From 9b9b172c6be07cc0755ab983cdb339a7d90e98a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A9ri=20Le=20Bouder?= <[email protected]> Date: Wed, 10 Aug 2011 23:47:26 +0200 Subject: [PATCH] restore Net::SSL+Crypt::SSL support This option has some limitation what's why IO::Socket::SSL remains the best option: - No alternate hostname support - Hostname validation has to be done manually --- lib/FusionInventory/Agent/HTTP/Client.pm | 118 +++++++++++++++++++++++------ t/components/client/ssl.t | 67 +++++++++++++++-- 2 files changed, 152 insertions(+), 33 deletions(-) diff --git a/lib/FusionInventory/Agent/HTTP/Client.pm b/lib/FusionInventory/Agent/HTTP/Client.pm index 0c68bc2..4ff6da4 100644 --- a/lib/FusionInventory/Agent/HTTP/Client.pm +++ b/lib/FusionInventory/Agent/HTTP/Client.pm @@ -20,14 +20,21 @@ sub new { if $params{ca_cert_dir} && ! -d $params{ca_cert_dir}; my $self = { - logger => $params{logger} || - FusionInventory::Agent::Logger->new(), - user => $params{user}, - password => $params{password}, - timeout => $params{timeout} || 180 + logger => $params{logger} || + FusionInventory::Agent::Logger->new(), + user => $params{user}, + password => $params{password}, + timeout => $params{timeout} || 180, + + no_ssl_check => $params{'no_ssl_check'}, + ssl_socket_class => $params{ssl_socket_class} || "IO::Socket::SSL", + + ca_cert_file => $params{ca_cert_file}, + ca_cert_dir => $params{ca_cert_dir}, }; bless $self, $class; +# $Net::HTTPS::SSL_SOCKET_CLASS = $self->{ssl_socket_class}; # create user agent $self->{ua} = LWP::UserAgent->new(keep_alive => 1, requests_redirectable => ['POST', 'GET', 'HEAD']); @@ -37,32 +44,39 @@ sub new { $self->{ua}->env_proxy; } + + $self->{ua}->agent($FusionInventory::Agent::AGENT_STRING); + $self->{ua}->timeout($params{timeout}); + + return $self; +} + +sub _turnSSLOn { + my ($self) = @_; + + # Already loaded? + return if $self->{ssl_on}; + + $ENV{PERL_NET_HTTPS_SSL_SOCKET_CLASS}=$self->{ssl_socket_class}; # SSL handling - if ($params{'no_ssl_check'}) { + if ($self->{'no_ssl_check'}) { if ($LWP::VERSION >= 6) { # LWP6 default behavior is to check the SSL hostname $self->{ua}->ssl_opts(verify_hostname => 0); } - } else { - # only IO::Socket::SSL can perform full server certificate validation, - # Net::SSL is only able to check certification authority, and not - # certificate hostname - IO::Socket::SSL->require(); - die - "failed to load IO::Socket::SSL" . - ", unable to perform SSL certificate validation" - if $EVAL_ERROR; + } elsif ($self->{ssl_socket_class} ne 'Net::SSL' && IO::Socket::SSL->require() && !$EVAL_ERROR) { + $self->{ssl_socket_class} = "IO::Socket::SSL"; if ($LWP::VERSION >= 6) { - $self->{ua}->ssl_opts(SSL_ca_file => $params{'ca_cert_file'}) - if $params{'ca_cert_file'}; - $self->{ua}->ssl_opts(SSL_ca_path => $params{'ca_cert_dir'}) - if $params{'ca_cert_dir'}; + $self->{ua}->ssl_opts(SSL_ca_file => $self->{'ca_cert_file'}) + if $self->{'ca_cert_file'}; + $self->{ua}->ssl_opts(SSL_ca_path => $self->{'ca_cert_dir'}) + if $self->{'ca_cert_dir'}; } else { # use a custom HTTPS handler to workaround default LWP5 behaviour FusionInventory::Agent::HTTP::Protocol::https->use( - ca_cert_file => $params{'ca_cert_file'}, - ca_cert_dir => $params{'ca_cert_dir'}, + ca_cert_file => $self->{'ca_cert_file'}, + ca_cert_dir => $self->{'ca_cert_dir'}, ); die "failed to load FusionInventory::Agent::HTTP::Protocol::https" . @@ -75,14 +89,48 @@ sub new { # abuse user agent internal to pass values to the handler, so # as to have different behaviors in the same process - $self->{ua}->{ssl_check} = $params{'no_ssl_check'} ? 0 : 1; + $self->{ua}->{ssl_check} = $self->{'no_ssl_check'} ? 0 : 1; } + } elsif (Crypt::SSLeay->require() && !$EVAL_ERROR) { + # This option has some limitation what's why IO::Socket::SSL + # remains the best option: + # - No alternate hostname support + # - Hostname validation has to be done manually + $self->{ssl_socket_class} = "Net::SSL"; + if ($LWP::VERSION >= 6) { + $self->{ua}->ssl_opts(SSL_ca_file => $self->{'ca_cert_file'}) + if $self->{'ca_cert_file'}; + $self->{ua}->ssl_opts(SSL_ca_path => $self->{'ca_cert_dir'}) + if $self->{'ca_cert_dir'}; + # Net::SSL+Crypt::SSLeay are not able to turn OpenSSL internal + # hostname check. We check that ourself with If-SSL-Cert-Subject + # after. For the moment we turn verify_hostname off + $self->{ua}->ssl_opts(verify_hostname => 0); + $self->{ua}->ssl_opts(SSL_verify_mode => 1); +# $self->{ua}->ssl_opts(SSL_verifycn_scheme => 'www'); + + } + $ENV{HTTPS_CA_FILE} = $self->{'ca_cert_file'} + if $self->{'ca_cert_file'}; + $ENV{HTTPS_CA_DIR} = $self->{'ca_cert_dir'} + if $self->{'ca_cert_dir'}; + + + # abuse user agent internal to pass values to the handler, so + # as to have different behaviors in the same process + $self->{ua}->{ssl_check} = $self->{'no_ssl_check'} ? 0 : 1; + + + } else { + die + "failed to load IO::Socket::SSL or Crypt::SSLeay" . + ", unable to perform SSL certificate validation" } - $self->{ua}->agent($FusionInventory::Agent::AGENT_STRING); - $self->{ua}->timeout($params{timeout}); + $self->{logger}->debug("SSL: ".$self->{ssl_socket_class}." loaded"); + + $self->{ssl_on} = 1; - return $self; } sub request { @@ -94,6 +142,26 @@ sub request { my $url = $request->uri(); my $scheme = $url->scheme(); + if ($scheme eq 'https') { + $self->_turnSSLOn(); +# Restore the initial socket class. Needed by the test-suite +# $Net::HTTPS::SSL_SOCKET_CLASS = $self->{ssl_socket_class}; + $ENV{PERL_NET_HTTPS_SSL_SOCKET_CLASS}=$self->{ssl_socket_class}; + $self->{ua}->default_header(''); +# $self->{ua}->ssl_opts(SSL_verifycn_scheme => undef); + if ( (!$self->{no_ssl_check}) && $self->{ssl_socket_class} eq 'Net::SSL' && $url =~ /^https:\/\/([^\/]+).*$/i ) { + my $re = $1; +# Accept SSL cert will hostname with wild-card +# http://forge.fusioninventory.org/issues/542 + $re =~ s/^([^\.]+)/($1|\\*)/; +# protect some characters, $re will be evaluated as a regex + $re =~ s/([\-\.])/\\$1/g; +# Drop the port numbers + $re =~ s/:\d+//g; + $self->{ua}->default_header('If-SSL-Cert-Subject' => '/CN='.$re.'($|\/)'); + } + } + my $result; eval { if ($OSNAME eq 'MSWin32' && $scheme eq 'https') { diff --git a/t/components/client/ssl.t b/t/components/client/ssl.t index 9851761..ba0e137 100755 --- a/t/components/client/ssl.t +++ b/t/components/client/ssl.t @@ -9,13 +9,17 @@ use Socket; use Test::More; use Test::Exception; +use UNIVERSAL::require; + use FusionInventory::Agent::HTTP::Client; use FusionInventory::Test::Server; +my $doNetSSL = Net::SSL->require; + if ($OSNAME eq 'MSWin32' || $OSNAME eq 'darwin') { plan skip_all => 'non working test on Windows and MacOS'; } else { - plan tests => 7; + plan tests => 8 + ($doNetSSL?6:0); } my $ok = sub { @@ -43,6 +47,17 @@ my $secure_client = FusionInventory::Agent::HTTP::Client->new( logger => $logger, ca_cert_file => 't/ssl/crt/ca.pem', ); +my $unsafe_client_net_ssl = FusionInventory::Agent::HTTP::Client->new( + logger => $logger, + no_ssl_check => 1, + ssl_socket_class => 'Net::SSL' +) if $doNetSSL; +my $secure_client_net_ssl = FusionInventory::Agent::HTTP::Client->new( + logger => $logger, + ca_cert_file => 't/ssl/crt/ca.pem', + ssl_socket_class => 'Net::SSL' +) if $doNetSSL; + # ensure the server get stopped even if an exception is thrown $SIG{__DIE__} = sub { $server->stop(); }; @@ -64,8 +79,13 @@ $server->background(); ok( $secure_client->request(HTTP::Request->new(GET => $url))->is_success(), - 'trusted certificate, correct hostname: connection success' + 'trusted certificate, correct hostname: connection success (IO::Socket::SSL)' ); +ok( + $secure_client_net_ssl->request(HTTP::Request->new(GET => $url))->is_success(), + 'trusted certificate, correct hostname: connection success (Net::SSL)' +) if $doNetSSL; + $server->stop(); @@ -86,8 +106,16 @@ $server->background(); ok( $secure_client->request(HTTP::Request->new(GET => $url))->is_success(), - 'trusted certificate, alternate hostname: connection success' + 'trusted certificate, alternate hostname: connection success (IO::Socket::SSL)' ); +# Alternate hostname is broken with Net::SSL +SKIP: { + skip "Alternate hostname is broken with Net::SSL/Crypt::SSLeay", 1; + ok( + $secure_client_net_ssl->request(HTTP::Request->new(GET => $url))->is_success(), + 'trusted certificate, alternate hostname: connection success (Net::SSL)' + ); +} $server->stop(); @@ -114,8 +142,14 @@ SKIP: { $secure_client->request( HTTP::Request->new(GET => 'https://localhost.localdomain:8080/public') )->is_success(), - 'trusted certificate, joker: connection success' + 'trusted certificate, joker: connection succes (IO::Socket::SSL)' ); + ok( + $secure_client_net_ssl->request( + HTTP::Request->new(GET => 'https://localhost.localdomain:8080/public') + )->is_success(), + 'trusted certificate, joker: connection success (Net::SSL)' + ) if $doNetSSL; $server->stop(); } @@ -137,13 +171,22 @@ $server->background(); ok( !$secure_client->request(HTTP::Request->new(GET => $url))->is_success(), - 'trusted certificate, wrong hostname: connection failure' + 'trusted certificate, wrong hostname: connection failure (IO::Socket::SSL)' ); +ok( + !$secure_client_net_ssl->request(HTTP::Request->new(GET => $url))->is_success(), + 'trusted certificate, wrong hostname: connection failure (Net::SSL)' +) if $doNetSSL; ok( $unsafe_client->request(HTTP::Request->new(GET => $url))->is_success(), - 'trusted certificate, wrong hostname, no check: connection success' + 'trusted certificate, wrong hostname, no check: connection success (IO::Socket::SSL)' ); +ok( + $unsafe_client_net_ssl->request(HTTP::Request->new(GET => $url))->is_success(), + 'trusted certificate, wrong hostname, no check: connection success (Net::SSL)' +) if $doNetSSL; + $server->stop(); @@ -164,13 +207,21 @@ $server->background(); ok( !$secure_client->request(HTTP::Request->new(GET => $url))->is_success(), - 'untrusted certificate, correct hostname: connection failure' + 'untrusted certificate, correct hostname: connection failure (IO::Socket::SSL)' ); +ok( + !$secure_client_net_ssl->request(HTTP::Request->new(GET => $url))->is_success(), + 'untrusted certificate, correct hostname: connection failure (Net::SSL)' +) if $doNetSSL; ok( $unsafe_client->request(HTTP::Request->new(GET => $url))->is_success(), - 'untrusted certificate, correct hostname, no check: connection success' + 'untrusted certificate, correct hostname, no check: connection success (IO::Socket::SSL)' ); +ok( + $unsafe_client_net_ssl->request(HTTP::Request->new(GET => $url))->is_success(), + 'untrusted certificate, correct hostname, no check: connection success (Net::SSL)' +) if $doNetSSL; $server->stop(); -- 1.7.5.4
_______________________________________________ Fusioninventory-devel mailing list [email protected] http://lists.alioth.debian.org/cgi-bin/mailman/listinfo/fusioninventory-devel
