From 396ec26494008e18b573707986e32733e468346d Mon Sep 17 00:00:00 2001 From: Ingela Anderton Andin Date: Wed, 26 Mar 2014 15:50:40 +0100 Subject: ssl: Add possibility to specify ssl options when calling ssl:ssl_accept --- lib/ssl/doc/src/ssl.xml | 69 +++++++++++++++++++++++----------------- lib/ssl/src/ssl.erl | 12 ++++++- lib/ssl/src/ssl_connection.erl | 31 +++++++++++++++++- lib/ssl/src/ssl_internal.hrl | 1 - lib/ssl/test/ssl_basic_SUITE.erl | 34 +++++++++++++++++++- lib/ssl/test/ssl_test_lib.erl | 25 +++++++++------ 6 files changed, 130 insertions(+), 42 deletions(-) (limited to 'lib/ssl') diff --git a/lib/ssl/doc/src/ssl.xml b/lib/ssl/doc/src/ssl.xml index 4bc1a9a644..ffee4bd1af 100644 --- a/lib/ssl/doc/src/ssl.xml +++ b/lib/ssl/doc/src/ssl.xml @@ -768,39 +768,45 @@ fun(srp, Username :: string(), UserState :: term()) -> - ssl_accept(ListenSocket) -> - ssl_accept(ListenSocket, Timeout) -> ok | {error, Reason} - Perform server-side SSL handshake + ssl_accept(Socket) -> + ssl_accept(Socket, Timeout) -> ok | {error, Reason} + Perform server-side SSL/TLS handshake - ListenSocket = sslsocket() + Socket = sslsocket() Timeout = integer() Reason = term() -

The ssl_accept function establish the SSL connection - on the server side. It should be called directly after - transport_accept, in the spawned server-loop.

+

Performs the SSL/TLS server-side handshake Socket is a socket as returned + by ssl:transport_accept/[1,2] +

- ssl_accept(ListenSocket, SslOptions) -> - ssl_accept(ListenSocket, SslOptions, Timeout) -> {ok, Socket} | {error, Reason} - Perform server-side SSL handshake + ssl_accept(Socket, SslOptions) -> + ssl_accept(Socket, SslOptions, Timeout) -> {ok, Socket} | ok | {error, Reason} + Perform server-side SSL/TLS handshake - ListenSocket = socket() + Socket = socket() | sslsocket() SslOptions = ssloptions() Timeout = integer() Reason = term() -

Upgrades a gen_tcp, or - equivalent, socket to an ssl socket i.e. performs the - ssl server-side handshake.

+

If Socket is a socket() - upgrades a gen_tcp, or equivalent, socket to an ssl socket + i.e. performs the SSL/TLS server-side handshake and returns the ssl socket. +

+

Note that the listen socket should be in {active, false} mode before telling the client that the server is ready to upgrade - and calling this function, otherwise the upgrade may + by calling this function, otherwise the upgrade may or may not succeed depending on timing.

+ +

If Socket is an sslsocket() - provides additional SSL/TLS options to those specified in ssl:listen/2 and then performs the SSL/TLS handshake. +

@@ -842,33 +848,38 @@ fun(srp, Username :: string(), UserState :: term()) -> - transport_accept(Socket) -> - transport_accept(Socket, Timeout) -> + transport_accept(ListenSocket) -> + transport_accept(ListenSocket, Timeout) -> {ok, NewSocket} | {error, Reason} Accept an incoming connection and prepare for ssl_accept - Socket = NewSocket = sslsocket() + ListenSocket = NewSocket = sslsocket() Timeout = integer() Reason = reason()

Accepts an incoming connection request on a listen socket. - ListenSocket must be a socket returned from - listen/2. The socket returned should be passed to - ssl_accept to complete ssl handshaking and - establishing the connection.

+ ListenSocket must be a socket returned from + ssl:listen/2. + The socket returned should be passed to + ssl:ssl_accept[2,3] + to complete handshaking i.e + establishing the SSL/TLS connection.

-

The socket returned can only be used with ssl_accept, - no traffic can be sent or received before that call.

+

The socket returned can only be used with + ssl:ssl_accept[2,3] + no traffic can be sent or received before that call.

The accepted socket inherits the options set for - ListenSocket in listen/2.

+ ListenSocket in ssl:listen/2.

The default - value for Timeout is infinity. If - Timeout is specified, and no connection is accepted - within the given time, {error, timeout} is - returned.

+ value for Timeout is infinity. If + Timeout is specified, and no connection is accepted + within the given time, {error, timeout} is + returned.

