diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..56915ac --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +LICENSE +META.yml +README +Makefile.PL diff --git a/Build.PL b/Build.PL index 83cb451..572592f 100644 --- a/Build.PL +++ b/Build.PL @@ -18,8 +18,6 @@ 'sign' => 0, 'test_files' => 't/*.t', - - 'add_to_cleanup' => [ 'META.yml', 'Makefile.PL', 'README', 'Makefile', 'LICENSE' ], # automatically generated 'requires' => { # Networking diff --git a/Changes b/Changes index d1532ce..a2579bb 100644 --- a/Changes +++ b/Changes @@ -1,4 +1,8 @@ Revision history for Perl extension POE::Component::SSLify. + +* 0.20 + + Split up the simple.t test into 2 tests for clarity, and added more diag messages for renegotiate, thanks HMBRAND! * 0.19 diff --git a/MANIFEST b/MANIFEST index 32d49d3..a4a7dc3 100644 --- a/MANIFEST +++ b/MANIFEST @@ -19,5 +19,6 @@ mylib/example.key t/1_load.t +t/2_simple.t +t/3_renegotiate.t t/apocalypse.t -t/simple.t diff --git a/POE-Component-SSLify-0.20.tar.gz b/POE-Component-SSLify-0.20.tar.gz new file mode 100644 index 0000000..2bf00c0 Binary files /dev/null and b/POE-Component-SSLify-0.20.tar.gz differ diff --git a/lib/POE/Component/SSLify/ClientHandle.pm b/lib/POE/Component/SSLify/ClientHandle.pm index f02437e..415bb60 100644 --- a/lib/POE/Component/SSLify/ClientHandle.pm +++ b/lib/POE/Component/SSLify/ClientHandle.pm @@ -3,7 +3,7 @@ # Initialize our version use vars qw( $VERSION ); -$VERSION = '0.19'; +$VERSION = '0.20'; # Import the SSL death routines use Net::SSLeay qw( die_now die_if_ssl_error ); diff --git a/lib/POE/Component/SSLify/ServerHandle.pm b/lib/POE/Component/SSLify/ServerHandle.pm index ff00f36..cf2058e 100644 --- a/lib/POE/Component/SSLify/ServerHandle.pm +++ b/lib/POE/Component/SSLify/ServerHandle.pm @@ -3,7 +3,7 @@ # Initialize our version use vars qw( $VERSION ); -$VERSION = '0.19'; +$VERSION = '0.20'; # Import the SSL death routines use Net::SSLeay qw( die_now die_if_ssl_error ); diff --git a/lib/POE/Component/SSLify.pm b/lib/POE/Component/SSLify.pm index 52eb594..2efb59c 100644 --- a/lib/POE/Component/SSLify.pm +++ b/lib/POE/Component/SSLify.pm @@ -3,24 +3,22 @@ # Initialize our version use vars qw( $VERSION ); -$VERSION = '0.19'; +$VERSION = '0.20'; # We need Net::SSLeay or all's a failure! BEGIN { - eval { require Net::SSLeay }; + eval { + require Net::SSLeay; + + # We need >= 1.36 because it contains a lot of important fixes + Net::SSLeay->import( 1.36 ); + }; # Check for errors... if ( $@ ) { # Oh boy! die $@; } else { - # Check to make sure the versions are what we want - # TODO what if Net::SSLeay is upgraded to 1.4? :( - if ( ! ( defined $Net::SSLeay::VERSION and - $Net::SSLeay::VERSION =~ /^1\.3/ ) ) { - warn 'Please upgrade Net::SSLeay to v1.30+ installed: v' . $Net::SSLeay::VERSION; - } - # Finally, load our subclass :) # ClientHandle isa ServerHandle so it will get loaded automatically require POE::Component::SSLify::ClientHandle; @@ -368,9 +366,9 @@ =head3 Net::SSLeay::renegotiate -This function has been tested ( it's in t/simple.t ) but it doesn't work on FreeBSD! I tracked it down to this security advisory: +This function has been tested ( it's in t/3_renegotiate.t ) but it doesn't work on FreeBSD! I tracked it down to this security advisory: L which explains it in detail. The test will skip this function -if it detects that you're on a FreeBSD system. However, if you have the updated OpenSSL library that fixes this you can use it. +if it detects that you're on a broken system. However, if you have the updated OpenSSL library that fixes this you can use it. =head1 FUNCTIONS @@ -502,7 +500,7 @@ You can find documentation for this module with the perldoc command. - perldoc POE::Component::SSLify + perldoc POE::Component::SSLify =head2 Websites diff --git a/t/2_simple.t b/t/2_simple.t new file mode 100644 index 0000000..3fc1615 --- /dev/null +++ b/t/2_simple.t @@ -0,0 +1,157 @@ +#!/usr/bin/perl + +# Thanks to ASCENT for this test! + +use strict; use warnings; + +my $numtests; +BEGIN { + $numtests = 16; + + eval "use Test::NoWarnings"; + if ( ! $@ ) { + # increment by one + $numtests++; + + } +} + +use Test::More tests => $numtests; + +use POE; +use POE::Component::Client::TCP; +use POE::Component::Server::TCP; +use POE::Component::SSLify qw/Client_SSLify Server_SSLify SSLify_Options SSLify_GetCipher SSLify_ContextCreate/; +use Net::SSLeay qw/ERROR_WANT_READ ERROR_WANT_WRITE/; +use POSIX qw/F_GETFL F_SETFL O_NONBLOCK EAGAIN EWOULDBLOCK/; + +# TODO rewrite this to use Test::POE::Server::TCP and stuff :) + +my $port; + +POE::Component::Server::TCP->new +( + Alias => 'myserver', + Address => '127.0.0.1', + Port => 0, + + Started => sub + { + use Socket qw/sockaddr_in/; + $port = (sockaddr_in($_[HEAP]->{listener}->getsockname))[0]; + }, + ClientConnected => sub + { + ok(1, 'SERVER: accepted'); + }, + ClientDisconnected => sub + { + ok(1, 'SERVER: client disconnected'); + $_[KERNEL]->post(myserver => 'shutdown'); + }, + ClientPreConnect => sub + { + eval { SSLify_Options('mylib/example.key', 'mylib/example.crt', 'sslv3') }; + eval { SSLify_Options('../mylib/example.key', '../mylib/example.crt', 'sslv3') } if ($@); + ok(!$@, "SERVER: SSLify_Options $@"); + + my $socket = eval { Server_SSLify($_[ARG0]) }; + ok(!$@, "SERVER: Server_SSLify $@"); + ok(1, 'SERVER: SSLify_GetCipher: '. SSLify_GetCipher($socket)); + + my $flags = fcntl($_[ARG0], F_GETFL, 0); + ok($flags & O_NONBLOCK, 'SERVER: SSLified socket is non-blocking?'); + + return ($socket); + }, + ClientInput => sub + { + my ($kernel, $heap, $request) = @_[KERNEL, HEAP, ARG0]; + + ## At this point, connection MUST be encrypted. + my $cipher = SSLify_GetCipher($heap->{client}->get_output_handle); + ok($cipher ne '(NONE)', "SERVER: SSLify_GetCipher: $cipher"); + + if ($request eq 'ping') + { + ok(1, "SERVER: recv: $request"); + $heap->{client}->put("pong"); + } + }, + ClientError => sub + { + # Thanks to H. Merijn Brand for spotting this FAIL in 5.12.0! + # The default PoCo::Server::TCP handler will throw a warning, which causes Test::NoWarnings to FAIL :( + my ($syscall, $errno, $error) = @_[ ARG0..ARG2 ]; + + # TODO are there other "errors" that is harmless? + $error = "Normal disconnection" unless $error; + my $msg = "Got SERVER $syscall error $errno: $error"; + unless ( $syscall eq 'read' and $errno == 0 ) { + fail( $msg ); + } else { + diag( $msg ) if $ENV{TEST_VERBOSE}; + } + }, +); + +POE::Component::Client::TCP->new +( + Alias => 'myclient', + RemoteAddress => '127.0.0.1', + RemotePort => $port, + Connected => sub + { + ok(1, 'CLIENT: connected'); + + $_[HEAP]->{server}->put("ping"); + }, + PreConnect => sub + { + my $ctx = eval { SSLify_ContextCreate(undef, undef, 'sslv3') }; + ok(!$@, "CLIENT: SSLify_ContextCreate $@"); + my $socket = eval { Client_SSLify($_[ARG0], undef, undef, $ctx) }; + ok(!$@, "CLIENT: Client_SSLify $@"); + ok(1, 'CLIENT: SSLify_GetCipher: '. SSLify_GetCipher($socket)); + + my $flags = fcntl($_[ARG0], F_GETFL, 0); + ok($flags & O_NONBLOCK, 'CLIENT: SSLified socket is non-blocking?'); + + return ($socket); + }, + ServerInput => sub + { + my ($kernel, $heap, $line) = @_[KERNEL, HEAP, ARG0]; + + ## At this point, connection MUST be encrypted. + my $cipher = SSLify_GetCipher($heap->{server}->get_output_handle); + ok($cipher ne '(NONE)', "CLIENT: SSLify_GetCipher: $cipher"); + + if ($line eq 'pong') + { + ok(1, "CLIENT: recv: $line"); + $kernel->yield('shutdown'); + } + }, + ServerError => sub + { + # Thanks to H. Merijn Brand for spotting this FAIL in 5.12.0! + # The default PoCo::Client::TCP handler will throw a warning, which causes Test::NoWarnings to FAIL :( + my ($syscall, $errno, $error) = @_[ ARG0..ARG2 ]; + + # TODO are there other "errors" that is harmless? + $error = "Normal disconnection" unless $error; + my $msg = "Got CLIENT $syscall error $errno: $error"; + unless ( $syscall eq 'read' and $errno == 0 ) { + fail( $msg ); + } else { + diag( $msg ) if $ENV{TEST_VERBOSE}; + } + }, +); + +$poe_kernel->run(); + +pass( 'shut down sanely' ); + +exit 0; diff --git a/t/3_renegotiate.t b/t/3_renegotiate.t new file mode 100644 index 0000000..6512c1b --- /dev/null +++ b/t/3_renegotiate.t @@ -0,0 +1,211 @@ +#!/usr/bin/perl + +# Thanks to ASCENT for this test! + +# This test adds renegotiation to the connection +# Since this is not supported on all platforms, it's marked TODO and adds custom logic +# to make sure it doesn't FAIL if it's not supported. + +use strict; use warnings; + +my $numtests; +BEGIN { + $numtests = 23; + + eval "use Test::NoWarnings"; + if ( ! $@ ) { + # increment by one + $numtests++; + + } +} + +use Test::More tests => $numtests; + +use POE; +use POE::Component::Client::TCP; +use POE::Component::Server::TCP; +use POE::Component::SSLify qw/Client_SSLify Server_SSLify SSLify_Options SSLify_GetCipher SSLify_ContextCreate/; +use Net::SSLeay qw/ERROR_WANT_READ ERROR_WANT_WRITE/; +use POSIX qw/F_GETFL F_SETFL O_NONBLOCK EAGAIN EWOULDBLOCK/; + +# TODO rewrite this to use Test::POE::Server::TCP and stuff :) + +my $port; +my $server_ping2; +my $client_ping2; + +POE::Component::Server::TCP->new +( + Alias => 'myserver', + Address => '127.0.0.1', + Port => 0, + + Started => sub + { + use Socket qw/sockaddr_in/; + $port = (sockaddr_in($_[HEAP]->{listener}->getsockname))[0]; + }, + ClientConnected => sub + { + ok(1, 'SERVER: accepted'); + }, + ClientDisconnected => sub + { + ok(1, 'SERVER: client disconnected'); + $_[KERNEL]->post(myserver => 'shutdown'); + }, + ClientPreConnect => sub + { + eval { SSLify_Options('mylib/example.key', 'mylib/example.crt', 'sslv3') }; + eval { SSLify_Options('../mylib/example.key', '../mylib/example.crt', 'sslv3') } if ($@); + ok(!$@, "SERVER: SSLify_Options $@"); + + my $socket = eval { Server_SSLify($_[ARG0]) }; + ok(!$@, "SERVER: Server_SSLify $@"); + ok(1, 'SERVER: SSLify_GetCipher: '. SSLify_GetCipher($socket)); + + my $flags = fcntl($_[ARG0], F_GETFL, 0); + ok($flags & O_NONBLOCK, 'SERVER: SSLified socket is non-blocking?'); + + return ($socket); + }, + ClientInput => sub + { + my ($kernel, $heap, $request) = @_[KERNEL, HEAP, ARG0]; + + ## At this point, connection MUST be encrypted. + my $cipher = SSLify_GetCipher($heap->{client}->get_output_handle); + ok($cipher ne '(NONE)', "SERVER: SSLify_GetCipher: $cipher"); + + if ($request eq 'ping') + { + ok(1, "SERVER: recv: $request"); + $heap->{client}->put("pong"); + } + elsif ($request eq 'ping2') + { + ok(1, "SERVER: recv: $request"); + $server_ping2++; + $heap->{client}->put("pong2"); + } + }, + ClientError => sub + { + # Thanks to H. Merijn Brand for spotting this FAIL in 5.12.0! + # The default PoCo::Server::TCP handler will throw a warning, which causes Test::NoWarnings to FAIL :( + my ($syscall, $errno, $error) = @_[ ARG0..ARG2 ]; + + # TODO are there other "errors" that is harmless? + $error = "Normal disconnection" unless $error; + my $msg = "Got SERVER $syscall error $errno: $error"; + unless ( $syscall eq 'read' and $errno == 0 ) { + fail( $msg ); + } else { + diag( $msg ) if $ENV{TEST_VERBOSE}; + } + }, +); + +POE::Component::Client::TCP->new +( + Alias => 'myclient', + RemoteAddress => '127.0.0.1', + RemotePort => $port, + Connected => sub + { + ok(1, 'CLIENT: connected'); + + $_[HEAP]->{server}->put("ping"); + }, + PreConnect => sub + { + my $ctx = eval { SSLify_ContextCreate(undef, undef, 'sslv3') }; + ok(!$@, "CLIENT: SSLify_ContextCreate $@"); + my $socket = eval { Client_SSLify($_[ARG0], undef, undef, $ctx) }; + ok(!$@, "CLIENT: Client_SSLify $@"); + ok(1, 'CLIENT: SSLify_GetCipher: '. SSLify_GetCipher($socket)); + + my $flags = fcntl($_[ARG0], F_GETFL, 0); + ok($flags & O_NONBLOCK, 'CLIENT: SSLified socket is non-blocking?'); + + return ($socket); + }, + ServerInput => sub + { + my ($kernel, $heap, $line) = @_[KERNEL, HEAP, ARG0]; + + ## At this point, connection MUST be encrypted. + my $cipher = SSLify_GetCipher($heap->{server}->get_output_handle); + ok($cipher ne '(NONE)', "CLIENT: SSLify_GetCipher: $cipher"); + + if ($line eq 'pong') + { + ok(1, "CLIENT: recv: $line"); + + # Skip 2 Net::SSLeay::renegotiate() tests on FreeBSD because of + # http://security.freebsd.org/advisories/FreeBSD-SA-09:15.ssl.asc + TODO: { + local $TODO = "Net::SSLeay::renegotiate() does not work on all platforms"; + + ## Force SSL renegotiation + my $ssl = tied(*{$heap->{server}->get_output_handle})->{ssl}; + my $reneg_num = Net::SSLeay::num_renegotiations($ssl); + + ok(1 == Net::SSLeay::renegotiate($ssl), 'CLIENT: SSL renegotiation'); + my $handshake = Net::SSLeay::do_handshake($ssl); + my $err = Net::SSLeay::get_error($ssl, $handshake); + + ## 1 == Successful handshake, ERROR_WANT_(READ|WRITE) == non-blocking. + ok($handshake == 1 || $err == ERROR_WANT_READ || $err == ERROR_WANT_WRITE, 'CLIENT: SSL handshake'); + ok($reneg_num < Net::SSLeay::num_renegotiations($ssl), 'CLIENT: Increased number of negotiations'); + } + + $heap->{server}->put('ping2'); + } + + elsif ($line eq 'pong2') + { + ok(1, "CLIENT: recv: $line"); + $client_ping2++; + $kernel->yield('shutdown'); + } + }, + ServerError => sub + { + # Thanks to H. Merijn Brand for spotting this FAIL in 5.12.0! + # The default PoCo::Client::TCP handler will throw a warning, which causes Test::NoWarnings to FAIL :( + my ($syscall, $errno, $error) = @_[ ARG0..ARG2 ]; + + # TODO are there other "errors" that is harmless? + $error = "Normal disconnection" unless $error; + my $msg = "Got CLIENT $syscall error $errno: $error"; + unless ( $syscall eq 'read' and $errno == 0 ) { + fail( $msg ); + } else { + diag( $msg ) if $ENV{TEST_VERBOSE}; + } + }, +); + +$poe_kernel->run(); + +# Add extra pass() to make the test harness happy if renegotiate did not work +if ( ! $server_ping2 ) { + local $TODO = "Net::SSLeay::renegotiate() does not work on all platforms"; + fail( "SERVER: Failed SSL renegotiation" ); +} +if ( ! $client_ping2 ) { + local $TODO = "Net::SSLeay::renegotiate() does not work on all platforms"; + fail( "CLIENT: Failed SSL renegotiation" ); +} +if ( ! $server_ping2 or ! $client_ping2 ) { + diag( "WARNING: Your platform/SSL library does not support renegotiation of the SSL socket." ); + diag( "This test harness detected that trying to renegotiate resulted in a disconnected socket." ); + diag( "POE::Component::SSLify will work on your system, but please do not attempt a SSL renegotiate." ); + diag( "Please talk with the author to figure out if this issue can be worked around, thank you!" ); +} + +pass( 'shut down sanely' ); + +exit 0; diff --git a/t/simple.t b/t/simple.t deleted file mode 100644 index cb6afc5..0000000 --- a/t/simple.t +++ /dev/null @@ -1,184 +0,0 @@ -#!/usr/bin/perl - -# Thanks to ASCENT for this test! - -use strict; use warnings; - -my $numtests; -BEGIN { - $numtests = 22; - - eval "use Test::NoWarnings"; - if ( ! $@ ) { - # increment by one - $numtests++; - - } -} - -use Test::More tests => $numtests; - -use POE; -use POE::Component::Client::TCP; -use POE::Component::Server::TCP; -use POE::Component::SSLify qw/Client_SSLify Server_SSLify SSLify_Options SSLify_GetCipher SSLify_ContextCreate/; -use Net::SSLeay qw/ERROR_WANT_READ ERROR_WANT_WRITE/; -use POSIX qw/F_GETFL F_SETFL O_NONBLOCK EAGAIN EWOULDBLOCK/; - -# TODO rewrite this to use Test::POE::Server::TCP and stuff :) - -my $port; - -POE::Component::Server::TCP->new -( - Alias => 'myserver', - Address => '127.0.0.1', - Port => 0, - - Started => sub - { - use Socket qw/sockaddr_in/; - $port = (sockaddr_in($_[HEAP]->{listener}->getsockname))[0]; - }, - ClientConnected => sub - { - ok(1, 'SERVER: accepted'); - }, - ClientDisconnected => sub - { - ok(1, 'SERVER: client disconnected'); - $_[KERNEL]->post(myserver => 'shutdown'); - }, - ClientPreConnect => sub - { - eval { SSLify_Options('mylib/example.key', 'mylib/example.crt', 'sslv3') }; - eval { SSLify_Options('../mylib/example.key', '../mylib/example.crt', 'sslv3') } if ($@); - ok(!$@, "SERVER: SSLify_Options $@"); - - my $socket = eval { Server_SSLify($_[ARG0]) }; - ok(!$@, "SERVER: Server_SSLify $@"); - ok(1, 'SERVER: SSLify_GetCipher: '. SSLify_GetCipher($socket)); - - my $flags = fcntl($_[ARG0], F_GETFL, 0); - ok($flags & O_NONBLOCK, 'SERVER: SSLified socket is non-blocking?'); - - return ($socket); - }, - ClientInput => sub - { - my ($kernel, $heap, $request) = @_[KERNEL, HEAP, ARG0]; - - ## At this point, connection MUST be encrypted. - my $cipher = SSLify_GetCipher($heap->{client}->get_output_handle); - ok($cipher ne '(NONE)', "SERVER: SSLify_GetCipher: $cipher"); - - if ($request eq 'ping') - { - ok(1, "SERVER: recv: $request"); - $heap->{client}->put("pong"); - } - elsif ($request eq 'ping2') - { - ok(1, "SERVER: recv: $request"); - $heap->{client}->put("pong2"); - } - }, - ClientError => sub - { - # Thanks to H. Merijn Brand for spotting this FAIL in 5.12.0! - # The default PoCo::Server::TCP handler will throw a warning, which causes Test::NoWarnings to FAIL :( - my ($syscall, $errno, $error) = @_[ ARG0..ARG2 ]; - - # TODO are there other "errors" that is harmless? - $error = "Normal disconnection" unless $error; - my $msg = "Got SERVER $syscall error $errno: $error"; - unless ( $syscall eq 'read' and $errno == 0 ) { - fail( $msg ); - } else { - diag( $msg ); - } - }, -); - -POE::Component::Client::TCP->new -( - Alias => 'myclient', - RemoteAddress => '127.0.0.1', - RemotePort => $port, - Connected => sub - { - ok(1, 'CLIENT: connected'); - - $_[HEAP]->{server}->put("ping"); - }, - PreConnect => sub - { - my $ctx = eval { SSLify_ContextCreate(undef, undef, 'sslv3') }; - ok(!$@, "CLIENT: SSLify_ContextCreate $@"); - my $socket = eval { Client_SSLify($_[ARG0], undef, undef, $ctx) }; - ok(!$@, "CLIENT: Client_SSLify $@"); - ok(1, 'CLIENT: SSLify_GetCipher: '. SSLify_GetCipher($socket)); - - my $flags = fcntl($_[ARG0], F_GETFL, 0); - ok($flags & O_NONBLOCK, 'CLIENT: SSLified socket is non-blocking?'); - - return ($socket); - }, - ServerInput => sub - { - my ($kernel, $heap, $line) = @_[KERNEL, HEAP, ARG0]; - - ## At this point, connection MUST be encrypted. - my $cipher = SSLify_GetCipher($heap->{server}->get_output_handle); - ok($cipher ne '(NONE)', "CLIENT: SSLify_GetCipher: $cipher"); - - if ($line eq 'pong') - { - ok(1, "CLIENT: recv: $line"); - - # Skip 2 Net::SSLeay::renegotiate() tests on FreeBSD because of - # http://security.freebsd.org/advisories/FreeBSD-SA-09:15.ssl.asc - TODO: { - local $TODO = "Net::SSLeay::renegotiate() does not work on all platforms"; - - ## Force SSL renegotiation - my $ssl = tied(*{$heap->{server}->get_output_handle})->{ssl}; - my $reneg_num = Net::SSLeay::num_renegotiations($ssl); - - ok(1 == Net::SSLeay::renegotiate($ssl), 'CLIENT: SSL renegotiation'); - my $handshake = Net::SSLeay::do_handshake($ssl); - my $err = Net::SSLeay::get_error($ssl, $handshake); - - ## 1 == Successful handshake, ERROR_WANT_(READ|WRITE) == non-blocking. - ok($handshake == 1 || $err == ERROR_WANT_READ || $err == ERROR_WANT_WRITE, 'CLIENT: SSL handshake'); - ok($reneg_num < Net::SSLeay::num_renegotiations($ssl), 'CLIENT: Increased number of negotiations'); - } - - $heap->{server}->put('ping2'); - } - - elsif ($line eq 'pong2') - { - ok(1, "CLIENT: recv: $line"); - $kernel->yield('shutdown'); - } - }, - ServerError => sub - { - # Thanks to H. Merijn Brand for spotting this FAIL in 5.12.0! - # The default PoCo::Client::TCP handler will throw a warning, which causes Test::NoWarnings to FAIL :( - my ($syscall, $errno, $error) = @_[ ARG0..ARG2 ]; - - # TODO are there other "errors" that is harmless? - $error = "Normal disconnection" unless $error; - my $msg = "Got CLIENT $syscall error $errno: $error"; - unless ( $syscall eq 'read' and $errno == 0 ) { - fail( $msg ); - } else { - diag( $msg ); - } - }, -); - -$poe_kernel->run(); -exit 0;