Skip to content

Commit

Permalink
add SNI support to all built-in web servers
Browse files Browse the repository at this point in the history
  • Loading branch information
kraih committed Jan 7, 2016
1 parent 2706d10 commit a2469e6
Show file tree
Hide file tree
Showing 11 changed files with 164 additions and 22 deletions.
1 change: 1 addition & 0 deletions Changes
@@ -1,5 +1,6 @@

6.40 2016-01-07
- Added SNI support to all built-in web servers. (bpmedley, sri)

6.39 2016-01-03
- Updated links to Mojolicious website.
Expand Down
8 changes: 4 additions & 4 deletions examples/connect-proxy.pl
Expand Up @@ -14,7 +14,7 @@

# Write chunk from client to server
my $server = $buffer{$client}{connection};
return Mojo::IOLoop->stream($server)->write($chunk) if $server;
return Mojo::IOLoop->stream($server)->write($chunk) if length $server;

# Read connect request from client
my $buffer = $buffer{$client}{client} .= $chunk;
Expand Down Expand Up @@ -58,10 +58,10 @@
}
);
}
}

# Invalid request from client
else { Mojo::IOLoop->remove($client) }
# Invalid request from client
else { Mojo::IOLoop->remove($client) }
}
}
);

Expand Down
14 changes: 11 additions & 3 deletions lib/Mojo/Server/Daemon.pm
Expand Up @@ -157,7 +157,11 @@ sub _listen {
reuse => $query->param('reuse')
};
if (my $port = $url->port) { $options->{port} = $port }
$options->{"tls_$_"} = $query->param($_) for qw(ca cert ciphers key version);
$options->{"tls_$_"} = $query->param($_) for qw(ca ciphers version);
/^(.*)_(cert|key)$/ and $options->{"tls_$2"}{$1} = $query->param($_)
for @{$query->names};
if (my $cert = $query->param('cert')) { $options->{'tls_cert'}{''} = $cert }
if (my $key = $query->param('key')) { $options->{'tls_key'}{''} = $key }
my $verify = $query->param('verify');
$options->{tls_verify} = hex $verify if defined $verify;
delete $options->{address} if $options->{address} eq '*';
Expand Down Expand Up @@ -267,8 +271,8 @@ Mojo::Server::Daemon - Non-blocking I/O HTTP and WebSocket server
=head1 DESCRIPTION
L<Mojo::Server::Daemon> is a full featured, highly portable non-blocking I/O
HTTP and WebSocket server, with IPv6, TLS, Comet (long polling), keep-alive and
multiple event loop support.
HTTP and WebSocket server, with IPv6, TLS, SNI, Comet (long polling), keep-alive
and multiple event loop support.
For better scalability (epoll, kqueue) and to provide non-blocking name
resolution, SOCKS5 as well as TLS support, the optional modules L<EV> (4.0+),
Expand Down Expand Up @@ -362,6 +366,10 @@ C<http://0.0.0.0:3000>).
# Use a custom certificate and key
$daemon->listen(['https://*:3000?cert=/x/server.crt&key=/y/server.key']);
# Domain specific certificates and keys (SNI)
$daemon->listen(
['https://*:3000?example.com_cert=/x/my.crt&example.com_key=/y/my.key']);
# Or even a custom certificate authority
$daemon->listen(
['https://*:3000?cert=/x/server.crt&key=/y/server.key&ca=/z/ca.crt']);
Expand Down
8 changes: 4 additions & 4 deletions lib/Mojo/Server/Hypnotoad.pm
Expand Up @@ -152,10 +152,10 @@ Mojo::Server::Hypnotoad - ALL GLORY TO THE HYPNOTOAD!
L<Mojo::Server::Hypnotoad> is a full featured, UNIX optimized, preforking
non-blocking I/O HTTP and WebSocket server, built around the very well tested
and reliable L<Mojo::Server::Prefork>, with IPv6, TLS, Comet (long polling),
keep-alive, multiple event loop and hot deployment support that just works.
Note that the server uses signals for process management, so you should avoid
modifying signal handlers in your applications.
and reliable L<Mojo::Server::Prefork>, with IPv6, TLS, SNI, Comet (long
polling), keep-alive, multiple event loop and hot deployment support that just
works. Note that the server uses signals for process management, so you should
avoid modifying signal handlers in your applications.
To start applications with it you can use the L<hypnotoad> script, which
listens on port C<8080>, automatically daemonizes the server process and
Expand Down
4 changes: 2 additions & 2 deletions lib/Mojo/Server/Morbo.pm
Expand Up @@ -110,8 +110,8 @@ Mojo::Server::Morbo - DOOOOOOOOOOOOOOOOOOM!
L<Mojo::Server::Morbo> is a full featured, self-restart capable non-blocking
I/O HTTP and WebSocket server, built around the very well tested and reliable
L<Mojo::Server::Daemon>, with IPv6, TLS, Comet (long polling), keep-alive and
multiple event loop support. Note that the server uses signals for process
L<Mojo::Server::Daemon>, with IPv6, TLS, SNI, Comet (long polling), keep-alive
and multiple event loop support. Note that the server uses signals for process
management, so you should avoid modifying signal handlers in your applications.
To start applications with it you can use the L<morbo> script.
Expand Down
2 changes: 1 addition & 1 deletion lib/Mojo/Server/Prefork.pm
Expand Up @@ -232,7 +232,7 @@ Mojo::Server::Prefork - Preforking non-blocking I/O HTTP and WebSocket server
L<Mojo::Server::Prefork> is a full featured, UNIX optimized, preforking
non-blocking I/O HTTP and WebSocket server, built around the very well tested
and reliable L<Mojo::Server::Daemon>, with IPv6, TLS, Comet (long polling),
and reliable L<Mojo::Server::Daemon>, with IPv6, TLS, SNI, Comet (long polling),
keep-alive and multiple event loop support. Note that the server uses signals
for process management, so you should avoid modifying signal handlers in your
applications.
Expand Down
12 changes: 12 additions & 0 deletions t/mojo/certs/domain.crt
@@ -0,0 +1,12 @@
-----BEGIN CERTIFICATE-----
MIIBtDCCAR0CCQCEuM5/riCs5zANBgkqhkiG9w0BAQUFADAaMQswCQYDVQQGEwJV
UzELMAkGA1UEAxMCY2EwHhcNMTYwMTA3MTY0MjE0WhcNMzYwMTAyMTY0MjE0WjAj
MQswCQYDVQQGEwJVUzEUMBIGA1UEAxMLZXhhbXBsZS5jb20wgZ8wDQYJKoZIhvcN
AQEBBQADgY0AMIGJAoGBAMQc4F687m9y9Wab9YIPfYDeIN9o+wut8P0uVvCgyGny
TtbQNmT6iaeJBm8xemElV5G67CF/aR6mHoJOXpxD3Xym9qro4qbfubHeDJZS93OB
JS/WoMYyfwES/E0GODONM/QDrwUanB8Arx88hxU9+8ARuVY+ML7ZgMllO6u9lA0T
AgMBAAEwDQYJKoZIhvcNAQEFBQADgYEANS5lefxEri0D4cfFN3Q5EhFKqzVQkwuy
RtvItyH1GdLSSjHwFcpnUj4y2luR+VmLJW2QEFw/mnKzCUxX9bp8bZITYcchIJNH
fJ+8MaoyxhlVrv0E5d6jiwOGZoBMYOTf7UAkfFkkLPGbWFVBur5mXaQ68SBgo6Cf
Pl2WcT5ghyM=
-----END CERTIFICATE-----
15 changes: 15 additions & 0 deletions t/mojo/certs/domain.key
@@ -0,0 +1,15 @@
-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQDEHOBevO5vcvVmm/WCD32A3iDfaPsLrfD9LlbwoMhp8k7W0DZk
+omniQZvMXphJVeRuuwhf2keph6CTl6cQ918pvaq6OKm37mx3gyWUvdzgSUv1qDG
Mn8BEvxNBjgzjTP0A68FGpwfAK8fPIcVPfvAEblWPjC+2YDJZTurvZQNEwIDAQAB
AoGBALujaWYb3QLTektX83oiuhE/9zHrLzBImCiWWubW34rHJXnRNAo/0M90gqyH
KbGjWfr8XwvZ7Uk+5jgFJe7b3CDGTUh7yvuHuypwJg/wmqnBlfj0S4c8lC8LB4+6
199D+lkfZyJKbBoIdHjCcB1RpZkb7DN3d9ylwRWA0OEWXwl5AkEA+GuhfS3C/627
7ax3ojQsyqmJKqzloxU34YxWw+R14dZOClF/tPwOsgbzy6Cvihq5azbWHCcZ06MI
DAfs0s+CVQJBAMoYrgG4XPVXxR0fdpclY1S1r+4RixgswawNTXdPCpcnhlfRy4ej
03UwyxeA7M4pxmVteTbbjjopVecamOgtyccCQChEUf3XcBc/kwm4ff/V0zjaeDhp
pCNmKhOuStYf7xe3RBkaEshEXyFuTRBBsJKDOHDvh48yq1YJxCEnG7UkG60CQAGn
7B0VfqV//5x6eoVIiCTUjEl+GU6sZzXasgzNN///Eem8TVeiLwRhzvg1VTtnOjnw
iLK7X9H4Lr0DCce1QFcCQCZseRu73kcYpIAJjPlgYCco6H3lObT4fekB3XRA2MRU
HP/9DewejMCAHgttD91mJ5BPY5xB8pfsJMUz2pC0FMg=
-----END RSA PRIVATE KEY-----
106 changes: 106 additions & 0 deletions t/mojo/daemon_ipv6_tls.t
Expand Up @@ -12,6 +12,11 @@ plan skip_all => 'set TEST_TLS to enable this test (developer only!)'
plan skip_all => 'IO::Socket::SSL 1.94+ required for this test!'
unless Mojo::IOLoop::Server::TLS;

