File Coverage

blib/lib/Mojo/IOLoop/TLS.pm
Criterion Covered Total %
statement 33 64 51.5
branch 5 34 14.7
condition 0 12 0.0
subroutine 12 16 75.0
pod 3 3 100.0
total 53 129 41.0


line stmt bran cond sub pod time code
1             package Mojo::IOLoop::TLS;
2 67     67   551276 use Mojo::Base 'Mojo::EventEmitter';
  67         133  
  67         352  
3              
4 67     67   17145 use Mojo::File qw(curfile);
  67         165  
  67         3685  
5 67     67   3752 use Mojo::IOLoop;
  67         333  
  67         780  
6 67     67   359 use Scalar::Util qw(weaken);
  67         102  
  67         6313  
7              
8             # TLS support requires IO::Socket::SSL
9 67 100   67   377 use constant TLS => $ENV{MOJO_NO_TLS} ? 0 : !!eval { require IO::Socket::SSL; IO::Socket::SSL->VERSION('2.009'); 1 };
  67         90  
  67         390  
  65         54883  
  65         3568562  
  65         5499  
10 67     67   722 use constant READ => TLS ? IO::Socket::SSL::SSL_WANT_READ() : 0;
  67         221  
  67         3242  
11 67     67   294 use constant WRITE => TLS ? IO::Socket::SSL::SSL_WANT_WRITE() : 0;
  67         149  
  67         59598  
