POE::Component::SSLify::NonBlock - Nonblocking SSL for POE with client certificate verification.
# Import the modules use POE::Component::SSLify qw( SSLify_Options SSLify_GetCTX ); use POE::Component::SSLify::NonBlock qw( Server_SSLify_NonBlock ); # Set the key + certificate file, only one time needed. eval { SSLify_Options( 'server.key', 'server.crt' ) }; if ( $@ ) { # Unable to load key or certificate file... } # Create a normal SocketFactory wheel or something my $factory = POE::Wheel::SocketFactory->new( ... ); # Converts the socket into a SSL socket POE can communicate with, every time on new socket needed. eval { $socket = Server_SSLify_NonBlock( SSLify_GetCTX(), $socket, { } ) }; if ( $@ ) { # Unable to SSLify it... } # Now, hand it off to ReadWrite my $rw = POE::Wheel::ReadWrite->new( Handle => $socket, ... );
Nonblocking SSL for POE with client certificate verification.
This component represents a common way of using ssl on a server, which needs to ensure that not one client can block the whole server. Further it allows to verificate client certificates.
SSL is a protocol which interacts with the client during the handshake multiple times. If the socket is blocking, as on pure POE::Component::SSLify, one client can block the whole server. Especially if you want to do client certificate verification, the user has the abilty to choose a client certificate. In this situation the ssl handshake is waiting, and in blocked mode the whole server also stops responding.
You have three opportunities to do client certificate verification:
Easiest way: Verify the certificate and let OpenSSL reject the connection during ssl handshake if there is no certificate or if it is unstrusted. Advanced way: Verify the certificate and poe handler determines if there is no certificate or if it is unstrusted. Complicated way: Verify the certificate and poe handler determines if there is no certificate, if it is unstrusted or if it is blocked by a CRL.
Generaly you can use the "Server-side usage" example above, but you have to enable the client certification feature with the "clientcertrequest" paramter. The Server_SSLify_NonBlock function allows a hash for parameters:
use POE::Component::SSLify qw( SSLify_Options SSLify_GetCTX ); use POE::Component::SSLify::NonBlock qw( Server_SSLify_NonBlock SSLify_Options_NonBlock_ClientCert ); eval { SSLify_Options( 'server.key', 'server.crt' ) }; if ( $@ ) { # Unable to load key or certificate file... } eval { SSLify_Options_NonBlock_ClientCert(SSLify_GetCTX(), 'ca.crt')) }; if ( $@ ) { # Unable to load certificate file... } ... eval { $heap->{socket} = Server_SSLify_NonBlock(SSLify_GetCTX(), $heap->{socket}, { clientcertrequest => 1 } ) }; if ( $@ ) { print "SSL Failed: ".$@."\n"; delete $heap->{wheel_client}; }
Now the server sends during SSL handshake the request for a client certificate. By default, POE::Component::SSLify::NonBlock aborts the connection if "clientcertrequest" is set and there is no client certificat or the certificate is not trusted.
use POE::Component::SSLify qw( SSLify_Options SSLify_GetCTX ); use POE::Component::SSLify::NonBlock qw( Server_SSLify_NonBlock SSLify_Options_NonBlock_ClientCert Server_SSLify_NonBlock_SSLDone ); eval { SSLify_Options( 'server.key', 'server.crt' ) }; if ( $@ ) { # Unable to load key or certificate file... } eval { SSLify_Options_NonBlock_ClientCert(SSLify_GetCTX(), 'ca.crt')) }; if ( $@ ) { # Unable to load certificate file... } ... client_accept => sub { ... eval { $heap->{socket} = Server_SSLify_NonBlock( SSLify_GetCTX(), $socket, { clientcertrequest => 1, noblockbadclientcert => 1 } ) }; if ( $@ ) { print "SSL Failed: ".$@."\n"; delete $heap->{wheel_client}; } $heap->{wheel_client} = POE::Wheel::ReadWrite->new( Handle => $heap->{socket}, Driver => POE::Driver::SysRW->new, Filter => POE::Filter::Stream->new, InputEvent => 'client_input', ... } }, client_input => sub { my ( $heap, $kernel, $input ) = @_[ HEAP, KERNEL, ARG0 ]; my $canwrite = exists $heap->{wheel_client} && (ref($heap->{wheel_client}) eq "POE::Wheel::ReadWrite"); return unless Server_SSLify_NonBlock_SSLDone($heap->{socket}); if (!(Server_SSLify_NonBlock_ClientCertificateExists($heap->{socket}))) { exists $heap->{wheel_client} && (ref($heap->{wheel_client}) eq "POE::Wheel::ReadWrite") && $heap->{wheel_client}->put("Content-type: text/html\r\n\r\nNoClientCertExists"); $kernel->yield("disconnect"); return; } elsif(!(Server_SSLify_NonBlock_ClientCertIsValid($heap->{socket}))) { exists $heap->{wheel_client} && (ref($heap->{wheel_client}) eq "POE::Wheel::ReadWrite") && $heap->{wheel_client}->put("Content-type: text/html\r\n\r\nClientCertInvalid"); $kernel->yield("disconnect"); return; } ... }, disconnect => sub { $_[KERNEL]->delay(close_delayed => 1) unless ($_[HEAP]->{disconnecting}++); }, close_delayed => sub { my ($kernel, $heap) = @_[KERNEL, HEAP]; delete $heap->{wheel_client}; }, ...
WARNING: For this to work you have to patch into Net::SSLeay the lines in the file net-ssleay-patch in the base path of the tar.gz of the packet, and then recompile and reinstall the Net::SSLeay package.
Here an solution with SSL/TLS on the fly and client authentication, initiated via "STARTTLS". For example if you want to do IMAPS, POPS or FTPS.
use POE::Component::SSLify qw( SSLify_Options SSLify_GetCTX ); use POE::Component::SSLify::NonBlock qw( Server_SSLify_NonBlock SSLify_Options_NonBlock_ClientCert Server_SSLify_NonBlock_ClientCertVerifyAgainstCRL Server_SSLify_NonBlock_SSLDone ); eval { SSLify_Options( 'server.key', 'server.crt' ) }; if ( $@ ) { # Unable to load key or certificate file... } eval { SSLify_Options_NonBlock_ClientCert(SSLify_GetCTX(), 'ca.crt')) }; if ( $@ ) { # Unable to load certificate file... } ... client_accept => sub { ... $heap->{wheel_client} = POE::Wheel::ReadWrite->new( Handle => $heap->{socket}, Driver => POE::Driver::SysRW->new, Filter => POE::Filter::Stream->new, InputEvent => 'client_input', ... } $heap->{mode} = 'plain'; }, client_input => sub { my ( $heap, $kernel, $input ) = @_[ HEAP, KERNEL, ARG0 ]; my $canwrite = exists $heap->{wheel_client} && (ref($heap->{wheel_client}) eq "POE::Wheel::ReadWrite"); if ($heap->{mode} eq "plain") { if ($input ~= /STARTTLS/) { $heap->{wheel_client}->put("Do now SSL Handshake.\n") if $canwrite; eval { $heap->{socket} = Server_SSLify_NonBlock( SSLify_GetCTX(), $socket, { clientcertrequest => 1, noblockbadclientcert => 1, getserial => 1 } ) }; if ( $@ ) { print "SSL Failed: ".$@."\n"; delete $heap->{wheel_client}; } $heap->{mode} = 'sslhandshake'; } else { $heap->{wheel_client}->put("First start TLS SSL with the 'STARTTLS' command.\n") if $canwrite; } } elsif($heap->{mode} eq 'sslhandshake') { return unless Server_SSLify_NonBlock_SSLDone($heap->{socket}); if (!(Server_SSLify_NonBlock_ClientCertificateExists($heap->{socket}))) { $heap->{wheel_client}->put("NoClientCertExists") if $canwrite; $kernel->yield("disconnect"); return; } elsif(!(Server_SSLify_NonBlock_ClientCertIsValid($heap->{socket}))) { $heap->{wheel_client}->put("ClientCertInvalid") if $canwrite; $kernel->yield("disconnect"); return; } elsif(!(Server_SSLify_NonBlock_ClientCertVerifyAgainstCRL($heap->{socket}, 'ca.crl'))) { $heap->{wheel_client}->put("CRL") if $canwrite; $kernel->yield("disconnect"); return; } $heap->{mode} = 'crytped'; } if ($heap->{mode} eq "cryped") { $heap->{wheel_client}->put("Yeah! You're authenticated!") if $canwrite; $kernel->yield("disconnect"); } }, disconnect => sub { $_[KERNEL]->delay(close_delayed => 1) unless ($_[HEAP]->{disconnecting}++); }, close_delayed => sub { my ($kernel, $heap) = @_[KERNEL, HEAP]; delete $heap->{wheel_client}; }, ...
Configures ssl ctx(context) to request from the client a certificate for authentication, which is verificated against the configured CA in the file $cacrt.
SSLify_Options_NonBlock_ClientCert(SSLify_GetCTX(), 'ca.crt');
Note:
SSLify_Options from POE::Component::SSLify must be first called !
Similar to Server_SSLify from POE::Component::SSLify. It needs further the CTX of POE::Component::SSLify and a hash for special options:
my $socket = shift; # get the socket from somewhere $socket = Server_SSLify_NonBlock(SSLify_GetCTX(), $socket, { option1 => 1, option1 => 2,... });
Options are:
clientcertrequest The client is requested for a client certificat during ssl handshake noblockbadclientcert If the client do not provide a client certificate, or the client certificate is untrusted, the connection will not be aborted. You can check for the errors via the functions Server_SSLify_NonBlock_ClientCertificateExists and Server_SSLify_NonBlock_ClientCertIsValid. debug Get debug messages during ssl handshake. Espacally usefull for Server_SSLify_NonBlock_ClientCertVerifyAgainstCRL. getserial Request the serial of the client certificate during ssl handshake. WARNING: You have to patch Net::SSLeay to provide the Net::SSLeay::X509_get_serialNumber function before you can set the getserial option! See the file net-ssleay-patch in the base path of the tar.gz of the packet.
SSLify_Options from POE::Component::SSLify must be set first!
Checks if the SSL handshake has been completed.
Server_SSLify_NonBlock_SSLDone($socket);
Verify if the client commited a valid client certificate.
Server_SSLify_NonBlock_ClientCertificateExists($socket);
Verify if the client certifcate is trusted by a loaded CA (see SSLify_Options_NonBlock_ClientCert).
Server_SSLify_NonBlock_ClientCertIsValid($socket);
Opens a CRL file, and verify if the serial of the client certificate is not contained in the CRL file. No file caching is done, each run opens the file new.
Note: If your CRL File is missing, can not be opened or has no blocked certificate at all, every call will get blocked.
Server_SSLify_NonBlock_ClientCertVerifyAgainstCRL($socket, 'ca.crl'); WARNING: You have to patch Net::SSLeay to provide the Net::SSLeay::verify_serial_against_crl_file function before you can set the getserial option! See the file net-ssleay-patch in the base path of the tar.gz of the packet.
You can use all functions from POE::Component::SSLify !
This module is based on POE::Component::SSLify, so we have in POE::Component::SSLify::NonBlock the same issues as on POE::Component::SSLify.
Stuffs all of the above functions in @EXPORT_OK so you have to request them directly
Server_SSLify_NonBlock_ClientCertVerifyAgainstCRL also verifies against the serial of the CA ! Make sure that you never use the serial of the CA for client certificates!
I did not test POE::Component::SSLify::NonBlock on Win32 platforms at all!
POE::Component::SSLify
Net::SSLeay
pRiVi <pRiVi@cpan.org>
This code is based on Apocalypse module POE::Component::SSLify, improved by client certification code and non-blocking sockets.
Copyright 2010 by Markus Mueller/Apocalypse/Rocco Caputo/Dariusz Jackowski.
Copyright 2010 by Markus Mueller
This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
To install POE::Component::SSLify::NonBlock, copy and paste the appropriate command in to your terminal.
cpanm
cpanm POE::Component::SSLify::NonBlock
CPAN shell
perl -MCPAN -e shell install POE::Component::SSLify::NonBlock
For more information on module installation, please visit the detailed CPAN module installation guide.