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