12              
13             has reactor => sub { Mojo::IOLoop->singleton->reactor }, weak => 1;
14              
15             # To regenerate the certificate run this command (28.06.2019)
16             # openssl req -x509 -newkey rsa:4096 -nodes -sha256 -out server.crt \
17             # -keyout server.key -days 7300 -subj '/CN=localhost'
18             my $CERT = curfile->sibling('resources', 'server.crt')->to_string;
19             my $KEY = curfile->sibling('resources', 'server.key')->to_string;
20              
21 2     2   6 sub DESTROY { shift->_cleanup }
22              
23 181     181 1 1432 sub can_tls {TLS}
24              
25             sub negotiate {
26 2 50   2 1 11 my ($self, $args) = (shift, ref $_[0] ? $_[0] : {@_});
27              
28 2         7 return $self->emit(error => 'IO::Socket::SSL 2.009+ required for TLS support') unless TLS;
29              
30 0         0 my $handle = $self->{handle};
31             return $self->emit(error => $IO::Socket::SSL::SSL_ERROR)
32 0 0       0 unless IO::Socket::SSL->start_SSL($handle, %{$self->_expand($args)});
  0         0  
33 0     0   0 $self->reactor->io($handle => sub { $self->_tls($handle, $args->{server}) });
  0         0  
34             }
35              
36 2     2 1 13 sub new { shift->SUPER::new(handle => shift) }
37              
38             sub _cleanup {
39 2     2   5 my $self = shift;
40 2 50       5 return undef unless my $reactor = $self->reactor;
41 2 50       8 $reactor->remove($self->{handle}) if $self->{handle};
42 2         14 return $self;
43             }
44              
45             sub _expand {
46 0     0     my ($self, $args) = @_;
47              
48 0           weaken $self;
49 0     0     my $tls = {SSL_error_trap => sub { $self->_cleanup->emit(error => $_[1]) }, SSL_startHandshake => 0};
  0            
50 0 0 0       $tls->{SSL_ca_file} = $args->{tls_ca} if $args->{tls_ca} && -T $args->{tls_ca};
51 0 0         $tls->{SSL_cert_file} = $args->{tls_cert} if $args->{tls_cert};
52 0 0         $tls->{SSL_key_file} = $args->{tls_key} if $args->{tls_key};
53 0 0         $tls->{SSL_server} = $args->{server} if $args->{server};
54 0 0         @{$tls}{keys %{$args->{tls_options}}} = values %{$args->{tls_options}} if $args->{tls_options};
  0            
  0            
  0            
55              
56 0 0         if ($args->{server}) {
57 0   0       $tls->{SSL_cert_file} ||= $CERT;
58 0   0       $tls->{SSL_key_file} ||= $KEY;
59             }
60             else {
61 0 0         unless (exists $tls->{SSL_hostname}) {
62 0 0         $tls->{SSL_hostname} = IO::Socket::SSL->can_client_sni ? $args->{address} : '';
63             }
64 0   0       $tls->{SSL_verifycn_name} ||= $args->{address};
65             }
66              
67 0           return $tls;
68             }
69              
70             sub _tls {
71 0     0     my ($self, $handle, $server) = @_;
72              
73             # Switch between reading and writing
74 0 0         if (!($server ? $handle->accept_SSL : $handle->connect_SSL)) {
    0          
75 0           my $err = $IO::Socket::SSL::SSL_ERROR;
76 0 0         if ($err == READ) { $self->reactor->watch($handle, 1, 0) }
  0 0          
77 0           elsif ($err == WRITE) { $self->reactor->watch($handle, 1, 1) }
78             }
79              
80 0           else { $self->_cleanup->emit(upgrade => delete $self->{handle}) }
81             }
82              
83             1;
84              
85             =encoding utf8
86              
87             =head1 NAME
88              
89             Mojo::IOLoop::TLS - Non-blocking TLS handshake
90              
91             =head1 SYNOPSIS
92              
93             use Mojo::IOLoop::TLS;
94              
95             # Negotiate TLS
96             my $tls = Mojo::IOLoop::TLS->new($old_handle);
97             $tls->on(upgrade => sub ($tls, $new_handle) {...});
98             $tls->on(error => sub ($tls, $err) {...});
99             $tls->negotiate(server => 1, tls_version => 'TLSv1_2');
100              
101             # Start reactor if necessary
102             $tls->reactor->start unless $tls->reactor->is_running;
103              
104             =head1 DESCRIPTION
105              
106             L negotiates TLS for L.
107              
108             =head1 EVENTS
109              
110             L inherits all events from L and can emit the following new ones.
111              
112             =head2 upgrade
113              
114             $tls->on(upgrade => sub ($tls, $handle) {...});
115              
116             Emitted once TLS has been negotiated.
117              
118             =head2 error
119              
120             $tls->on(error => sub ($tls, $err) {...});
121              
122             Emitted if an error occurs during negotiation, fatal if unhandled.
123              
124             =head1 ATTRIBUTES
125              
126             L implements the following attributes.
127              
128             =head2 reactor
129              
130             my $reactor = $tls->reactor;
131             $tls = $tls->reactor(Mojo::Reactor::Poll->new);
132              
133             Low-level event reactor, defaults to the C attribute value of the global L singleton. Note that
134             this attribute is weakened.
135              
136             =head1 METHODS
137              
138             L inherits all methods from L and implements the following new ones.
139              
140             =head2 can_tls
141              
142             my $bool = Mojo::IOLoop::TLS->can_tls;
143              
144             True if L 2.009+ is installed and TLS support enabled.
145              
146             =head2 negotiate
147              
148             $tls->negotiate(server => 1, tls_version => 'TLSv1_2');
149             $tls->negotiate({server => 1, tls_version => 'TLSv1_2'});
150              
151             Negotiate TLS.
152              
153             These options are currently available:
154              
155             =over 2
156              
157             =item server
158              
159             server => 1
160              
161             Negotiate TLS from the server-side, defaults to the client-side.
162              
163             =item tls_ca
164              
165             tls_ca => '/etc/tls/ca.crt'
166              
167             Path to TLS certificate authority file.
168              
169             =item tls_cert
170              
171             tls_cert => '/etc/tls/server.crt'
172             tls_cert => {'mojolicious.org' => '/etc/tls/mojo.crt'}
173              
174             Path to the TLS cert file, defaults to a built-in test certificate on the server-side.
175              
176             =item tls_key
177              
178             tls_key => '/etc/tls/server.key'
179             tls_key => {'mojolicious.org' => '/etc/tls/mojo.key'}
180              
181             Path to the TLS key file, defaults to a built-in test key on the server-side.
182              
183             =item tls_options
184              
185             tls_options => {SSL_alpn_protocols => ['foo', 'bar'], SSL_verify_mode => 0x00, SSL_version => 'TLSv1_2'}
186              
187             Additional options for L.
188              
189             =back
190              
191             =head2 new
192              
193             my $tls = Mojo::IOLoop::TLS->new($handle);
194              
195             Construct a new L object.
196              
197             =head1 SEE ALSO
198              
199             L, L, L.
200              
201             =cut