Skip to content

Commit

Permalink
add Mojo::IOLoop::TLS class
Browse files Browse the repository at this point in the history
  • Loading branch information
kraih committed Jan 12, 2017
1 parent 4e2cc18 commit 47ef225
Show file tree
Hide file tree
Showing 9 changed files with 100 additions and 72 deletions.
78 changes: 19 additions & 59 deletions lib/Mojo/IOLoop/Server.pm
Expand Up @@ -3,31 +3,17 @@ use Mojo::Base 'Mojo::EventEmitter';

use Carp 'croak';
use IO::Socket::IP;
use Mojo::File 'path';
use Mojo::IOLoop;
use Mojo::IOLoop::TLS;
use Scalar::Util 'weaken';
use Socket qw(IPPROTO_TCP TCP_NODELAY);

# TLS support requires IO::Socket::SSL
use constant TLS => $ENV{MOJO_NO_TLS}
? 0
: eval 'use IO::Socket::SSL 1.94 (); 1';
use constant TLS_READ => TLS ? IO::Socket::SSL::SSL_WANT_READ() : 0;
use constant TLS_WRITE => TLS ? IO::Socket::SSL::SSL_WANT_WRITE() : 0;

# To regenerate the certificate run this command (18.04.2012)
# openssl req -new -x509 -keyout server.key -out server.crt -nodes -days 7300
my $CERT = path(__FILE__)->dirname->child('resources', 'server.crt')->to_string;
my $KEY = path(__FILE__)->dirname->child('resources', 'server.key')->to_string;

has reactor => sub { Mojo::IOLoop->singleton->reactor };

sub DESTROY {
my $self = shift;
$ENV{MOJO_REUSE} =~ s/(?:^|\,)\Q$self->{reuse}\E// if $self->{reuse};
return unless my $reactor = $self->reactor;
$self->stop if $self->{handle};
$reactor->remove($_) for values %{$self->{handles}};
$self->stop if $self->{handle} && $self->reactor;
}

sub generate_port {
Expand Down Expand Up @@ -79,25 +65,9 @@ sub listen {
@$self{qw(handle single_accept)} = ($handle, $args->{single_accept});

return unless $args->{tls};
croak 'IO::Socket::SSL 1.94+ required for TLS support' unless TLS;

weaken $self;
my $tls = $self->{tls} = {
SSL_cert_file => $args->{tls_cert} || $CERT,
SSL_error_trap => sub {
return unless my $handle = delete $self->{handles}{shift()};
$self->reactor->remove($handle);
close $handle;
},
SSL_honor_cipher_order => 1,
SSL_key_file => $args->{tls_key} || $KEY,
SSL_startHandshake => 0,
SSL_verify_mode => $args->{tls_verify} // ($args->{tls_ca} ? 0x03 : 0x00)
};
$tls->{SSL_ca_file} = $args->{tls_ca}
if $args->{tls_ca} && -T $args->{tls_ca};
$tls->{SSL_cipher_list} = $args->{tls_ciphers} if $args->{tls_ciphers};
$tls->{SSL_version} = $args->{tls_version} if $args->{tls_version};
croak 'IO::Socket::SSL 1.94+ required for TLS support'
unless Mojo::IOLoop::TLS->has_tls;
$self->{args} = $args;
}

sub port { shift->{handle}->sockport }
Expand All @@ -124,31 +94,21 @@ sub _accept {
setsockopt $handle, IPPROTO_TCP, TCP_NODELAY, 1;

# Start TLS handshake
$self->emit(accept => $handle) and next unless my $tls = $self->{tls};
$self->_handshake($self->{handles}{$handle} = $handle)
if $handle = IO::Socket::SSL->start_SSL($handle, %$tls, SSL_server => 1);
}
}

sub _handshake {
my ($self, $handle) = @_;
weaken $self;
$self->reactor->io($handle => sub { $self->_tls($handle) });
}

sub _tls {
my ($self, $handle) = @_;

# Accepted
if ($handle->accept_SSL) {
$self->reactor->remove($handle);
return $self->emit(accept => delete $self->{handles}{$handle});
$self->emit(accept => $handle) and next unless my $args = $self->{args};
my $id = "$handle";
my $tls = $self->{handles}{$id}
= Mojo::IOLoop::TLS->new(reactor => $self->reactor);
weaken $tls->{reactor};
$tls->on(
negotiated => sub {
my $handle = pop;
delete $self->{handles}{$id};
$self->emit(accept => $handle);
}
);
$tls->on(error => sub { delete $self->{handles}{$id} });
$tls->negotiate(%$args, handle => $handle);
}

# Switch between reading and writing
my $err = $IO::Socket::SSL::SSL_ERROR;
if ($err == TLS_READ) { $self->reactor->watch($handle, 1, 0) }
elsif ($err == TLS_WRITE) { $self->reactor->watch($handle, 1, 1) }
}