# To regenerate all required certificates run these commands (07.01.2016)
# openssl genrsa -out domain.key 1024
# openssl req -new -key domain.key -out domain.csr -subj "/C=US/CN=example.com"
# openssl x509 -req -days 7300 -in domain.csr -out domain.crt -CA ca.crt \
# -CAkey ca.key -CAcreateserial
use Mojo::IOLoop;
use Mojo::Server::Daemon;
use Mojo::UserAgent;
Expand All @@ -22,6 +27,77 @@ app->log->level('fatal');

get '/' => {text => 'works!'};

# CONNECT proxy server for testing
my (%buffer, $forward);
my $id = Mojo::IOLoop->server(
{address => '[::1]'} => sub {
my ($loop, $stream, $client) = @_;

# Connection to client
$stream->on(
read => sub {
my ($stream, $chunk) = @_;

# Write chunk from client to server
my $server = $buffer{$client}{connection};
return Mojo::IOLoop->stream($server)->write($chunk) if length $server;

# Read connect request from client
my $buffer = $buffer{$client}{client} .= $chunk;
if ($buffer =~ /\x0d?\x0a\x0d?\x0a$/) {
$buffer{$client}{client} = '';
if ($buffer =~ /CONNECT \S+:\d+/) {

# Connection to server
$buffer{$client}{connection} = Mojo::IOLoop->client(
{address => '[::1]', port => $forward} => sub {
my ($loop, $err, $stream) = @_;

# Connection to server failed
if ($err) {
Mojo::IOLoop->remove($client);
return delete $buffer{$client};
}

# Start forwarding data in both directions
Mojo::IOLoop->stream($client)
->write("HTTP/1.1 200 OK\x0d\x0a"
. "Connection: keep-alive\x0d\x0a\x0d\x0a");
$stream->on(
read => sub {
my ($stream, $chunk) = @_;
Mojo::IOLoop->stream($client)->write($chunk);
}
);

# Server closed connection
$stream->on(
close => sub {
Mojo::IOLoop->remove($client);
delete $buffer{$client};
}
);
}
);
}

# Invalid request from client
else { Mojo::IOLoop->remove($client) }
}
}
);

# Client closed connection
$stream->on(
close => sub {
my $buffer = delete $buffer{$client};
Mojo::IOLoop->remove($buffer->{connection}) if $buffer->{connection};
}
);
}
);
my $proxy = Mojo::IOLoop->acceptor($id)->port;

