Просмотр исходного кода

Enable TLS upgrades via ranch_ssl:handshake/3

Based on the work done by @juhlig.
Loïc Hoguin 7 лет назад
Родитель
Сommit
a767abb47e

+ 11 - 0
doc/src/guide/transports.asciidoc

@@ -144,6 +144,17 @@ possible to use a file descriptor opened in raw mode:
 {ok, RawFile} = file:open(Filename, [raw, read, binary]),
 {ok, SentBytes} = Transport:sendfile(Socket, RawFile, Offset, Bytes, Opts).
 
+=== Upgrading a TCP socket to SSL
+
+A connected TCP socket can be upgraded to a SSL socket via the function
+`ranch_ssl:handshake/3`. The socket *must* be in `{active, false}` mode
+before telling the client that the server is ready to upgrade in order
+to avoid race conditions.
+
+.Performing a TLS handshake on a TCP socket
+[source,erlang]
+{ok, NewSocket} = ranch_ssl:handshake(Socket, SslOpts, 5000).
+
 === Writing a transport handler
 
 A transport handler is a module implementing the `ranch_transport` behavior.

+ 6 - 1
doc/src/manual/ranch_transport.asciidoc

@@ -55,13 +55,18 @@ Options = any():: Options for initialization.
 Timeout = timeout():: Handshake timeout.
 CSocket1 = any():: Initialized socket for this connection.
 
-Perform post-accept initialization of the connection.
+Perform any necessary handshake for this transport.
 
 This function will be called by connection processes
 before performing any socket operation. It allows
 transports that require extra initialization to perform
 their task and return a socket that is ready to use.
 
+This function may also be used to upgrade a connection
+from a transport to another depending on the capabilities
+of the transports. For example a `ranch_tcp` socket may
+be upgraded to a `ranch_ssl` one using this function.
+
 === listen(TransOpts) -> {ok, LSocket} | {error, atom()}
 
 TransOpts = any():: Transport options.

+ 4 - 1
src/ranch_ssl.erl

@@ -135,11 +135,14 @@ accept_ack(CSocket, Timeout) ->
 	{ok, _} = handshake(CSocket, [], Timeout),
 	ok.
 
--spec handshake(ssl:sslsocket(), opts(), timeout()) -> {ok, ssl:sslsocket()}.
+-spec handshake(inet:socket() | ssl:sslsocket(), opts(), timeout())
+	-> {ok, ssl:sslsocket()}.
 handshake(CSocket, Opts, Timeout) ->
 	case ssl:ssl_accept(CSocket, Opts, Timeout) of
 		ok ->
 			{ok, CSocket};
+		{ok, NewSocket} ->
+			{ok, NewSocket};
 		%% Garbage was most likely sent to the socket, don't error out.
 		{error, {tls_alert, _}} ->
 			ok = close(CSocket),

+ 22 - 1
test/acceptor_SUITE.erl

@@ -54,6 +54,7 @@ groups() ->
 		ssl_accept_ack,
 		ssl_sni_echo,
 		ssl_sni_fail,
+		ssl_upgrade_from_tcp,
 		ssl_getopts_capability,
 		ssl_getstat_capability,
 		ssl_error_eaddrinuse,
@@ -466,6 +467,26 @@ do_ssl_sni_fail() ->
 	{'EXIT', _} = begin catch ranch:get_port(Name) end,
 	ok.
 
+ssl_upgrade_from_tcp(_) ->
+	doc("Ensure a TCP socket can be upgraded to SSL"),
+	Name = name(),
+	{ok, _} = ranch:start_listener(Name,
+		ranch_tcp, #{},
+		ssl_upgrade_protocol, []),
+	Port = ranch:get_port(Name),
+	{ok, Socket} = gen_tcp:connect("localhost", Port, [binary, {active, false}, {packet, raw}]),
+	ok = gen_tcp:send(Socket, <<"ECHO Before upgrading to SSL">>),
+	{ok, <<"Before upgrading to SSL">>} = gen_tcp:recv(Socket, 23, 1000),
+	ok = gen_tcp:send(Socket, <<"UPGRADE">>),
+	{ok, <<"READY">>} = gen_tcp:recv(Socket, 5, 1000),
+	{ok, SslSocket} = ssl:connect(Socket, [{verify, verify_none}], 5000),
+	ok = ssl:send(SslSocket, <<"ECHO After upgrading to SSL">>),
+	{ok, <<"After upgrading to SSL">>} = ssl:recv(SslSocket, 22, 1000),
+	ok = ranch:stop_listener(Name),
+	{error, closed} = ssl:recv(SslSocket, 0, 1000),
+	{'EXIT', _} = begin catch ranch:get_port(Name) end,
+	ok.
+
 ssl_graceful(_) ->
 	doc("Ensure suspending and resuming of listeners does not kill active connections."),
 	Name = name(),
@@ -1041,7 +1062,7 @@ supervisor_clean_conns_sup_restart(_) ->
 	Server = erlang:whereis(ranch_server),
 	ServerMonRef = erlang:monitor(process, Server),
 	%% Exit because Name already registered and is alive.
-	{'EXIT', _}  = (catch ranch_server:set_connections_sup(Name, self())),
+	{'EXIT', _} = (catch ranch_server:set_connections_sup(Name, self())),
 	receive
 		{'DOWN', ServerMonRef, process, Server, _} ->
 			error(ranch_server_down)

+ 27 - 0
test/ssl_upgrade_protocol.erl

@@ -0,0 +1,27 @@
+-module(ssl_upgrade_protocol).
+-behaviour(ranch_protocol).
+
+-export([start_link/4]).
+-export([init/3]).
+
+start_link(Ref, _Socket, Transport, Opts) ->
+	Pid = spawn_link(?MODULE, init, [Ref, Transport, Opts]),
+	{ok, Pid}.
+
+init(Ref, Transport, _Opts = []) ->
+	{ok, Socket} = ranch:handshake(Ref),
+	loop(Socket, Transport).
+
+loop(Socket, Transport) ->
+	case Transport:recv(Socket, 0, 5000) of
+		{ok, <<"UPGRADE">>} when Transport =:= ranch_tcp ->
+			ok = Transport:send(Socket, <<"READY">>),
+			Opts = ct_helper:get_certs_from_ets(),
+			{ok, NewSocket} = ranch_ssl:handshake(Socket, [{verify, verify_none}|Opts], 1000),
+			loop(NewSocket, ranch_ssl);
+		{ok, <<"ECHO ", More/binary>>} ->
+			ok = Transport:send(Socket, More),
+			loop(Socket, Transport);
+		_ ->
+			ok = Transport:close(Socket)
+	end.