1;
Expand Down
68 changes: 68 additions & 0 deletions lib/Mojo/IOLoop/TLS.pm
@@ -0,0 +1,68 @@
package Mojo::IOLoop::TLS;
use Mojo::Base 'Mojo::EventEmitter';

use Mojo::File 'path';
use Mojo::IOLoop;
use Scalar::Util 'weaken';

# TLS support requires IO::Socket::SSL
use constant TLS => $ENV{MOJO_NO_TLS}
? 0
: eval 'use IO::Socket::SSL 1.94 (); 1';
use constant TLS_READ => TLS ? IO::Socket::SSL::SSL_WANT_READ() : 0;
use constant TLS_WRITE => TLS ? IO::Socket::SSL::SSL_WANT_WRITE() : 0;

has reactor => sub { Mojo::IOLoop->singleton->reactor };

# To regenerate the certificate run this command (18.04.2012)
# openssl req -new -x509 -keyout server.key -out server.crt -nodes -days 7300
my $CERT = path(__FILE__)->dirname->child('resources', 'server.crt')->to_string;
my $KEY = path(__FILE__)->dirname->child('resources', 'server.key')->to_string;

sub DESTROY {
my $self = shift;
return unless my $reactor = $self->reactor;
$reactor->remove($self->{handle}) if $self->{handle};
}

sub has_tls {TLS}

sub negotiate {
my ($self, $args) = (shift, ref $_[0] ? $_[0] : {@_});

return $self->emit(error => 'IO::Socket::SSL 1.94+ required for TLS support')
unless TLS;

weaken $self;
my $tls = {
SSL_cert_file => $args->{tls_cert} || $CERT,
SSL_error_trap => sub { $self->emit(error => $_[1]) },
SSL_honor_cipher_order => 1,
SSL_key_file => $args->{tls_key} || $KEY,
SSL_startHandshake => 0,
SSL_verify_mode => $args->{tls_verify} // ($args->{tls_ca} ? 0x03 : 0x00)
};
$tls->{SSL_ca_file} = $args->{tls_ca}
if $args->{tls_ca} && -T $args->{tls_ca};
$tls->{SSL_cipher_list} = $args->{tls_ciphers} if $args->{tls_ciphers};
$tls->{SSL_version} = $args->{tls_version} if $args->{tls_version};

my $handle = $args->{handle};
return $self->emit(error => "TLS upgrade failed: $IO::Socket::SSL::SSL_ERROR")
unless IO::Socket::SSL->start_SSL($handle, %$tls, SSL_server => 1);
$self->reactor->io($self->{handle} = $handle => sub { $self->_tls($handle) });
}

sub _tls {
my ($self, $handle) = @_;

return $self->emit(negotiated => delete $self->{handle})
if $handle->accept_SSL;

# Switch between reading and writing
my $err = $IO::Socket::SSL::SSL_ERROR;
if ($err == TLS_READ) { $self->reactor->watch($handle, 1, 0) }
elsif ($err == TLS_WRITE) { $self->reactor->watch($handle, 1, 1) }
}

1;
4 changes: 2 additions & 2 deletions t/mojo/daemon_ipv6_tls.t
Expand Up @@ -3,14 +3,14 @@ use Mojo::Base -strict;
BEGIN { $ENV{MOJO_REACTOR} = 'Mojo::Reactor::Poll' }

use Test::More;
use Mojo::IOLoop::Server;
use Mojo::IOLoop::TLS;

plan skip_all => 'set TEST_IPV6 to enable this test (developer only!)'
unless $ENV{TEST_IPV6};
plan skip_all => 'set TEST_TLS to enable this test (developer only!)'
unless $ENV{TEST_TLS};
plan skip_all => 'IO::Socket::SSL 1.94+ required for this test!'
unless Mojo::IOLoop::Server::TLS;
unless Mojo::IOLoop::TLS->has_tls;