diff --git a/lib/ssl/src/ssl.erl b/lib/ssl/src/ssl.erl index a88bf45293..743753bf7d 100644 --- a/lib/ssl/src/ssl.erl +++ b/lib/ssl/src/ssl.erl @@ -195,7 +195,8 @@ transport_accept(#sslsocket{pid = {ListenSocket, -spec ssl_accept(#sslsocket{} | port(), timeout()| [ssl_option() | transport_option()]) -> ok | {ok, #sslsocket{}} | {error, reason()}. --spec ssl_accept(port(), [ssl_option()| transport_option()], timeout()) -> + +-spec ssl_accept(#sslsocket{} | port(), [ssl_option()] | [ssl_option()| transport_option()], timeout()) -> {ok, #sslsocket{}} | {error, reason()}. %% %% Description: Performs accept on an ssl listen socket. e.i. performs @@ -210,6 +211,15 @@ ssl_accept(#sslsocket{} = Socket, Timeout) -> ssl_accept(ListenSocket, SslOptions) when is_port(ListenSocket) -> ssl_accept(ListenSocket, SslOptions, infinity). +ssl_accept(#sslsocket{} = Socket, [], Timeout) -> + ssl_accept(#sslsocket{} = Socket, Timeout); +ssl_accept(#sslsocket{} = Socket, SslOptions, Timeout) -> + try + {ok, #config{ssl = SSL}} = handle_options(SslOptions, server), + ssl_connection:handshake(Socket, SSL, Timeout) + catch + Error = {error, _Reason} -> Error + end; ssl_accept(Socket, SslOptions, Timeout) when is_port(Socket) -> {Transport,_,_,_} = proplists:get_value(cb_info, SslOptions, {gen_tcp, tcp, tcp_closed, tcp_error}), diff --git a/lib/ssl/src/ssl_connection.erl b/lib/ssl/src/ssl_connection.erl index ed9e4d344f..c2810a199f 100644 --- a/lib/ssl/src/ssl_connection.erl +++ b/lib/ssl/src/ssl_connection.erl @@ -36,7 +36,7 @@ -include_lib("public_key/include/public_key.hrl"). %% Setup --export([connect/8, ssl_accept/7, handshake/2, +-export([connect/8, ssl_accept/7, handshake/2, handshake/3, socket_control/4]). %% User Events @@ -100,6 +100,20 @@ handshake(#sslsocket{pid = Pid}, Timeout) -> Error -> Error end. + +%%-------------------------------------------------------------------- +-spec handshake(#sslsocket{}, #ssl_options{}, timeout()) -> ok | {error, reason()}. +%% +%% Description: Starts ssl handshake with some new options +%%-------------------------------------------------------------------- +handshake(#sslsocket{pid = Pid}, SslOptions, Timeout) -> + case sync_send_all_state_event(Pid, {start, SslOptions, Timeout}) of + connected -> + ok; + Error -> + Error + end. + %-------------------------------------------------------------------- -spec socket_control(tls_connection | dtls_connection, port(), pid(), atom()) -> {ok, #sslsocket{}} | {error, reason()}. @@ -650,6 +664,10 @@ handle_sync_event({start, Timeout}, StartFrom, StateName, State) -> {next_state, StateName, State#state{start_or_recv_from = StartFrom, timer = Timer}, get_timeout(State)}; +handle_sync_event({start, Opts, Timeout}, From, StateName, #state{ssl_options = SslOpts} = State) -> + NewOpts = new_ssl_options(Opts, SslOpts), + handle_sync_event({start, Timeout}, From, StateName, State#state{ssl_options = NewOpts}); + handle_sync_event(close, _, StateName, #state{protocol_cb = Connection} = State) -> %% Run terminate before returning %% so that the reuseaddr inet-option will work @@ -1855,3 +1873,14 @@ make_premaster_secret({MajVer, MinVer}, rsa) -> <>; make_premaster_secret(_, _) -> undefined. + +%% One day this can be maps instead, but we have to be backwards compatible for now +new_ssl_options(New, Old) -> + new_ssl_options(tuple_to_list(New), tuple_to_list(Old), []). + +new_ssl_options([], [], Acc) -> + list_to_tuple(lists:reverse(Acc)); +new_ssl_options([undefined | Rest0], [Head1| Rest1], Acc) -> + new_ssl_options(Rest0, Rest1, [Head1 | Acc]); +new_ssl_options([Head0 | Rest0], [_| Rest1], Acc) -> + new_ssl_options(Rest0, Rest1, [Head0 | Acc]). diff --git a/lib/ssl/src/ssl_internal.hrl b/lib/ssl/src/ssl_internal.hrl index cec5d8fbb1..8bf5b30a83 100644 --- a/lib/ssl/src/ssl_internal.hrl +++ b/lib/ssl/src/ssl_internal.hrl @@ -101,7 +101,6 @@ reuse_sessions :: boolean(), renegotiate_at, secure_renegotiate, - debug, %% undefined if not hibernating, or number of ms of %% inactivity after which ssl_connection will go into %% hibernation diff --git a/lib/ssl/test/ssl_basic_SUITE.erl b/lib/ssl/test/ssl_basic_SUITE.erl index 0148e1f5bc..8e3d2e4b80 100644 --- a/lib/ssl/test/ssl_basic_SUITE.erl +++ b/lib/ssl/test/ssl_basic_SUITE.erl @@ -119,7 +119,8 @@ options_tests() -> ]. api_tests() -> - [connection_info, + [new_options_in_accept, + connection_info, peername, peercert, peercert_with_client_cert, @@ -325,6 +326,37 @@ alerts(Config) when is_list(Config) -> end end, Alerts). %%-------------------------------------------------------------------- +new_options_in_accept() -> + [{doc,"Test that you can set ssl options in ssl_accept/3 and not tcp upgrade"}]. +new_options_in_accept(Config) when is_list(Config) -> + ClientOpts = ?config(client_opts, Config), + ServerOpts = ?config(server_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {ssl_opts, [{versions, [sslv3]}, + {ciphers,[{rsa,rc4_128,sha}]}]}, %% To be set in ssl_accept/3 + {mfa, {?MODULE, connection_info_result, []}}, + {options, ServerOpts}]), + + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, connection_info_result, []}}, + {options, [{versions, [sslv3]} | ClientOpts]}]), + + ct:log("Testcase ~p, Client ~p Server ~p ~n", + [self(), Client, Server]), + + ServerMsg = ClientMsg = {ok, {sslv3, {rsa, rc4_128, sha}}}, + + ssl_test_lib:check_result(Server, ServerMsg, Client, ClientMsg), + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). +%%-------------------------------------------------------------------- + connection_info() -> [{doc,"Test the API function ssl:connection_info/1"}]. connection_info(Config) when is_list(Config) -> diff --git a/lib/ssl/test/ssl_test_lib.erl b/lib/ssl/test/ssl_test_lib.erl index 7d8ece8d19..59f10d53a6 100644 --- a/lib/ssl/test/ssl_test_lib.erl +++ b/lib/ssl/test/ssl_test_lib.erl @@ -106,7 +106,8 @@ connect(#sslsocket{} = ListenSocket, Opts) -> Node = proplists:get_value(node, Opts), ReconnectTimes = proplists:get_value(reconnect_times, Opts, 0), Timeout = proplists:get_value(timeout, Opts, infinity), - AcceptSocket = connect(ListenSocket, Node, 1 + ReconnectTimes, dummy, Timeout), + SslOpts = proplists:get_value(ssl_opts, Opts, []), + AcceptSocket = connect(ListenSocket, Node, 1 + ReconnectTimes, dummy, Timeout, SslOpts), case ReconnectTimes of 0 -> AcceptSocket; @@ -121,24 +122,30 @@ connect(ListenSocket, Opts) -> [ListenSocket]), AcceptSocket. -connect(_, _, 0, AcceptSocket, _) -> +connect(_, _, 0, AcceptSocket, _, _) -> AcceptSocket; -connect(ListenSocket, Node, N, _, Timeout) -> - ct:log("~p:~p~nssl:transport_accept(~p)~n", [?MODULE,?LINE, ListenSocket]), + +connect(ListenSocket, Node, N, _, Timeout, []) -> + ct:log("ssl:transport_accept(~p)~n", [ListenSocket]), {ok, AcceptSocket} = rpc:call(Node, ssl, transport_accept, [ListenSocket]), ct:log("~p:~p~nssl:ssl_accept(~p, ~p)~n", [?MODULE,?LINE, AcceptSocket, Timeout]), case rpc:call(Node, ssl, ssl_accept, [AcceptSocket, Timeout]) of ok -> -ct:log("~p:~p~nok from ssl:ssl_accept@~p",[?MODULE,?LINE, Node]), - connect(ListenSocket, Node, N-1, AcceptSocket, Timeout); + connect(ListenSocket, Node, N-1, AcceptSocket, Timeout, []); Result -> -ct:log("~p:~p~nssl:ssl_accept@~p ret ~p",[?MODULE,?LINE, Node,Result]), + ct:log("~p:~p~nssl:ssl_accept@~p ret ~p",[?MODULE,?LINE, Node,Result]), Result - end. + end; +connect(ListenSocket, Node, _, _, Timeout, Opts) -> + ct:log("ssl:transport_accept(~p)~n", [ListenSocket]), + {ok, AcceptSocket} = rpc:call(Node, ssl, transport_accept, + [ListenSocket]), + ct:log("ssl:ssl_accept(~p,~p, ~p)~n", [AcceptSocket, Opts, Timeout]), + rpc:call(Node, ssl, ssl_accept, [AcceptSocket, Opts, Timeout]), + AcceptSocket. - remove_close_msg(0) -> ok; remove_close_msg(ReconnectTimes) -> -- cgit v1.2.3