File Coverage

blib/lib/IO/Async/SSL.pm
Criterion Covered Total %
statement 147 152 96.7
branch 54 70 77.1
condition 30 45 66.6
subroutine 25 26 96.1
pod 0 6 0.0
total 256 299 85.6


line stmt bran cond sub pod time code
1             # You may distribute under the terms of either the GNU General Public License
2             # or the Artistic License (the same terms as Perl itself)
3             #
4             # (C) Paul Evans, 2010-2023 -- leonerd@leonerd.org.uk
5              
6             package IO::Async::SSL 0.25;
7              
8 11     11   2209858 use v5.14;
  11         47  
9 11     11   72 use warnings;
  11         89  
  11         943  
10              
11 11     11   87 use Carp;
  11         21  
  11         1274  
12              
13 11     11   495 use POSIX qw( EAGAIN EWOULDBLOCK );
  11         5188  
  11         126  
14              
15 11     11   14000 use IO::Socket::SSL 2.003 qw( $SSL_ERROR SSL_WANT_READ SSL_WANT_WRITE ); # default_ca
  11         1322036  
  11         96  
16             # require >= 2.003 for bugfixes - see RT#125220
17              
18 11     11   8691 use Future 0.33; # ->catch_with_f
  11         121824  
  11         626  
19 11     11   6092 use IO::Async::Handle 0.29;
  11         129114  
  11         577  
20 11     11   1402 use IO::Async::Loop 0.61; # new Listen API
  11         25959  
  11         26829  