# To regenerate all required certificates run these commands (07.01.2016)
# openssl genrsa -out domain.key 1024
Expand Down
4 changes: 2 additions & 2 deletions t/mojo/ioloop_tls.t
Expand Up @@ -3,12 +3,12 @@ use Mojo::Base -strict;
BEGIN { $ENV{MOJO_REACTOR} = 'Mojo::Reactor::Poll' }

use Test::More;
use Mojo::IOLoop::Server;
use Mojo::IOLoop::TLS;

plan skip_all => 'set TEST_TLS to enable this test (developer only!)'
unless $ENV{TEST_TLS};
plan skip_all => 'IO::Socket::SSL 1.94+ required for this test!'
unless Mojo::IOLoop::Server::TLS;
unless Mojo::IOLoop::TLS->has_tls;

# To regenerate all required certificates run these commands (12.12.2014)
# openssl genrsa -out ca.key 1024
Expand Down
4 changes: 2 additions & 2 deletions t/mojo/user_agent_online.t
Expand Up @@ -6,12 +6,12 @@ BEGIN {
}

use Test::More;
use Mojo::IOLoop::Server;
use Mojo::IOLoop::TLS;

plan skip_all => 'set TEST_ONLINE to enable this test (developer only!)'
unless $ENV{TEST_ONLINE};
plan skip_all => 'IO::Socket::SSL 1.94+ required for this test!'
unless Mojo::IOLoop::Server::TLS;
unless Mojo::IOLoop::TLS->has_tls;

use IO::Socket::INET;
use Mojo::IOLoop;
Expand Down
2 changes: 1 addition & 1 deletion t/mojo/user_agent_socks.t
Expand Up @@ -10,7 +10,7 @@ plan skip_all => 'set TEST_SOCKS to enable this test (developer only!)'
plan skip_all => 'IO::Socket::Socks 0.64+ required for this test!'
unless Mojo::IOLoop::Client::SOCKS;
plan skip_all => 'IO::Socket::SSL 1.94+ required for this test!'
unless Mojo::IOLoop::Server::TLS;
unless Mojo::IOLoop::TLS->has_tls;

use Mojo::IOLoop;
use Mojo::IOLoop::Server;
Expand Down
4 changes: 2 additions & 2 deletions t/mojo/user_agent_tls.t
Expand Up @@ -3,12 +3,12 @@ use Mojo::Base -strict;
BEGIN { $ENV{MOJO_REACTOR} = 'Mojo::Reactor::Poll' }

use Test::More;
use Mojo::IOLoop::Server;
use Mojo::IOLoop::TLS;

plan skip_all => 'set TEST_TLS to enable this test (developer only!)'
unless $ENV{TEST_TLS};
plan skip_all => 'IO::Socket::SSL 1.94+ required for this test!'
unless Mojo::IOLoop::Server::TLS;
unless Mojo::IOLoop::TLS->has_tls;

use Mojo::IOLoop;
use Mojo::Server::Daemon;
Expand Down
4 changes: 2 additions & 2 deletions t/mojo/websocket_proxy_tls.t
Expand Up @@ -3,12 +3,12 @@ use Mojo::Base -strict;
BEGIN { $ENV{MOJO_REACTOR} = 'Mojo::Reactor::Poll' }

use Test::More;
use Mojo::IOLoop::Server;
use Mojo::IOLoop::TLS;

plan skip_all => 'set TEST_TLS to enable this test (developer only!)'
unless $ENV{TEST_TLS};
plan skip_all => 'IO::Socket::SSL 1.94+ required for this test!'
unless Mojo::IOLoop::Server::TLS;
unless Mojo::IOLoop::TLS->has_tls;

use Mojo::IOLoop;
use Mojo::Server::Daemon;
Expand Down
4 changes: 2 additions & 2 deletions t/mojolicious/tls_lite_app.t
Expand Up @@ -3,12 +3,12 @@ use Mojo::Base -strict;
BEGIN { $ENV{MOJO_REACTOR} = 'Mojo::Reactor::Poll' }

use Test::More;
use Mojo::IOLoop::Server;
use Mojo::IOLoop::TLS;

plan skip_all => 'set TEST_TLS to enable this test (developer only!)'
unless $ENV{TEST_TLS};
plan skip_all => 'IO::Socket::SSL 1.94+ required for this test!'
unless Mojo::IOLoop::Server::TLS;
unless Mojo::IOLoop::TLS->has_tls;

use Mojo::IOLoop;
use Mojo::UserAgent;
Expand Down

0 comments on commit 47ef225

Please sign in to comment.