NAME POE::Filter::SSL - The easiest and flexiblest way to SSL in POE! VERSION Version 0.41 DESCRIPTION This module allows one to secure connections of *POE::Wheel::ReadWrite* with OpenSSL by a *POE::Filter* object, and behaves (beside of SSLing) as *POE::Filter::Stream*. *POE::Filter::SSL* can be added, switched and removed during runtime, for example if you want to initiate SSL (see the *SSL on an established connection* example in *SYNOPSIS*) on an already established connection. You are able to combine *POE::Filter::SSL* with other filters, for example have a HTTPS server together with *POE::Filter::HTTPD* (see the *HTTPS-Server* example in *SYNOPSIS*). *POE::Filter::SSL* is based on *Net::SSLeay*, but got two XS functions which *Net::SSLeay* is missing. Features Full non-blocking processing No use of sockets at all Server and client mode Optional client certificate verification Allows one to accept connections with invalid or missing client certificate and return custom error data CRL check of client certificates Retrieve client certificate details (subject name, issuer name, certificate serial) Upcoming Features Direct cipher encryption without SSL or TLS protocol, for example with static AES encryption SYNOPSIS By default *POE::Filter::SSL* acts as a SSL server. To use it in client mode you just have to set the *client* option of *new()*. TCP-Client #!perl use warnings; use strict; use POE qw(Component::Client::TCP Filter::SSL); POE::Component::Client::TCP->new( RemoteAddress => "yahoo.com", RemotePort => 443, Filter => [ "POE::Filter::SSL", client => 1 ], Connected => sub { $_[HEAP]{server}->put("HEAD /\r\n\r\n"); }, ServerInput => sub { print "from server: ".$_[ARG0]."\n"; }, ); POE::Kernel->run(); exit; TCP-Server #!perl use warnings; use strict; use POE qw(Component::Server::TCP); POE::Component::Server::TCP->new( Port => 443, ClientFilter => [ "POE::Filter::SSL", crt => 'server.crt', key => 'server.key' ], ClientConnected => sub { print "got a connection from $_[HEAP]{remote_ip}\n"; $_[HEAP]{client}->put("Smile from the server!\r\n"); }, Alias => "tcp", ClientInput => sub { my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP]; $_[HEAP]{client}->put("You sent:\r\n".$_[ARG0]); $_[KERNEL]->yield("shutdown"); }, ); POE::Kernel->run; exit; HTTPS-Server use POE::Filter::SSL::PreFilter use POE::Filter::SSL; use POE::Component::Server::HTTP; use HTTP::Status; my $aliases = POE::Component::Server::HTTP->new( Port => 443, ContentHandler => { '/' => \&handler, '/dir/' => sub { return; }, '/file' => sub { return; } }, Headers => { Server => 'My Server' }, PreFilter => POE::Filter::SSL->new( crt => 'server.crt', key => 'server.key', cacrt => 'ca.crt' ) ); sub handler { my ($request, $response) = @_; $response->code(RC_OK); $response->content("Hi, you fetched ". $request->uri); return RC_OK; } POE::Kernel->run(); POE::Kernel->call($aliases->{httpd}, "shutdown"); # next line isn't really needed POE::Kernel->call($aliases->{tcp}, "shutdown"); SSL on an established connection Advanced Example This example is an IMAP-Relay which forwards the connections to a IMAP server by username. It allows one the unencrypted transfer on port 143, with the option of SSL on the established connection (STARTTLS). On port 993 it allows one to do direct SSL. Tested with Thunderbird version 3.0.5. #!perl use warnings; use strict; use POE qw(Component::Server::TCP Component::Client::TCP Filter::SSL Filter::Stream); my $defaultImapServer = "not.existing.de"; my $usernameToImapServer = { user1 => 'mailserver1.domain.de', user2 => 'mailserver2.domain.de', # ... }; POE::Component::Server::TCP->new( Port => 143, ClientFilter => "POE::Filter::Stream", ClientDisconnected => \&disconnect, ClientConnected => \&connected, ClientInput => \&handleInput, InlineStates => { send_stuff => \&send_stuff, _child => \&child } ); POE::Component::Server::TCP->new( Port => 993, ClientFilter => [ "POE::Filter::SSL", crt => 'server.crt', key => 'server.key' ], ClientConnected => \&connected, ClientDisconnected => \&disconnect, ClientInput => \&handleInput, InlineStates => { send_stuff => \&send_stuff, _child => \&child } ); sub disconnect { my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP]; logevent('server got disconnect', $session); $kernel->post($heap->{client_id} => "shutdown"); } sub connected { my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP]; logevent("got a connection from ".$heap->{remote_ip}, $session); $heap->{client}->put("* OK [CAPABILITY IMAP4rev1 UIDPLUS CHILDREN NAMESPACE THREAD=ORDEREDSUBJECT THREAD=REFERENCES SORT QUOTA IDLE ACL ACL2=UNION STARTTLS] IMAP Relay v0.1 ready.\r\n"); } sub send_stuff { my ($heap, $stuff, $session) = @_[HEAP, ARG0, SESSION]; logevent("-> ".length($stuff)." Bytes", $session); (defined($heap->{client})) && (ref($heap->{client}) eq "POE::Wheel::ReadWrite") && $heap->{client}->put($stuff); } sub child { my ($heap, $child_op, $child) = @_[HEAP, ARG0, ARG1]; if ($child_op eq "create") { $heap->{client_id} = $child->ID; } } sub handleInput { my ($kernel, $session, $heap, $input) = @_[KERNEL, SESSION, HEAP, ARG0]; if($heap->{forwarding}) { return $kernel->yield("shutdown") unless (defined($heap->{client_id})); $kernel->post($heap->{client_id} => send_stuff => $input); } elsif ($input =~ /^(\d+)\s+STARTTLS[\r\n]+/i) { $_[HEAP]{client}->put($1." OK Begin SSL/TLS negotiation now.\r\n"); logevent("SSLing now...", $session); $_[HEAP]{client}->set_filter(POE::Filter::SSL->new(crt => 'server.crt', key => 'server.key')); } elsif ($input =~ /^(\d+)\s+CAPABILITY[\r\n]+/i) { $_[HEAP]{client}->put("* CAPABILITY IMAP4rev1 UIDPLUS CHILDREN NAMESPACE THREAD=ORDEREDSUBJECT THREAD=REFERENCES SORT QUOTA IDLE ACL ACL2=UNION STARTTLS\r\n"); $_[HEAP]{client}->put($1." OK CAPABILITY completed\r\n"); } elsif ($input =~ /^(\d+)\s+login\s+\"(\S+)\"\s+\"(\S+)\"[\r\n]+/i) { my $username = $2; my $pass = $3; logevent("login of user ".$username, $session); spawn_client_side($username, $input); $heap->{forwarding}++; } else { logevent("unknown command before login, disconnecting.", $session); return $kernel->yield("shutdown"); } } sub spawn_client_side { my $username = shift; POE::Component::Client::TCP->new( RemoteAddress => $usernameToImapServer->{$username} || $defaultImapServer, RemotePort => 143, Filter => "POE::Filter::Stream", Started => sub { $_[HEAP]->{server_id} = $_[SENDER]->ID; $_[HEAP]->{buf} = $_[ARG0]; $_[HEAP]->{skip} = 0; }, Connected => sub { my ($heap, $session) = @_[HEAP, SESSION]; logevent('client connected', $session); $heap->{server}->put($heap->{buf}); delete $heap->{buf}; }, ServerInput => sub { my ($kernel, $heap, $session, $input) = @_[KERNEL, HEAP, SESSION, ARG0]; #logevent('client got input', $session, $input); $kernel->post($heap->{server_id} => send_stuff => $input) if ($heap->{skip}++); }, Disconnected => sub { my ($kernel, $heap, $session) = @_[KERNEL, HEAP, SESSION]; logevent('client disconnected', $session); $kernel->post($heap->{server_id} => 'shutdown'); }, InlineStates => { send_stuff => sub { my ($heap, $stuff, $session) = @_[HEAP, ARG0, SESSION]; logevent("<- ".length($stuff)." Bytes", $session); (defined($heap->{server})) && (ref($heap->{server}) eq "POE::Wheel::ReadWrite") && $heap->{server}->put($stuff); }, }, Args => [ shift ] ); } sub logevent { my ($state, $session, $arg) = @_; my $id = $session->ID(); print "session $id $state "; print ": $arg" if (defined $arg); print "\n"; } POE::Kernel->run; Client certificate verification Advanced Example The following example implements a HTTPS server with client certificate verification, which shows details about the verified client certificate. #!perl use strict; use warnings; use Socket; use POE qw( Wheel::SocketFactory Wheel::ReadWrite Driver::SysRW Filter::SSL Filter::Stackable Filter::HTTPD ); POE::Session->create( inline_states => { _start => sub { my $heap = $_[HEAP]; $heap->{listener} = POE::Wheel::SocketFactory->new( BindAddress => '0.0.0.0', BindPort => 443, Reuse => 'yes', SuccessEvent => 'socket_birth', FailureEvent => '_stop', ); }, _stop => sub { delete $_[HEAP]->{listener}; }, socket_birth => sub { my ($socket) = $_[ARG0]; POE::Session->create( inline_states => { _start => sub { my ($heap, $kernel, $connected_socket, $address, $port) = @_[HEAP, KERNEL, ARG0, ARG1, ARG2]; $heap->{sslfilter} = POE::Filter::SSL->new( crt => 'server.crt', key => 'server.key', cacrt => 'ca.crt', cipher => 'DHE-RSA-AES256-GCM-SHA384:AES256-SHA', #cacrl => 'ca.crl', # Uncomment this, if you have a CRL file. debug => 1, clientcert => 1 ); $heap->{socket_wheel} = POE::Wheel::ReadWrite->new( Handle => $connected_socket, Driver => POE::Driver::SysRW->new(), Filter => POE::Filter::Stackable->new(Filters => [ $heap->{sslfilter}, POE::Filter::HTTPD->new() ]), InputEvent => 'socket_input', ErrorEvent => '_stop', ); }, socket_input => sub { my ($kernel, $heap, $buf) = @_[KERNEL, HEAP, ARG0]; my (@certid) = ($heap->{sslfilter}->clientCertIds()); my $content = ''; if ($heap->{sslfilter}->clientCertValid()) { $content .= "Hello valid client Certifcate:"; } else { $content .= "None or invalid client certificate:"; } $content .= "