1NAME 2 POE::Filter::SSL - The easiest and flexiblest way to SSL in POE! 3 4VERSION 5 Version 0.41 6 7DESCRIPTION 8 This module allows one to secure connections of *POE::Wheel::ReadWrite* 9 with OpenSSL by a *POE::Filter* object, and behaves (beside of SSLing) 10 as *POE::Filter::Stream*. 11 12 *POE::Filter::SSL* can be added, switched and removed during runtime, 13 for example if you want to initiate SSL (see the *SSL on an established 14 connection* example in *SYNOPSIS*) on an already established connection. 15 You are able to combine *POE::Filter::SSL* with other filters, for 16 example have a HTTPS server together with *POE::Filter::HTTPD* (see the 17 *HTTPS-Server* example in *SYNOPSIS*). 18 19 *POE::Filter::SSL* is based on *Net::SSLeay*, but got two XS functions 20 which *Net::SSLeay* is missing. 21 22 Features 23 24 Full non-blocking processing 25 26 No use of sockets at all 27 28 Server and client mode 29 30 Optional client certificate verification 31 32 Allows one to accept connections with invalid or missing client 33 certificate and return custom error data 34 35 CRL check of client certificates 36 37 Retrieve client certificate details (subject name, issuer name, 38 certificate serial) 39 40 Upcoming Features 41 42 Direct cipher encryption without SSL or TLS protocol, for example 43 with static AES encryption 44 45SYNOPSIS 46 By default *POE::Filter::SSL* acts as a SSL server. To use it in client 47 mode you just have to set the *client* option of *new()*. 48 49 TCP-Client 50 #!perl 51 52 use warnings; 53 use strict; 54 55 use POE qw(Component::Client::TCP Filter::SSL); 56 57 POE::Component::Client::TCP->new( 58 RemoteAddress => "yahoo.com", 59 RemotePort => 443, 60 Filter => [ "POE::Filter::SSL", client => 1 ], 61 Connected => sub { 62 $_[HEAP]{server}->put("HEAD /\r\n\r\n"); 63 }, 64 ServerInput => sub { 65 print "from server: ".$_[ARG0]."\n"; 66 }, 67 ); 68 69 POE::Kernel->run(); 70 exit; 71 72 TCP-Server 73 #!perl 74 75 use warnings; 76 use strict; 77 78 use POE qw(Component::Server::TCP); 79 80 POE::Component::Server::TCP->new( 81 Port => 443, 82 ClientFilter => [ "POE::Filter::SSL", crt => 'server.crt', key => 'server.key' ], 83 ClientConnected => sub { 84 print "got a connection from $_[HEAP]{remote_ip}\n"; 85 $_[HEAP]{client}->put("Smile from the server!\r\n"); 86 }, 87 Alias => "tcp", 88 ClientInput => sub { 89 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP]; 90 $_[HEAP]{client}->put("You sent:\r\n".$_[ARG0]); 91 $_[KERNEL]->yield("shutdown"); 92 }, 93 ); 94 95 POE::Kernel->run; 96 exit; 97 98 HTTPS-Server 99 use POE::Filter::SSL::PreFilter 100 use POE::Filter::SSL; 101 use POE::Component::Server::HTTP; 102 use HTTP::Status; 103 my $aliases = POE::Component::Server::HTTP->new( 104 Port => 443, 105 ContentHandler => { 106 '/' => \&handler, 107 '/dir/' => sub { return; }, 108 '/file' => sub { return; } 109 }, 110 Headers => { Server => 'My Server' }, 111 PreFilter => POE::Filter::SSL->new( 112 crt => 'server.crt', 113 key => 'server.key', 114 cacrt => 'ca.crt' 115 ) 116 ); 117 118 sub handler { 119 my ($request, $response) = @_; 120 $response->code(RC_OK); 121 $response->content("Hi, you fetched ". $request->uri); 122 return RC_OK; 123 } 124 125 POE::Kernel->run(); 126 POE::Kernel->call($aliases->{httpd}, "shutdown"); 127 # next line isn't really needed 128 POE::Kernel->call($aliases->{tcp}, "shutdown"); 129 130 SSL on an established connection 131 Advanced Example 132 This example is an IMAP-Relay which forwards the connections to a IMAP 133 server by username. It allows one the unencrypted transfer on port 134 143, with the option of SSL on the established connection (STARTTLS). 135 On port 993 it allows one to do direct SSL. 136 137 Tested with Thunderbird version 3.0.5. 138 139 #!perl 140 141 use warnings; 142 use strict; 143 144 use POE qw(Component::Server::TCP Component::Client::TCP Filter::SSL Filter::Stream); 145 146 my $defaultImapServer = "not.existing.de"; 147 my $usernameToImapServer = { 148 user1 => 'mailserver1.domain.de', 149 user2 => 'mailserver2.domain.de', 150 # ... 151 }; 152 153 POE::Component::Server::TCP->new( 154 Port => 143, 155 ClientFilter => "POE::Filter::Stream", 156 ClientDisconnected => \&disconnect, 157 ClientConnected => \&connected, 158 ClientInput => \&handleInput, 159 InlineStates => { 160 send_stuff => \&send_stuff, 161 _child => \&child 162 } 163 ); 164 165 POE::Component::Server::TCP->new( 166 Port => 993, 167 ClientFilter => [ "POE::Filter::SSL", crt => 'server.crt', key => 'server.key' ], 168 ClientConnected => \&connected, 169 ClientDisconnected => \&disconnect, 170 ClientInput => \&handleInput, 171 InlineStates => { 172 send_stuff => \&send_stuff, 173 _child => \&child 174 } 175 ); 176 177 sub disconnect { 178 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP]; 179 logevent('server got disconnect', $session); 180 $kernel->post($heap->{client_id} => "shutdown"); 181 } 182 183 sub connected { 184 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP]; 185 logevent("got a connection from ".$heap->{remote_ip}, $session); 186 $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"); 187 } 188 189 sub send_stuff { 190 my ($heap, $stuff, $session) = @_[HEAP, ARG0, SESSION]; 191 logevent("-> ".length($stuff)." Bytes", $session); 192 (defined($heap->{client})) && (ref($heap->{client}) eq "POE::Wheel::ReadWrite") && 193 $heap->{client}->put($stuff); 194 } 195 196 sub child { 197 my ($heap, $child_op, $child) = @_[HEAP, ARG0, ARG1]; 198 if ($child_op eq "create") { 199 $heap->{client_id} = $child->ID; 200 } 201 } 202 203 sub handleInput { 204 my ($kernel, $session, $heap, $input) = @_[KERNEL, SESSION, HEAP, ARG0]; 205 if($heap->{forwarding}) { 206 return $kernel->yield("shutdown") unless (defined($heap->{client_id})); 207 $kernel->post($heap->{client_id} => send_stuff => $input); 208 } elsif ($input =~ /^(\d+)\s+STARTTLS[\r\n]+/i) { 209 $_[HEAP]{client}->put($1." OK Begin SSL/TLS negotiation now.\r\n"); 210 logevent("SSLing now...", $session); 211 $_[HEAP]{client}->set_filter(POE::Filter::SSL->new(crt => 'server.crt', key => 'server.key')); 212 } elsif ($input =~ /^(\d+)\s+CAPABILITY[\r\n]+/i) { 213 $_[HEAP]{client}->put("* CAPABILITY IMAP4rev1 UIDPLUS CHILDREN NAMESPACE THREAD=ORDEREDSUBJECT THREAD=REFERENCES SORT QUOTA IDLE ACL ACL2=UNION STARTTLS\r\n"); 214 $_[HEAP]{client}->put($1." OK CAPABILITY completed\r\n"); 215 } elsif ($input =~ /^(\d+)\s+login\s+\"(\S+)\"\s+\"(\S+)\"[\r\n]+/i) { 216 my $username = $2; 217 my $pass = $3; 218 logevent("login of user ".$username, $session); 219 spawn_client_side($username, $input); 220 $heap->{forwarding}++; 221 } else { 222 logevent("unknown command before login, disconnecting.", $session); 223 return $kernel->yield("shutdown"); 224 } 225 } 226 227 sub spawn_client_side { 228 my $username = shift; 229 POE::Component::Client::TCP->new( 230 RemoteAddress => $usernameToImapServer->{$username} || $defaultImapServer, 231 RemotePort => 143, 232 Filter => "POE::Filter::Stream", 233 Started => sub { 234 $_[HEAP]->{server_id} = $_[SENDER]->ID; 235 $_[HEAP]->{buf} = $_[ARG0]; 236 $_[HEAP]->{skip} = 0; 237 }, 238 Connected => sub { 239 my ($heap, $session) = @_[HEAP, SESSION]; 240 logevent('client connected', $session); 241 $heap->{server}->put($heap->{buf}); 242 delete $heap->{buf}; 243 }, 244 ServerInput => sub { 245 my ($kernel, $heap, $session, $input) = @_[KERNEL, HEAP, SESSION, ARG0]; 246 #logevent('client got input', $session, $input); 247 $kernel->post($heap->{server_id} => send_stuff => $input) if ($heap->{skip}++); 248 }, 249 Disconnected => sub { 250 my ($kernel, $heap, $session) = @_[KERNEL, HEAP, SESSION]; 251 logevent('client disconnected', $session); 252 $kernel->post($heap->{server_id} => 'shutdown'); 253 }, 254 InlineStates => { 255 send_stuff => sub { 256 my ($heap, $stuff, $session) = @_[HEAP, ARG0, SESSION]; 257 logevent("<- ".length($stuff)." Bytes", $session); 258 (defined($heap->{server})) && (ref($heap->{server}) eq "POE::Wheel::ReadWrite") && 259 $heap->{server}->put($stuff); 260 }, 261 }, 262 Args => [ shift ] 263 ); 264 } 265 266 sub logevent { 267 my ($state, $session, $arg) = @_; 268 my $id = $session->ID(); 269 print "session $id $state "; 270 print ": $arg" if (defined $arg); 271 print "\n"; 272 } 273 274 POE::Kernel->run; 275 276 Client certificate verification 277 Advanced Example 278 The following example implements a HTTPS server with client 279 certificate verification, which shows details about the verified 280 client certificate. 281 282 #!perl 283 284 use strict; 285 use warnings; 286 use Socket; 287 use POE qw( 288 Wheel::SocketFactory 289 Wheel::ReadWrite 290 Driver::SysRW 291 Filter::SSL 292 Filter::Stackable 293 Filter::HTTPD 294 ); 295 296 POE::Session->create( 297 inline_states => { 298 _start => sub { 299 my $heap = $_[HEAP]; 300 $heap->{listener} = POE::Wheel::SocketFactory->new( 301 BindAddress => '0.0.0.0', 302 BindPort => 443, 303 Reuse => 'yes', 304 SuccessEvent => 'socket_birth', 305 FailureEvent => '_stop', 306 ); 307 }, 308 _stop => sub { 309 delete $_[HEAP]->{listener}; 310 }, 311 socket_birth => sub { 312 my ($socket) = $_[ARG0]; 313 POE::Session->create( 314 inline_states => { 315 _start => sub { 316 my ($heap, $kernel, $connected_socket, $address, $port) = @_[HEAP, KERNEL, ARG0, ARG1, ARG2]; 317 $heap->{sslfilter} = POE::Filter::SSL->new( 318 crt => 'server.crt', 319 key => 'server.key', 320 cacrt => 'ca.crt', 321 cipher => 'DHE-RSA-AES256-GCM-SHA384:AES256-SHA', 322 #cacrl => 'ca.crl', # Uncomment this, if you have a CRL file. 323 debug => 1, 324 clientcert => 1 325 ); 326 $heap->{socket_wheel} = POE::Wheel::ReadWrite->new( 327 Handle => $connected_socket, 328 Driver => POE::Driver::SysRW->new(), 329 Filter => POE::Filter::Stackable->new(Filters => [ 330 $heap->{sslfilter}, 331 POE::Filter::HTTPD->new() 332 ]), 333 InputEvent => 'socket_input', 334 ErrorEvent => '_stop', 335 ); 336 }, 337 socket_input => sub { 338 my ($kernel, $heap, $buf) = @_[KERNEL, HEAP, ARG0]; 339 my (@certid) = ($heap->{sslfilter}->clientCertIds()); 340 my $content = ''; 341 if ($heap->{sslfilter}->clientCertValid()) { 342 $content .= "Hello <font color=green>valid</font> client Certifcate:"; 343 } else { 344 $content .= "None or <font color=red>invalid</font> client certificate:"; 345 } 346 $content .= "<hr>"; 347 foreach my $certid (@certid) { 348 $certid = $certid ? $certid->[0]."<br>".$certid->[1]."<br>SERIAL=".$heap->{sslfilter}->hexdump($certid->[2]) : 'No client certificate'; 349 $content .= $certid."<hr>"; 350 } 351 $content .= "Your URL was: ".$buf->uri."<hr>" 352 if (ref($buf) eq "HTTP::Request"); 353 $content .= localtime(time()); 354 my $response = HTTP::Response->new(200); 355 $response->push_header('Content-type', 'text/html'); 356 $response->content($content); 357 $heap->{socket_wheel}->put($response); 358 $kernel->delay(_stop => 1); 359 }, 360 _stop => sub { 361 delete $_[HEAP]->{socket_wheel}; 362 } 363 }, 364 args => [$socket], 365 ); 366 } 367 } 368 ); 369 370 $poe_kernel->run(); 371 372FUNCTIONS 373 new(option = value, option => value, option...)> 374 Returns a new *POE::Filter::SSL* object. It accepts the following 375 options: 376 377 client 378 By default *POE::Filter::SSL* acts as a SSL server. To use it in 379 client mode, you have to set this option. 380 381 crt{mem} 382 The certificate file (.crt) for the server, a client certificate 383 in client mode. 384 385 You are able to pass the already inmemory crt file as scalar via 386 *crtmem*. 387 388 key{mem} 389 The key file (.key) of the certificate (see *crt* above). 390 391 You are able to pass the already inmemory key file as scalar via 392 *keymem*. 393 394 cacrt{mem} 395 The ca certificate file (ca.crt), which is used to verificate the 396 client certificates against a CA. You can store multiple ca in one 397 file, all of them gets imported. 398 399 You are able to pass the already inmemory cacrt file as scalar via 400 *cacrtmem* or as an array ref of scalars, if you have multiple ca. 401 402 caverifydepth 403 By default the ca verify depth is 5, you can override this via 404 this option. 405 406 chain 407 Chain certificate, you need it for example for startssl.org which 408 needs a intermedia certificates. Here you can configure it. You 409 can generate this the following way: 410 411 cat client.crt intermediate.crt ca.crt > chain.pem 412 413 In this case, you normalyly have no *key* and *crt* option. 414 Currently it is not possible to pass this inmemory, only by file. 415 416 cacrl 417 Configures a CRL (ca.crl) against the client certificate is 418 verified by *clientCertValid()*. 419 420 dhcert{mem} 421 If you want to enable perfect forward secrecy, here you can enable 422 Diffie-Hellman. You just have to create a dhparam file and there 423 here the path to the path/to/FILENAME.pem where your 424 Diffie-Hellman (pem format) stays. 425 426 openssl dhparam -check -text -5 2048 -out path/to/FILENAME.pem 427 428 You are able to pass the already inmemory dhparam file as 429 scalar(string) via *dhcertmem*. 430 431 clientcert 432 Only in server mode: Request during ssl handshake from the client 433 a client certificat. 434 435 WARNING: If the client provides an untrusted or no client 436 certificate, the connection is not failing. You have to ask 437 *clientCertValid()* if the certificate is valid! 438 439 sni 440 Allows one to set the SNI hostname indication in first packet of 441 handshake. See 442 https://de.wikipedia.org/wiki/Server_Name_Indication 443 444 tls 445 Force in the handshake the use of tls, disables support for the 446 obsolete SSL handshake. 447 448 tls1_2 449 Force in the handshake the use of tls in version 1.2, disables 450 support for the obsolete SSL handshake. 451 452 nohonor 453 By default, as server, *POE::Filter:SSL* sets the option 454 *SSL_OP_CIPHER_SERVER_PREFERENCE*. For more information you may 455 google the pendant of apache *SSLHonorCipherOrder*. 456 457 To flip back to the old behaviour, not setting this option, you 458 can set nohonor. 459 460 cipher 461 Specify which ciphers are allowed for the synchronous encrypted 462 transfer of the data over the ssl connection. 463 464 Example: 465 466 cipher => 'DHE-RSA-AES256-GCM-SHA384:AES256-SHA' 467 468 blockbadclientcert 469 Let OpenSSL deny the connection if there is no client certificate. 470 471 WARNING: If the client is listed in the CRL file or an invalid 472 client certifiate has been sent, the connection will be 473 established! You have to ask *clientCertValid()* if you have the 474 *crl* option set on *new()*, otherwise to ask 475 *clientCertNotOnCRL()* if the certificate is listed on your CRL 476 file! 477 478 ignoreVerifyErrors 479 WARNING: Before using this option, you should be realy sure that 480 you know what you are doing! 481 482 Specify to ignore specific errors on verifying the certificate 483 chain: This is for example useful to be able to fetch the time 484 from via secure and trusted TLS connection. In this case, your 485 time is wrong, so must ignore time errors, which are 9: 486 X509_V_ERR_CERT_NOT_YET_VALID (certificate is not yet valid) and 487 10: X509_V_ERR_CERT_HAS_EXPIRED (certificate has expired). 488 489 The list of errors you can ignore can be found on the 490 documentation: 491 492 <https://wiki.openssl.org/index.php/Manual:Verify(1)> 493 494 Example: 495 496 ignoreVerifyErrors => [ 9, 10, ] 497 498 handshakeDone(options) 499 Returns *true* if the handshake is done and all data for handshake 500 has been written out. It accepts the following options: 501 502 ignorebuf 503 Returns *true* if OpenSSL has established the connection, 504 regardless if all data has been written out. This is needed if you 505 want to exchange the Filter of *POE::Wheel::ReadWrite* before the 506 first data comes in. This option have been only used by 507 *doHandshake()* to be able to add new filters before first 508 cleartext data to be processed gets in. 509 510 clientCertNotOnCRL($file) 511 Verifies if the serial of the client certificate is not contained in 512 the CRL $file. No file caching is done, each call opens the file 513 again. 514 515 WARNING: If your CRL file is missing, can not be opened is empty or 516 has no blocked certificate at all in it, then every call will get 517 blocked! 518 519 clientCertIds() 520 Returns an array of every certificate found by OpenSSL. Each element 521 is again a array. The first element is the value of 522 *X509_get_subject_name*, second is the value of 523 *X509_get_issuer_name* and third element is the serial of the 524 certificate in binary form. You have to use *split()* and *ord()*, 525 or the *hexdump()* function, to convert it to a readable form. 526 527 Example: 528 529 my ($certid) = ($heap->{sslfilter}->clientCertIds()); 530 $certid = $certid ? $certid->[0]."<br>".$certid->[1]."<br>SERIAL=".$heap->{sslfilter}->hexdump($certid->[2]) : 'No client certificate'; 531 532 getCipher() 533 Returns the used cryptographic algorithm and length. 534 535 Example: 536 537 $sslfilter->getCipher() 538 539 clientCertValid() 540 Returns *true* if there is a client certificate that is valid. It 541 also tests against the CRL, if you have the *cacrl* option set on 542 *new()*. 543 544 doHandshake($readWrite, $filter, $filter, ...) !!!REMOVED!!! 545 WARNING: POE::Filter:SSL now is able to do the ssh handshake now 546 without any helpers. Because of this, this function has been 547 removed! 548 549 Allows one to add filters after the ssl handshake. It has to be 550 called in the input handler, and needs the passing of the 551 *POE::Wheel::ReadWhile* object. If it returns false, you have to 552 return from the input handler. 553 554 See the *HTTPS-Server*, *SSL on an established connection* and 555 *Client certificate verification* examples in *SYNOPSIS* 556 557 clientCertExists() 558 Returns *true* if there is a client certificate, that might be 559 untrusted. 560 561 WARNING: If the client provides an untrusted client certificate a 562 client certificate that is listed in CRL, this function returns 563 *true*. You have to ask *clientCertValid()* if the certificate is 564 valid! 565 566 errorhandler 567 By default, every ssl error is escalated via carp. You may change 568 this behaviour via this option to: 569 570 "ignore" 571 Do not report any error. 572 573 *CODE* 574 Setting errorhandler to a reference of a function allows one to be 575 called it callback function with the following options: 576 577 ARG1: POE:SSL::Filter instance 578 579 ARG2: Ref on a Hash with the following keys: 580 581 ret The return code of Net::SSLeay::connect (client) or Net::SSLeay::accept (server) 582 ssl The SSL context (SSL_CTX) 583 msg The error message as text, as normally reported via carp 584 get_error The error code of get_error the ssl context 585 error The error code of get_error without context 586 587 "carp" (or undef) 588 Do Carp/carp on error. 589 590 "confess" 591 Do Carp/confess (stacktrace) on error. 592 593 "carponetime" 594 Report carp for one occurrence only one time - over all! 595 596 debug 597 Shows debug messages of *clientCertNotOnCRL()*. 598 599 hexdump($string) 600 Returns string data in hex format. 601 602 Example: 603 604 perl -e 'use POE::Filter::SSL; print POE::Filter::SSL->hexdump("test")."\n";' 605 74:65:73:74 606 607 Internal functions and POE::Filter handler 608 VERIFY() 609 POE_FILTER_X509_get_serialNumber() 610 POE_FILTER_SSL_CTX_set_tmp_dh() 611 POE_FILTER_SSL_CTX_set_tmp_rsa() 612 POE_FILTER_SSL_set_tmp_dh() 613 clone() 614 doSSL() 615 get() 616 get_one() 617 get_one_start() 618 get_pending() 619 writeToSSLBIO() 620 writeToSSL() 621 put() 622 verify_serial_against_crl_file() 623 DOSENDBACK() 624 checkForDoSendback() 625 CTX_add_client_CA() 626 PEMdataToEVP_PKEY 627 PEMdataToX509 628 dataToBio 629 630AUTHOR 631 Markus Schraeder, "<privi at cpan.org>" 632 633BUGS 634 Please report any bugs or feature requests to "bug-poe-filter-ssl at 635 rt.cpan.org", or through the web interface at 636 <http://rt.cpan.org/NoAuth/ReportBug.html?Queue=POE-Filter-SSL>. I will 637 be notified, and then you'll automatically be notified of progress on 638 your bug as I make changes. 639 640SUPPORT 641 You can find documentation for this module with the perldoc command. 642 643 perldoc POE::Filter::SSL 644 645 You can also look for information at: 646 647 * RT: CPAN's request tracker 648 649 <http://rt.cpan.org/NoAuth/Bugs.html?Dist=POE-Filter-SSL> 650 651 * AnnoCPAN: Annotated CPAN documentation 652 653 <http://annocpan.org/dist/POE-Filter-SSL> 654 655 * CPAN Ratings 656 657 <http://cpanratings.perl.org/d/POE-Filter-SSL> 658 659 * Search CPAN 660 661 <http://search.cpan.org/dist/POE-Filter-SSL> 662 663Commercial support 664 Commercial support can be gained at <sslsupport at cryptomagic.eu>. 665 666 Used in our products, you can find on <https://www.cryptomagic.eu/> 667 668COPYRIGHT & LICENSE 669 Copyright 2010-2017 Markus Schraeder, CryptoMagic GmbH, all rights 670 reserved. 671 672 This program is free software; you can redistribute it and/or modify it 673 under the same terms as Perl itself. 674 675