--- SSL.pm.1396971177.bak 2014-03-07 22:07:51.000000000 +0200 +++ SSL.pm 2015-01-13 08:55:01.000000000 +0200 @@ -4,7 +4,7 @@ use Net::SSLeay; use POE qw (Filter::HTTPD Filter::Stackable Wheel::ReadWrite); use Scalar::Util qw(blessed); -use Carp qw(carp); +use Carp qw(carp croak); use POE; use vars qw($VERSION @ISA); @@ -24,9 +24,6 @@ require Net::SSLeay; Net::SSLeay->import( 1.30 ); }; - Net::SSLeay::load_error_strings(); - Net::SSLeay::SSLeay_add_ssl_algorithms(); - Net::SSLeay::randomize(); no warnings 'redefine'; my $old_new = \&POE::Wheel::ReadWrite::new; @@ -199,6 +196,166 @@ require XSLoader; XSLoader::load('POE::Filter::SSL', $VERSION); +use constant SSL_VERIFY_NONE => Net::SSLeay::VERIFY_NONE(); +use constant SSL_VERIFY_PEER => Net::SSLeay::VERIFY_PEER(); +use constant SSL_VERIFY_FAIL_IF_NO_PEER_CERT => Net::SSLeay::VERIFY_FAIL_IF_NO_PEER_CERT(); +use constant SSL_VERIFY_CLIENT_ONCE => Net::SSLeay::VERIFY_CLIENT_ONCE(); + +# from openssl/ssl.h; should be better in Net::SSLeay +use constant SSL_SENT_SHUTDOWN => 1; +use constant SSL_RECEIVED_SHUTDOWN => 2; + +use constant SSL_OCSP_NO_STAPLE => 0b00001; +use constant SSL_OCSP_MUST_STAPLE => 0b00010; +use constant SSL_OCSP_FAIL_HARD => 0b00100; +use constant SSL_OCSP_FULL_CHAIN => 0b01000; +use constant SSL_OCSP_TRY_STAPLE => 0b10000; + +use constant SSL_MODE_ENABLE_PARTIAL_WRITE => 1; +use constant SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER => 2; + +# capabilities of underlying Net::SSLeay/openssl +my $can_client_sni; # do we support SNI on the client side +my $can_server_sni; # do we support SNI on the server side +my $can_npn; # do we support NPN +my $can_ecdh; # do we support ECDH key exchange +my $can_ocsp; # do we support OCSP +my $can_ocsp_staple; # do we support OCSP stapling +BEGIN { + $can_client_sni = Net::SSLeay::OPENSSL_VERSION_NUMBER() >= 0x01000000; + $can_server_sni = defined &Net::SSLeay::get_servername; + $can_npn = defined &Net::SSLeay::P_next_proto_negotiated; + $can_ecdh = defined &Net::SSLeay::CTX_set_tmp_ecdh && + # There is a regression with elliptic curves on 1.0.1d with 64bit + # http://rt.openssl.org/Ticket/Display.html?id=2975 + ( Net::SSLeay::OPENSSL_VERSION_NUMBER() != 0x1000104f + || length(pack('P', 0)) == 4 ); + $can_ocsp = defined &Net::SSLeay::OCSP_cert2ids; + $can_ocsp_staple = $can_ocsp + && defined &Net::SSLeay::set_tlsext_status_type; +} + +# get constants for SSL_OP_NO_* now, instead calling the releated functions +# every time we setup a connection +my %SSL_OP_NO; +for (qw( SSLv2 SSLv3 TLSv1 TLSv1_1 TLSv11:TLSv1_1 TLSv1_2 TLSv12:TLSv1_2 )) { + my ($k, $op) = m{:} ? split(m{:}, $_, 2) : ($_, $_); + my $sub = "Net::SSLeay::OP_NO_$op"; + $SSL_OP_NO{$k} = eval { no strict 'refs'; &$sub; } || 0; +} + +# global defaults +my %DEFAULT_SSL_ARGS = ( + SSL_check_crl => 0, + SSL_version => 'SSLv23:!SSLv3:!SSLv2', # consider both SSL3.0 and SSL2.0 as broken + SSL_verify_callback => undef, + SSL_verifycn_scheme => undef, # fallback cn verification + SSL_verifycn_publicsuffix => undef, # fallback default list verification + #SSL_verifycn_name => undef, # use from PeerAddr/PeerHost - do not override in set_args_filter_hack 'use_defaults' + SSL_npn_protocols => undef, # meaning depends whether on server or client side + SSL_cipher_list => + 'EECDH+AESGCM+ECDSA EECDH+AESGCM EECDH+ECDSA +AES256 EECDH EDH+AESGCM '. + 'EDH ALL +SHA +3DES +RC4 !LOW !EXP !eNULL !aNULL !DES !MD5 !PSK !SRP', +); + +my %DEFAULT_SSL_CLIENT_ARGS = ( + %DEFAULT_SSL_ARGS, + SSL_verify_mode => SSL_VERIFY_PEER, + + SSL_ca_file => undef, + SSL_ca_path => undef, + + # older versions of F5 BIG-IP hang when getting SSL client hello >255 bytes + # http://support.f5.com/kb/en-us/solutions/public/13000/000/sol13037.html + # http://guest:guest@rt.openssl.org/Ticket/Display.html?id=2771 + # Debian works around this by disabling TLSv1_2 on the client side + # Chrome and IE11 use TLSv1_2 but use only a few ciphers, so that packet + # stays small enough + # The following list is taken from IE11, except that we don't do RC4-MD5, + # RC4-SHA is already bad enough. Also, we have a different sort order + # compared to IE11, because we put ciphers supporting forward secrecy on top + + SSL_cipher_list => join(' ', + qw( + ECDHE-ECDSA-AES128-GCM-SHA256 + ECDHE-ECDSA-AES128-SHA256 + ECDHE-ECDSA-AES256-GCM-SHA384 + ECDHE-ECDSA-AES256-SHA384 + ECDHE-ECDSA-AES128-SHA + ECDHE-ECDSA-AES256-SHA + ECDHE-RSA-AES128-SHA256 + ECDHE-RSA-AES128-SHA + ECDHE-RSA-AES256-SHA + DHE-DSS-AES128-SHA256 + DHE-DSS-AES128-SHA + DHE-DSS-AES256-SHA256 + DHE-DSS-AES256-SHA + AES128-SHA256 + AES128-SHA + AES256-SHA256 + AES256-SHA + EDH-DSS-DES-CBC3-SHA + DES-CBC3-SHA + RC4-SHA + ), + # just to make sure, that we don't accidentely add bad ciphers above + '!EXP !LOW !eNULL !aNULL !DES !MD5 !PSK !SRP' + ) +); + +# set values inside _init to work with perlcc, RT#95452 +my %DEFAULT_SSL_SERVER_ARGS; + +# Initialization of OpenSSL internals +# This will be called once during compilation - perlcc users might need to +# call it again by hand, see RT#95452 +{ + sub init { + # library_init returns false if the library was already initialized. + # This way we can find out if the library needs to be re-initialized + # inside code compiled with perlcc + Net::SSLeay::library_init() or return; + + Net::SSLeay::load_error_strings(); + Net::SSLeay::SSLeay_add_ssl_algorithms(); + Net::SSLeay::OpenSSL_add_all_digests(); + Net::SSLeay::randomize(); + + %DEFAULT_SSL_SERVER_ARGS = ( + %DEFAULT_SSL_ARGS, + SSL_verify_mode => SSL_VERIFY_NONE, + SSL_honor_cipher_order => 1, # trust server to know the best cipher + SSL_dh => do { + my $bio = Net::SSLeay::BIO_new(Net::SSLeay::BIO_s_mem()); + # generated with: openssl dhparam 2048 + Net::SSLeay::BIO_write($bio,<<'DH'); +-----BEGIN DH PARAMETERS----- +MIIBCAKCAQEAr8wskArj5+1VCVsnWt/RUR7tXkHJ7mGW7XxrLSPOaFyKyWf8lZht +iSY2Lc4oa4Zw8wibGQ3faeQu/s8fvPq/aqTxYmyHPKCMoze77QJHtrYtJAosB9SY +CN7s5Hexxb5/vQ4qlQuOkVrZDiZO9GC4KaH9mJYnCoAsXDhDft6JT0oRVSgtZQnU +gWFKShIm+JVjN94kGs0TcBEesPTK2g8XVHK9H8AtSUb9BwW2qD/T5RmgNABysApO +Ps2vlkxjAHjJcqc3O+OiImKik/X2rtBTZjpKmzN3WWTB0RJZCOWaLlDO81D01o1E +aZecz3Np9KIYey900f+X7zC2bJxEHp95ywIBAg== +-----END DH PARAMETERS----- +DH + my $dh = Net::SSLeay::PEM_read_bio_DHparams($bio); + Net::SSLeay::BIO_free($bio); + $dh or die 'no DH'; + $dh; + }, + $can_ecdh ? ( SSL_ecdh_curve => 'prime256v1' ) : (), + ); + } + # Call it once at compile time and try it at INIT. + # This should catch all cases of including the module, e.g 'use' (INIT) or + # 'require' (compile time) and works also with perlcc + { + no warnings; + INIT { init() } + init(); + } +} + sub checkForDoSendback { my $chunks = shift; $chunks = $chunks->[0] if ((ref($chunks) eq "ARRAY") && @@ -219,7 +376,66 @@ $self->{client} = $params->{client} || 0; $self->{params} = $params; - $self->{context} = Net::SSLeay::CTX_new(); + my $ssl_op = Net::SSLeay::OP_ALL(); + $ssl_op |= &Net::SSLeay::OP_SINGLE_DH_USE; + $ssl_op |= &Net::SSLeay::OP_SINGLE_ECDH_USE if $can_ecdh; + + my $ver; + for (split(/\s*:\s*/, $params->{version} || $DEFAULT_SSL_ARGS{SSL_version})) { + m{^(!?)(?:(SSL(?:v2|v3|v23|v2/3))|(TLSv1(?:_?[12])?))$}i + || croak('invalid version specified'); + my $not = $1; + ( my $v = lc($2||$3) ) =~ s{^(...)}{\U$1}; + if ( $not ) { + $ssl_op |= $SSL_OP_NO{$v} if exists $SSL_OP_NO{$v} && defined $SSL_OP_NO{$v}; + } else { + croak('cannot set multiple SSL protocols in version') + if $ver && $v ne $ver; + $ver = $v; + $ver =~ s{/}{}; # interpret SSLv2/3 as SSLv23 + $ver =~ s{(TLSv1)(\d)}{$1\_$2}; # TLSv1_1 + } + } + $ver = '' unless defined $ver; + + my $ctx_new_sub = UNIVERSAL::can( 'Net::SSLeay', + $ver eq 'SSLv2' ? 'CTX_v2_new' : + $ver eq 'SSLv3' ? 'CTX_v3_new' : + $ver eq 'TLSv1' ? 'CTX_tlsv1_new' : + $ver eq 'TLSv1_1' ? 'CTX_tlsv1_1_new' : + $ver eq 'TLSv1_2' ? 'CTX_tlsv1_2_new' : + 'CTX_new' + ) || die(sprintf "SSL Version %s not supported", $ver); + + $self->{context} = $ctx_new_sub->() || die 'SSL Context init failed'; + + # SSL_OP_CIPHER_SERVER_PREFERENCE + $ssl_op |= 0x00400000 if $params->{client}; + + if ($ver eq 'SSLv23' && !($ssl_op & $SSL_OP_NO{SSLv3})) { + # At least LibreSSL disables SSLv3 by default in SSL_CTX_new. + # If we really want SSL3.0 we need to explicitly allow it with + # SSL_CTX_clear_options. + Net::SSLeay::CTX_clear_options($self->{context}, $SSL_OP_NO{SSLv3}); + } + + Net::SSLeay::CTX_set_options($self->{context}, $ssl_op); + + # if we don't set session_id_context if client certificate is expected + # client session caching will fail + # if user does not provide explicit id just use the stringification + # of the context + if ( my $id = $params->{SSL_session_id_context} + || ( ( $params->{client} ? $DEFAULT_SSL_CLIENT_ARGS{SSL_verify_mode} : $DEFAULT_SSL_SERVER_ARGS{SSL_verify_mode} ) & 0x01 ) && $self->{context} ) { + Net::SSLeay::CTX_set_session_id_context($self->{context}, $id, length($id)); + } + + # SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER makes syswrite return if at least one + # buffer was written and not block for the rest + # SSL_MODE_ENABLE_PARTIAL_WRITE can be necessary for non-blocking because we + # cannot guarantee, that the location of the buffer stays constant + #Net::SSLeay::CTX_set_mode( $self->{context}, + # SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER|SSL_MODE_ENABLE_PARTIAL_WRITE); Net::SSLeay::CTX_use_RSAPrivateKey_file($self->{context}, $params->{key}, &Net::SSLeay::FILETYPE_PEM); Net::SSLeay::CTX_use_certificate_file($self->{context}, $params->{crt}, &Net::SSLeay::FILETYPE_PEM); @@ -261,6 +477,10 @@ if $params->{blockbadclientcert}; Net::SSLeay::set_verify($self->{ssl}, $orfilter, \&VERIFY); } + + if (ref $params->{FailureEvent} eq 'ARRAY') { + $self->{FailureEvent} = $params->{FailureEvent}; + } $globalinfos = [0, 0, []]; @@ -428,6 +648,15 @@ carp("POE::Filter::SSL: UNEXPECTED ERROR: ERR1:".$err." ERR2:".$err2.($self->{client} ? '' : " HINT: ". "Check if you have configured a CRT and KEY file, and that ". "both are readable")); # unless ($err2 == 5); # SSL_ERROR_SYSCALL + $self->{unexpected_error} = 1; + $poe_kernel->call(@{$self->{FailureEvent}}, + sprintf("POE::Filter::SSL: UNEXPECTED ERROR: ERR1:%s ERR2:%s%s", + $err, + $err2, + ($self->{client} ? '' : + ' HINT: Check if you have configured a CRT and KEY file, and that both are readable') + ) + ) if $self->{FailureEvent}; $ret++ unless $self->{accepted}++; return $ret; } @@ -445,6 +674,21 @@ return Net::SSLeay::get_cipher($self->{ssl}); } +sub getVersion { + my $self = shift; + my $ssl = $self->{ssl} || return; + my $version = Net::SSLeay::version($ssl) || return; + return + $version == 0x0303 ? 'TLSv1_2' : + $version == 0x0302 ? 'TLSv1_1' : + $version == 0x0301 ? 'TLSv1' : + $version == 0x0300 ? 'SSLv3' : + $version == 0x0002 ? 'SSLv2' : + $version == 0xfeff ? 'DTLS1' : + undef; + +} + sub clientCertExists { my $self = shift; return ((ref($self->{infos}) eq "ARRAY") && ($self->{infos}->[1]));