21              
22             =head1 NAME
23              
24             C - use SSL/TLS with L
25              
26             =head1 SYNOPSIS
27              
28             use IO::Async::Loop;
29             use IO::Async::SSL;
30              
31             my $loop = IO::Async::Loop->new();
32              
33             $loop->SSL_connect(
34             host => "www.example.com",
35             service => "https",
36              
37             on_stream => sub {
38             my ( $stream ) = @_;
39              
40             $stream->configure(
41             on_read => sub {
42             ...
43             },
44             );
45              
46             $loop->add( $stream );
47              
48             ...
49             },
50              
51             on_resolve_error => sub { print STDERR "Cannot resolve - $_[0]\n"; },
52             on_connect_error => sub { print STDERR "Cannot connect\n"; },
53             on_ssl_error => sub { print STDERR "Cannot negotiate SSL - $_[-1]\n"; },
54             );
55              
56             =head1 DESCRIPTION
57              
58             This module extends existing L classes with extra methods to allow
59             the use of SSL or TLS-based connections using L. It does not
60             directly provide any methods or functions of its own.
61              
62             Primarily, it provides C and C, which yield
63             C-upgraded socket handles or L
64             instances, and two forms of C to upgrade an existing TCP
65             connection to use SSL.
66              
67             As an additional convenience, if the C and C
68             options are omitted, the module will attempt to provide them by querying the
69             result of L's C function. Otherwise, the module
70             will print a warning and set C instead.
71              
72             =cut
73              
74             my %SSL_ca_args = IO::Socket::SSL::default_ca();
75              
76             sub _SSL_args
77             {
78 33     33   303 my %args = @_;
79              
80             # SSL clients (i.e. non-server) require a verify mode
81 33 50 100     262 if( !$args{SSL_server} and !defined $args{SSL_verify_mode} and
      100        
      66        
82             !defined $args{SSL_ca_file} and !defined $args{SSL_ca_path} ) {
83 1 50       6 unless( %SSL_ca_args ) {
84 0         0 carp "Unable to set SSL_VERIFY_PEER because IO::Socket::SSL::default_ca() gives nothing";
85 0         0 $SSL_ca_args{SSL_verify_mode} = IO::Socket::SSL::SSL_VERIFY_NONE();
86             }
87              
88 1         8 %args = ( %SSL_ca_args, %args );
89             }
90              
91 33         677 return %args;
92             }
93              
94             sub sslread
95             {
96 293     293 0 265674 my $stream = shift;
97 293         668 my ( $fh, undef, $len ) = @_;
98              
99 293         866 my $ret = $stream->_sysread( $fh, $_[1], $len );
100              
101 293   66     30312 my $read_wants_write = !defined $ret &&
102             ( $! == EAGAIN or $! == EWOULDBLOCK ) &&
103             $SSL_ERROR == SSL_WANT_WRITE;
104 293         1135 $stream->want_writeready_for_read( $read_wants_write );
105              
106             # It's possible SSL_read took all the data out of the filehandle, thus
107             # making it not appear read-ready any more.
108 293 100       6803 if( $fh->pending ) {
109 16     16   273 $stream->loop->later( sub { $stream->on_read_ready } );
  16         4214  
110             }
111              
112 293         4915 return $ret;
113             }
114              
115             sub sslwrite
116             {
117 263     263 0 119873 my $stream = shift;
118 263         651 my ( $fh, undef, $len ) = @_;
119              
120             # Placate RT98372
121 263 50       1105 utf8::downgrade( $_[1] ) or
122             carp "Wide character in sslwrite";
123              
124 263         772 my $ret = $stream->_syswrite( $fh, $_[1], $len );
125              
126 263   33     29305 my $write_wants_read = !defined $ret &&
127             ( $! == EAGAIN or $! == EWOULDBLOCK ) &&
128             $SSL_ERROR == SSL_WANT_READ;
129 263         885 $stream->want_readready_for_write( $write_wants_read );
130             # If write wants read, there's no point waiting on writereadiness either
131 263         5865 $stream->want_writeready_for_write( !$write_wants_read );
132              
133 263         5202 return $ret;
134             }
135              
136             =head1 LOOP METHODS
137              
138             The following extra methods are added to L.
139              
140             =cut
141              
142             =head2 SSL_upgrade
143              
144             ( $stream or $socket ) = $loop->SSL_upgrade( %params )->get;
145              
146             This method upgrades a given stream filehandle into an SSL-wrapped stream,
147             returning a future which will yield the given stream object or socket.
148              
149             Takes the following parameters:
150              
151             =over 8
152              
153             =item handle => IO::Async::Stream | IO
154              
155             The C object containing the IO handle of an
156             already-established connection to act as the transport for SSL; or the plain
157             IO socket handle itself.
158              
159             If an C is passed it will have the C and C
160             functions set on it suitable for SSL use, and will be returned as the result
161             from the future.
162              
163             If a plain socket handle is passed, that will be returned from the future
164             instead.
165              
166             =item SSL_server => BOOL
167              
168             If true, indicates this is the server side of the connection.
169              
170             =back
171              
172             In addition, any parameter whose name starts C will be passed to the
173             C constructor.
174              
175             The following legacy callback arguments are also supported, in case the
176             returned future is not used:
177              
178             =over 8
179              
180             =item on_upgraded => CODE
181              
182             A continuation that is invoked when the socket has been successfully upgraded
183             to SSL. It will be passed an instance of an C, which will
184             have appropriate SSL-compatible reader/writer functions attached.
185              
186             $on_upgraded->( $sslsocket )
187              
188             =item on_error => CODE
189              
190             A continuation that is invoked if C detects an error while
191             negotiating the upgrade.
192              
193             $on_error->( $! )
194              
195             =back
196              
197             =cut
198              
199             sub IO::Async::Loop::SSL_upgrade
200             {
201 22     22 0 576286 my $loop = shift;
202 22         149 my %params = @_;
203              
204 22         139 my $f = $loop->new_future;
205              
206 22 50       5683 $params{handle} or croak "Expected 'handle'";
207              
208 22         69 my $stream;
209             my $socket;
210 22 100       247 if( $params{handle}->isa( "IO::Async::Stream" ) ) {
211 16         77 $stream = delete $params{handle};
212 16         76 $socket = $stream->read_handle;
213             }
214             else {
215 6         18 $socket = delete $params{handle};
216             }
217              
218             {
219 22         109 my $on_upgraded = delete $params{on_upgraded} or defined wantarray
220 22 50 66     220 or croak "Expected 'on_upgraded' or to return a Future";
221             my $on_error = delete $params{on_error} or defined wantarray
222 22 50 66     161 or croak "Expected 'on_error' or to return a Future";
223              
224 22 100       135 $f->on_done( $on_upgraded ) if $on_upgraded;
225 22 100       308 $f->on_fail( $on_error ) if $on_error;
226             }
227              
228 22         347 my %ssl_params = map { $_ => delete $params{$_} } grep m/^SSL_/, keys %params;
  45         244  
229              
230             eval {
231             $socket = IO::Socket::SSL->start_SSL( $socket, _SSL_args
232             SSL_startHandshake => 0,
233              
234             # Required to make IO::Socket::SSL not ->close before we have a chance to remove it from the loop
235       4     SSL_error_trap => sub { },
236              
237 22 100       287 %ssl_params,
238             ) or die IO::Socket::SSL->errstr;
239 22 100       66 } or do {
240 1         122362 chomp( my $e = $@ );
241 1         10 return $f->fail( $e, "ssl" );
242             };
243              
244 21 100       1995762 my $ready_method = $ssl_params{SSL_server} ? "accept_SSL" : "connect_SSL";
245              
246             my $ready = sub {
247 47     47   52438 my ( $self ) = @_;
248 47 100       441 if( $socket->$ready_method ) {
249 17         23027 $loop->remove( $self );
250              
251 17 100       4474 if( $stream ) {
252 15         156 $stream->configure(
253             handle => $socket,
254             reader => \&sslread,
255             writer => \&sslwrite,
256             );
257             }
258              
259 17   66     3867 $f->done( $stream || $socket );
260 17         3474 return;
261             }
262              
263 30 100 66     52301 if( $! != EAGAIN and $! != EWOULDBLOCK ) {
264 4         15 my $errstr = IO::Socket::SSL::errstr();
265 4         56 $loop->remove( $self );
266 4         1177 $f->fail( $errstr, "ssl" );
267 4         680 return;
268             }
269              
270 26         187 $self->want_readready ( $SSL_ERROR == SSL_WANT_READ );
271 26         340 $self->want_writeready( $SSL_ERROR == SSL_WANT_WRITE );
272 21         306 };
273              
274             # We're going to steal the IO handle from $stream, so we'll have to
275             # temporarily deconfigure it
276 21 100       274 $stream->configure( handle => undef ) if $stream;
277              
278 21         4165 $loop->add( my $handle = IO::Async::Handle->new(
279             handle => $socket,
280             on_read_ready => $ready,
281             on_write_ready => $ready,
282             ) );
283              
284 21         11356 $ready->( $handle );
285              
286 21 100       619 return $f if defined wantarray;
287              
288             # Caller is not going to keep hold of the Future, so we have to ensure it
289             # stays alive somehow
290 2     2   29 $f->on_ready( sub { undef $f } ); # intentional cycle
  2         58  
291             }
292              
293             =head2 SSL_connect
294              
295             $stream = $loop->SSL_connect( %params )->get;
296              
297             This method performs a non-blocking connection to a given address or set of
298             addresses, upgrades the socket to SSL, then yields a C
299             object when the SSL handshake is complete.
300              
301             It takes all the same arguments as C. Any argument
302             whose name starts C will be passed on to the L
303             constructor rather than the Loop's C method. It is not required to
304             pass the C option, as SSL implies this will be C.
305              
306             This method can also upgrade an existing C or subclass
307             instance given as the C argument, by setting the C and
308             C functions.
309              
310             =head2 SSL_connect (void)
311              
312             $loop->SSL_connect( %params,
313             on_connected => sub { ... },
314             on_stream => sub { ... },
315             );
316              
317             When not returning a future, this method also supports the C and
318             C continuations.
319              
320             In addition, the following arguments are then required:
321              
322             =over 8
323              
324             =item on_ssl_error => CODE
325              
326             A continuation that is invoked if C detects an SSL-based
327             error once the actual stream socket is connected.
328              
329             =back
330              
331             If the C continuation is used, the socket handle it yields will
332             be a C, which must be wrapped in C to
333             be used by C. The C continuation will already yield such
334             an instance.
335              
336             =cut
337              
338             sub IO::Async::Loop::SSL_connect
339             {
340 6     6 0 1530061 my $loop = shift;
341 6         178 my %params = @_;
342              
343 6         98 my %ssl_params = map { $_ => delete $params{$_} } grep m/^SSL_/, keys %params;
  5         67  
344              
345 6         32 my $on_done;
346 6 100       70 if( exists $params{on_connected} ) {
    100          
347 1         4 my $on_connected = delete $params{on_connected};
348             $on_done = sub {
349 0     0   0 my ( $stream ) = @_;
350 0         0 $on_connected->( $stream->read_handle );
351 1         23 };
352             }
353             elsif( exists $params{on_stream} ) {
354 1         2 my $on_stream = delete $params{on_stream};
355 1         3 $on_done = $on_stream;
356             }
357             else {
358 4 50       75 croak "Expected 'on_connected' or 'on_stream' or to return a Future" unless defined wantarray;
359             }
360              
361             my $on_ssl_error = delete $params{on_ssl_error} or defined wantarray or
362 6 50 66     82 croak "Expected 'on_ssl_error' or to return a Future";
363              
364 6   66     82 my $stream = delete $params{handle} || do {
365             require IO::Async::Stream;
366             IO::Async::Stream->new;
367             };
368              
369 6 50       514 $stream->isa( "IO::Async::Stream" ) or
370             croak "Can only SSL_connect a handle instance of IO::Async::Stream";
371              
372             # Don't ->connect with the handle yet, because we'll first have to use the
373             # socket to perform SSL_upgrade on. We don't want to confuse the loop by
374             # giving it the same fd twice.
375              
376             my $f = $loop->connect(
377             socktype => 'stream', # SSL over DGRAM or RAW makes no sense
378             %params,
379             )->then( sub {
380 6     6   26576 my ( $socket ) = @_;
381              
382 6         82 $stream->configure( handle => $socket );
383              
384 6         1793 $loop->SSL_upgrade(
385             _SSL_args( %ssl_params ),
386             handle => $stream,
387             )
388 6         177 });
389              
390 6 100       85408 $f->on_done( $on_done ) if $on_done;
391             $f->on_fail( sub {
392 1 50 33 1   214 $on_ssl_error->( $_[0] ) if defined $_[1] and $_[1] eq "ssl";
393 6 100       147 }) if $on_ssl_error;
394              
395 6 100       451 return $f if defined wantarray;
396              
397             # Caller is not going to keep hold of the Future, so we have to ensure it
398             # stays alive somehow
399 2     2   17 $f->on_ready( sub { undef $f } ); # intentional cycle
  2         138  
400             }
401              
402             =head2 SSL_listen
403              
404             $loop->SSL_listen( %params )->get;
405              
406             This method sets up a listening socket using the addresses given, and will
407             invoke the callback each time a new connection is accepted on the socket and
408             the SSL handshake has been completed. This can be either the C or
409             C continuation; C is not supported.
410              
411             It takes all the same arguments as C. Any argument
412             whose name starts C will be passed on to the L
413             constructor rather than the Loop's C method. It is not required to
414             pass the C option, as SSL implies this will be C.
415              
416             In addition, the following arguments are rquired:
417              
418             =over 8
419              
420             =item on_ssl_error => CODE
421              
422             A continuation that is invoked if C detects an SSL-based
423             error once the actual stream socket is connected.
424              
425             =back
426              
427             The underlying L socket will also require the server key and
428             certificate for a server-mode socket. See its documentation for more details.
429              
430             If the C continuation is used, the socket handle it yields will be
431             a C, which must be wrapped in C to be
432             used by C. The C continuation will already yield such an
433             instance.
434              
435             =cut
436              
437             sub IO::Async::Loop::SSL_listen
438             {
439 5     5 0 781458 my $loop = shift;
440 5         61 my %params = @_;
441              
442 5         145 my %ssl_params = map { $_ => delete $params{$_} } grep m/^SSL_/, keys %params;
  10         45  
443             my $on_ssl_error = delete $params{on_ssl_error} or defined wantarray
444 5 50 66     75 or croak "Expected 'on_ssl_error'";
445              
446             my $f = $loop->listen(
447             socktype => 'stream',
448             %params,
449             )->on_done( sub {
450 5     5   24658 my $listener = shift;
451              
452 5         91 my $cleartext_acceptor = $listener->acceptor;
453             my $ssl_acceptor = sub {
454 5         127049 my $listener = shift;
455 5         24 my ( $listen_sock, %params ) = @_;
456 5         15 my $stream = $params{handle};
457 5 50 66     82 !defined $stream or $stream->isa( "IO::Async::Stream" ) or
458             croak "Can only accept SSL on IO::Async::Stream handles";
459              
460             $listener->$cleartext_acceptor( $listen_sock )->then( sub {
461 5         2258 my ( $socket ) = @_;
462              
463 5 50       44 return Future->done() unless $socket; # EAGAIN
464              
465 5 100       31 $stream->configure( handle => $socket ) if $stream;
466              
467             $loop->SSL_upgrade(
468             _SSL_args( SSL_server => 1, %ssl_params ),
469             handle => ( $stream || $socket ),
470             )->catch_with_f( ssl => sub {
471 1         125 my ( $f, $failure ) = @_;
472 1 50       6 if( $on_ssl_error ) {
473 1         24 $on_ssl_error->( $failure );
474 1         14 return Future->done; # eat it
475             }
476 0         0 return $f;
477 5   66     709 });
478 5         38 });
479 5         68 };
480              
481 5         50 $listener->configure( acceptor => $ssl_acceptor );
482 5         61 });
483              
484 5 100       127316 return $f if defined wantarray;
485              
486             # Caller is not going to keep hold of the Future, so we have to ensure it
487             # stays alive somehow
488 2     2   71 $f->on_ready( sub { undef $f } ); # intentional cycle
  2         145  
489             }
490              
491             =head1 STREAM PROTOCOL METHODS
492              
493             The following extra methods are added to L.
494              
495             =cut
496              
497             =head2 SSL_upgrade
498              
499             $protocol->SSL_upgrade( %params )->get;
500              
501             A shortcut to calling C<< $loop->SSL_upgrade >>. This method will unconfigure
502             the C of the Protocol, upgrade its underlying filehandle to SSL,
503             then reconfigure it again with SSL reader and writer functions on it. It takes
504             the same arguments as C<< $loop->SSL_upgrade >>, except that the C
505             argument is not required as it's taken from the Protocol's C.
506              
507             =cut
508              
509             sub IO::Async::Protocol::Stream::SSL_upgrade
510             {
511 2     2 0 205329 my $protocol = shift;
512 2         11 my %params = @_;
513              
514 2 50       20 my $on_upgraded = delete $params{on_upgraded} or croak "Expected 'on_upgraded'";
515              
516 2 50       10 my $loop = $protocol->get_loop or croak "Expected to be a member of a Loop";
517              
518 2         13 my $transport = $protocol->transport;
519              
520 2         12 $protocol->configure( transport => undef );
521              
522             $loop->SSL_upgrade(
523             handle => $transport,
524             on_upgraded => sub {
525 2     2   167 my ( $transport ) = @_;
526              
527 2         12 $protocol->configure( transport => $transport );
528              
529 2         1321 $on_upgraded->();
530             },
531              
532 2         809 %params,
533             );
534             }
535              
536             =head1 AUTHOR
537              
538             Paul Evans
539              
540             =cut
541              
542             0x55AA;