# IPv6 and TLS
my $daemon = Mojo::Server::Daemon->new(
app => app,
Expand All @@ -35,4 +111,34 @@ my $tx = $ua->get("https://[::1]:$port/");
is $tx->res->code, 200, 'right status';
is $tx->res->body, 'works!', 'right content';

# IPv6, TLS, SNI and a proxy
SKIP: {
skip 'SNI support required!', 1
unless IO::Socket::SSL->can_client_sni && IO::Socket::SSL->can_server_sni;
$daemon = Mojo::Server::Daemon->new(app => app, silent => 1);
my $listen
= 'https://[::1]'
. '?127.0.0.1_cert=t/mojo/certs/server.crt'
. '&127.0.0.1_key=t/mojo/certs/server.key'
. '&example.com_cert=t/mojo/certs/domain.crt'
. '&example.com_key=t/mojo/certs/domain.key';
$daemon->listen([$listen])->start;
$forward = Mojo::IOLoop->acceptor($daemon->acceptors->[0])->port;
$ua = Mojo::UserAgent->new(
ioloop => Mojo::IOLoop->singleton,
ca => 't/mojo/certs/ca.crt'
);
$ua->proxy->https("http://[::1]:$proxy");
$tx = $ua->get("https://example.com/");
is $tx->res->code, 200, 'right status';
is $tx->res->body, 'works!', 'right content';
ok !$tx->error, 'no error';
$tx = $ua->get("https://127.0.0.1/");
is $tx->res->code, 200, 'right status';
is $tx->res->body, 'works!', 'right content';
ok !$tx->error, 'no error';
$tx = $ua->get("https://has.no.cert/");
like $tx->error->{message}, qr/hostname verification failed/, 'right error';
}

done_testing();
8 changes: 4 additions & 4 deletions t/mojo/websocket_proxy.t
Expand Up @@ -52,7 +52,7 @@ my $id = Mojo::IOLoop->server(

# Write chunk from client to server
my $server = $buffer{$client}{connection};
return Mojo::IOLoop->stream($server)->write($chunk) if $server;
return Mojo::IOLoop->stream($server)->write($chunk) if length $server;

# Read connect request from client
my $buffer = $buffer{$client}{client} .= $chunk;
Expand Down Expand Up @@ -94,10 +94,10 @@ my $id = Mojo::IOLoop->server(
}
);
}
}

# Invalid request from client
else { Mojo::IOLoop->remove($client) }
# Invalid request from client
else { Mojo::IOLoop->remove($client) }
}
}
);

Expand Down
8 changes: 4 additions & 4 deletions t/mojo/websocket_proxy_tls.t
Expand Up @@ -71,7 +71,7 @@ my $id = Mojo::IOLoop->server(

# Write chunk from client to server
my $server = $buffer{$client}{connection};
return Mojo::IOLoop->stream($server)->write($chunk) if $server;
return Mojo::IOLoop->stream($server)->write($chunk) if length $server;

# Read connect request from client
my $buffer = $buffer{$client}{client} .= $chunk;
Expand Down Expand Up @@ -113,10 +113,10 @@ my $id = Mojo::IOLoop->server(
}
);
}
}

# Invalid request from client
else { Mojo::IOLoop->remove($client) }
# Invalid request from client
else { Mojo::IOLoop->remove($client) }
}
}
);

Expand Down

0 comments on commit a2469e6

Please sign in to comment.