aboutsummaryrefslogtreecommitdiffstats
path: root/lib/ssl/src
diff options
context:
space:
mode:
Diffstat (limited to 'lib/ssl/src')
-rw-r--r--lib/ssl/src/dtls_connection.erl71
-rw-r--r--lib/ssl/src/inet_tls_dist.erl601
-rw-r--r--lib/ssl/src/ssl.appup.src2
-rw-r--r--lib/ssl/src/ssl.erl97
-rw-r--r--lib/ssl/src/ssl_cipher.erl331
-rw-r--r--lib/ssl/src/ssl_config.erl8
-rw-r--r--lib/ssl/src/ssl_connection.erl145
-rw-r--r--lib/ssl/src/ssl_connection.hrl15
-rw-r--r--lib/ssl/src/ssl_handshake.erl137
-rw-r--r--lib/ssl/src/ssl_internal.hrl3
-rw-r--r--lib/ssl/src/tls_connection.erl26
11 files changed, 985 insertions, 451 deletions
diff --git a/lib/ssl/src/dtls_connection.erl b/lib/ssl/src/dtls_connection.erl
index 7bc7fc3fc6..0fe568759d 100644
--- a/lib/ssl/src/dtls_connection.erl
+++ b/lib/ssl/src/dtls_connection.erl
@@ -55,7 +55,7 @@
%% gen_statem state functions
-export([init/3, error/3, downgrade/3, %% Initiation and take down states
- hello/3, certify/3, cipher/3, abbreviated/3, %% Handshake states
+ hello/3, user_hello/3, certify/3, cipher/3, abbreviated/3, %% Handshake states
connection/3]).
%% gen_statem callbacks
-export([callback_mode/0, terminate/3, code_change/4, format_status/2]).
@@ -73,8 +73,7 @@ start_fsm(Role, Host, Port, Socket, {#ssl_options{erl_dist = false},_, Tracker}
{ok, Pid} = dtls_connection_sup:start_child([Role, Host, Port, Socket,
Opts, User, CbInfo]),
{ok, SslSocket} = ssl_connection:socket_control(?MODULE, Socket, Pid, CbModule, Tracker),
- ok = ssl_connection:handshake(SslSocket, Timeout),
- {ok, SslSocket}
+ ssl_connection:handshake(SslSocket, Timeout)
catch
error:{badmatch, {error, _} = Error} ->
Error
@@ -492,10 +491,11 @@ hello(enter, _, #state{role = client} = State0) ->
{State, Actions} = handle_flight_timer(State0),
{keep_state, State, Actions};
hello(internal, #client_hello{cookie = <<>>,
- client_version = Version} = Hello, #state{role = server,
- transport_cb = Transport,
- socket = Socket,
- protocol_specific = #{current_cookie_secret := Secret}} = State0) ->
+ client_version = Version} = Hello,
+ #state{role = server,
+ transport_cb = Transport,
+ socket = Socket,
+ protocol_specific = #{current_cookie_secret := Secret}} = State0) ->
{ok, {IP, Port}} = dtls_socket:peername(Transport, Socket),
Cookie = dtls_handshake:cookie(Secret, IP, Port, Hello),
%% FROM RFC 6347 regarding HelloVerifyRequest message:
@@ -509,24 +509,6 @@ hello(internal, #client_hello{cookie = <<>>,
{State2, Actions} = send_handshake(VerifyRequest, State1),
{Record, State} = next_record(State2),
next_event(?FUNCTION_NAME, Record, State#state{tls_handshake_history = ssl_handshake:init_handshake_history()}, Actions);
-hello(internal, #client_hello{cookie = Cookie} = Hello, #state{role = server,
- transport_cb = Transport,
- socket = Socket,
- protocol_specific = #{current_cookie_secret := Secret,
- previous_cookie_secret := PSecret}} = State0) ->
- {ok, {IP, Port}} = dtls_socket:peername(Transport, Socket),
- case dtls_handshake:cookie(Secret, IP, Port, Hello) of
- Cookie ->
- handle_client_hello(Hello, State0);
- _ ->
- case dtls_handshake:cookie(PSecret, IP, Port, Hello) of
- Cookie ->
- handle_client_hello(Hello, State0);
- _ ->
- %% Handle bad cookie as new cookie request RFC 6347 4.1.2
- hello(internal, Hello#client_hello{cookie = <<>>}, State0)
- end
- end;
hello(internal, #hello_verify_request{cookie = Cookie}, #state{role = client,
host = Host, port = Port,
ssl_options = SslOpts,
@@ -551,6 +533,34 @@ hello(internal, #hello_verify_request{cookie = Cookie}, #state{role = client,
Hello#client_hello.session_id}},
{Record, State} = next_record(State3),
next_event(?FUNCTION_NAME, Record, State, Actions);
+hello(internal, #client_hello{extensions = Extensions} = Hello, #state{ssl_options = #ssl_options{handshake = hello},
+ start_or_recv_from = From} = State) ->
+ {next_state, user_hello, State#state{start_or_recv_from = undefined,
+ hello = Hello},
+ [{reply, From, {ok, ssl_connection:map_extensions(Extensions)}}]};
+hello(internal, #server_hello{extensions = Extensions} = Hello, #state{ssl_options = #ssl_options{handshake = hello},
+ start_or_recv_from = From} = State) ->
+ {next_state, user_hello, State#state{start_or_recv_from = undefined,
+ hello = Hello},
+ [{reply, From, {ok, ssl_connection:map_extensions(Extensions)}}]};
+hello(internal, #client_hello{cookie = Cookie} = Hello, #state{role = server,
+ transport_cb = Transport,
+ socket = Socket,
+ protocol_specific = #{current_cookie_secret := Secret,
+ previous_cookie_secret := PSecret}} = State0) ->
+ {ok, {IP, Port}} = dtls_socket:peername(Transport, Socket),
+ case dtls_handshake:cookie(Secret, IP, Port, Hello) of
+ Cookie ->
+ handle_client_hello(Hello, State0);
+ _ ->
+ case dtls_handshake:cookie(PSecret, IP, Port, Hello) of
+ Cookie ->
+ handle_client_hello(Hello, State0);
+ _ ->
+ %% Handle bad cookie as new cookie request RFC 6347 4.1.2
+ hello(internal, Hello#client_hello{cookie = <<>>}, State0)
+ end
+ end;
hello(internal, #server_hello{} = Hello,
#state{connection_states = ConnectionStates0,
negotiated_version = ReqVersion,
@@ -570,6 +580,12 @@ hello(internal, {handshake, {#client_hello{cookie = <<>>} = Handshake, _}}, Stat
hello(internal, {handshake, {#hello_verify_request{} = Handshake, _}}, State) ->
%% hello_verify should not be in handshake history
{next_state, ?FUNCTION_NAME, State, [{next_event, internal, Handshake}]};
+hello(internal, #change_cipher_spec{type = <<1>>}, State0) ->
+ {State1, Actions0} = send_handshake_flight(State0, retransmit_epoch(?FUNCTION_NAME, State0)),
+ {Record, State2} = next_record(State1),
+ {next_state, ?FUNCTION_NAME, State, Actions} = next_event(?FUNCTION_NAME, Record, State2, Actions0),
+ %% This will reset the retransmission timer by repeating the enter state event
+ {repeat_state, State, Actions};
hello(info, Event, State) ->
gen_info(Event, ?FUNCTION_NAME, State);
hello(state_timeout, Event, State) ->
@@ -577,6 +593,11 @@ hello(state_timeout, Event, State) ->
hello(Type, Event, State) ->
gen_handshake(?FUNCTION_NAME, Type, Event, State).
+user_hello(enter, _, State) ->
+ {keep_state, State};
+user_hello(Type, Event, State) ->
+ gen_handshake(?FUNCTION_NAME, Type, Event, State).
+
%%--------------------------------------------------------------------
-spec abbreviated(gen_statem:event_type(), term(), #state{}) ->
gen_statem:state_function_result().
diff --git a/lib/ssl/src/inet_tls_dist.erl b/lib/ssl/src/inet_tls_dist.erl
index 8e605bec65..3e9828a2fe 100644
--- a/lib/ssl/src/inet_tls_dist.erl
+++ b/lib/ssl/src/inet_tls_dist.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2011-2017. All Rights Reserved.
+%% Copyright Ericsson AB 2011-2018. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -29,13 +29,16 @@
-export([gen_listen/2, gen_accept/2, gen_accept_connection/6,
gen_setup/6, gen_close/2, gen_select/2]).
--export([split_node/1, nodelay/0]).
+-export([nodelay/0]).
+
+-export([verify_client/3, verify_server/3, cert_nodes/1]).
-export([dbg/0]). % Debug
-include_lib("kernel/include/net_address.hrl").
-include_lib("kernel/include/dist.hrl").
-include_lib("kernel/include/dist_util.hrl").
+-include_lib("public_key/include/public_key.hrl").
-include("ssl_api.hrl").
@@ -49,25 +52,20 @@ select(Node) ->
gen_select(inet_tcp, Node).
gen_select(Driver, Node) ->
- case split_node(Node) of
- false ->
- false;
- Host ->
+ case dist_util:split_node(Node) of
+ {node,_,Host} ->
case Driver:getaddr(Host) of
{ok, _} -> true;
_ -> false
- end
+ end;
+ _ ->
+ false
end.
%% -------------------------------------------------------------------------
is_node_name(Node) ->
- case split_node(Node) of
- false ->
- false;
- _Host ->
- true
- end.
+ dist_util:is_node_name(Node).
%% -------------------------------------------------------------------------
@@ -142,13 +140,13 @@ f_getll(DistCtrl) ->
f_address(SslSocket, Node) ->
case ssl:peername(SslSocket) of
{ok, Address} ->
- case split_node(Node) of
- false ->
- {error, no_node};
- Host ->
+ case dist_util:split_node(Node) of
+ {node,_,Host} ->
#net_address{
address=Address, host=Host,
- protocol=tls, family=inet}
+ protocol=tls, family=inet};
+ _ ->
+ {error, no_node}
end
end.
@@ -224,42 +222,158 @@ gen_accept(Driver, Listen) ->
accept_loop(Driver, Listen, Kernel) ->
case Driver:accept(Listen) of
{ok, Socket} ->
- Opts = get_ssl_options(server),
- wait_for_code_server(),
- case ssl:ssl_accept(
- Socket, [{active, false}, {packet, 4}] ++ Opts,
- net_kernel:connecttime()) of
- {ok, #sslsocket{pid = DistCtrl} = SslSocket} ->
- monitor_pid(DistCtrl),
- trace(
- Kernel !
- {accept, self(), DistCtrl,
- Driver:family(), tls}),
- receive
- {Kernel, controller, Pid} ->
- ok = ssl:controlling_process(SslSocket, Pid),
- trace(
- Pid ! {self(), controller});
- {Kernel, unsupported_protocol} ->
- exit(trace(unsupported_protocol))
- end,
- accept_loop(Driver, Listen, Kernel);
- {error, {options, _}} = Error ->
- %% Bad options: that's probably our fault.
- %% Let's log that.
+ case check_ip(Driver, Socket) of
+ true ->
+ accept_loop(Driver, Listen, Kernel, Socket);
+ {false,IP} ->
error_logger:error_msg(
- "Cannot accept TLS distribution connection: ~s~n",
- [ssl:format_error(Error)]),
- _ = trace(Error),
- gen_tcp:close(Socket);
- Other ->
- _ = trace(Other),
- gen_tcp:close(Socket)
+ "** Connection attempt from "
+ "disallowed IP ~w ** ~n", [IP]),
+ ?shutdown2(no_node, trace({disallowed, IP}))
end;
Error ->
exit(trace(Error))
- end,
- accept_loop(Driver, Listen, Kernel).
+ end.
+
+accept_loop(Driver, Listen, Kernel, Socket) ->
+ {Opts,CertNodesFun} =
+ setup_verify_client(
+ Driver, Socket, get_ssl_options(server)),
+ wait_for_code_server(),
+ case
+ ssl:ssl_accept(
+ Socket,
+ trace([{active, false},{packet, 4}|Opts]),
+ net_kernel:connecttime())
+ of
+ {ok, #sslsocket{pid = DistCtrl} = SslSocket} ->
+ trace(
+ Kernel !
+ {accept, self(), DistCtrl,
+ Driver:family(), tls}),
+ receive
+ {Kernel, controller, Pid} ->
+ ok = ssl:controlling_process(SslSocket, Pid),
+ trace(
+ Pid ! {self(), controller, CertNodesFun});
+ {Kernel, unsupported_protocol} ->
+ exit(trace(unsupported_protocol))
+ end,
+ accept_loop(Driver, Listen, Kernel);
+ {error, {options, _}} = Error ->
+ %% Bad options: that's probably our fault.
+ %% Let's log that.
+ error_logger:error_msg(
+ "Cannot accept TLS distribution connection: ~s~n",
+ [ssl:format_error(Error)]),
+ gen_tcp:close(Socket),
+ exit(trace(Error));
+ Other ->
+ gen_tcp:close(Socket),
+ exit(trace(Other))
+ end.
+
+
+%% {verify_fun,{fun ?MODULE:verify_client/3,_}} is used
+%% as a configuration marker that verify_client/3 shall be used.
+%%
+%% Replace the State in the first occurence of
+%% {verify_fun,{fun ?MODULE:verify_client/3,State}} and remove the rest.
+%% The inserted state is not accesible from a configuration file
+%% since it is dynamic and connection dependent.
+%%
+setup_verify_client(Driver, Socket, Opts) ->
+ setup_verify_client(Driver, Socket, Opts, undefined, []).
+%%
+setup_verify_client(_Driver, _Socket, [], CertNodesFun, OptsR) ->
+ {lists:reverse(OptsR),CertNodesFun};
+setup_verify_client(Driver, Socket, [Opt|Opts], CertNodesFun, OptsR) ->
+ case Opt of
+ {verify_fun,{Fun,NewCertNodesFun}} ->
+ case Fun =:= fun ?MODULE:verify_client/3 of
+ true when is_function(NewCertNodesFun, 1) ->
+ if
+ CertNodesFun =:= undefined ->
+ case inet:peername(Socket) of
+ {ok,{PeerIP,_Port}} ->
+ setup_verify_client(
+ Driver, Socket, Opts, NewCertNodesFun,
+ [{verify_fun,
+ {Fun,
+ {NewCertNodesFun,Driver,PeerIP}}}
+ |OptsR]);
+ {error,Reason} ->
+ exit(trace({no_peername,Reason}))
+ end;
+ true ->
+ setup_verify_client(
+ Driver, Socket, Opts, CertNodesFun, OptsR)
+ end;
+ true ->
+ exit(
+ trace(
+ {verify_client_bad_argument,CertNodesFun}));
+ false ->
+ setup_verify_client(
+ Driver, Socket, Opts, CertNodesFun, [Opt|OptsR])
+ end;
+ _ ->
+ setup_verify_client(
+ Driver, Socket, Opts, CertNodesFun, [Opt|OptsR])
+ end.
+
+%% Same as verify_peer but check cert host names for
+%% peer IP address
+verify_client(_, {bad_cert,_} = Reason, _) ->
+ {fail,Reason};
+verify_client(_, {extension,_}, S) ->
+ {unknown,S};
+verify_client(_, valid, S) ->
+ {valid,S};
+verify_client(PeerCert, valid_peer, {CertNodesFun,Driver,PeerIP} = S) ->
+ %%
+ %% Parse out all node names from the peer's certificate
+ %%
+ case CertNodesFun(PeerCert) of
+ undefined ->
+ %% Certificate allows all nodes
+ {valid,S};
+ [] ->
+ %% Certificate allows no nodes
+ {fail,cert_missing_node_name};
+ CertNodes ->
+ %% Check if the IP address of one of the nodes
+ %% in the peer certificate has the peer's IP address
+ case filter_nodes_by_ip(CertNodes, Driver, PeerIP) of
+ [] ->
+ {fail,cert_no_host_with_peer_ip};
+ _ ->
+ {valid,S}
+ end
+ end.
+
+%% Filter out the nodes that has got the given IP address
+%%
+filter_nodes_by_ip(Nodes, Driver, IP) ->
+ [Node ||
+ Node <- Nodes,
+ case dist_util:split_node(Node) of
+ {node,_,Host} ->
+ filter_host_by_ip(Host, Driver, IP);
+ {host,Host} ->
+ filter_host_by_ip(Host, Driver, IP);
+ {name,_Name} ->
+ true
+ end].
+
+filter_host_by_ip(Host, Driver, IP) ->
+ case Driver:getaddr(Host) of
+ {ok,IP} ->
+ true;
+ _ ->
+ false
+ end.
+
wait_for_code_server() ->
%% This is an ugly hack. Upgrading a socket to TLS requires the
@@ -311,29 +425,81 @@ gen_accept_connection(
do_accept(Driver, Kernel, AcceptPid, DistCtrl, MyNode, Allowed, SetupTime) ->
SslSocket = ssl_connection:get_sslsocket(DistCtrl),
receive
- {AcceptPid, controller} ->
+ {AcceptPid, controller, CertNodesFun} ->
Timer = dist_util:start_timer(SetupTime),
- case check_ip(Driver, SslSocket) of
- true ->
- HSData0 = hs_data_common(SslSocket),
- HSData =
- HSData0#hs_data{
- kernel_pid = Kernel,
- this_node = MyNode,
- socket = DistCtrl,
- timer = Timer,
- this_flags = 0,
- allowed = Allowed},
- link(DistCtrl),
- dist_util:handshake_other_started(trace(HSData));
- {false,IP} ->
- error_logger:error_msg(
- "** Connection attempt from "
- "disallowed IP ~w ** ~n", [IP]),
- ?shutdown2(no_node, trace({disallowed, IP}))
- end
+ NewAllowed =
+ allowed_nodes(
+ Driver, CertNodesFun, SslSocket, Allowed),
+ HSData0 = hs_data_common(SslSocket),
+ HSData =
+ HSData0#hs_data{
+ kernel_pid = Kernel,
+ this_node = MyNode,
+ socket = DistCtrl,
+ timer = Timer,
+ this_flags = 0,
+ allowed = NewAllowed},
+ link(DistCtrl),
+ dist_util:handshake_other_started(trace(HSData))
end.
+%% Return a list of allowed nodes according to
+%% the given Allowed list that matches the peer certificate
+%%
+allowed_nodes(_Driver, undefined, _SslSocket, Allowed) ->
+ Allowed;
+allowed_nodes(Driver, CertNodesFun, SslSocket, Allowed) ->
+ case ssl:peercert(SslSocket) of
+ {ok,PeerCertDER} ->
+ case ssl:peername(SslSocket) of
+ {ok,{PeerIP,_Port}} ->
+ PeerCert = public_key:pkix_decode_cert(PeerCertDER, otp),
+ %%
+ %% Parse out all node names from the peer's certificate
+ %%
+ case CertNodesFun(PeerCert) of
+ undefined ->
+ %% Certificate allows all nodes
+ Allowed;
+ [] ->
+ %% Certificate allows no nodes
+ ?shutdown(cert_missing_node_name);
+ CertNodes ->
+ %% Filter out all nodes in the
+ %% allowed list that is in peer
+ %% certificate and that has got
+ %% the same IP address as the peer
+ allowed(
+ filter_nodes_by_ip(
+ CertNodes, Driver, PeerIP),
+ Allowed)
+ end;
+ Error1 ->
+ ?shutdown2(no_peer_ip, trace(Error1))
+ end;
+ Error2 ->
+ ?shutdown2(no_peer_cert, trace(Error2))
+ end.
+
+allowed(CertNodes, []) ->
+ %% Empty allowed list means all allowed
+ %% -> allow only certificate nodes
+ CertNodes;
+allowed(CertNodes, Allowed) ->
+ %% Find the intersection of the allowed list and certificate nodes
+ case
+ [CertNode ||
+ CertNode <- CertNodes,
+ dist_util:is_allowed(CertNode, Allowed)]
+ of
+ [] ->
+ error_logger:error_msg(
+ "** Connection attempt from "
+ "disallowed node(s) ~p ** ~n", [CertNodes]),
+ ?shutdown2(CertNodes, trace({is_allowed, not_allowed}));
+ NewAllowed ->
+ NewAllowed
+ end.
setup(Node, Type, MyNode, LongOrShortNames, SetupTime) ->
@@ -351,7 +517,7 @@ gen_setup(Driver, Node, Type, MyNode, LongOrShortNames, SetupTime) ->
[link, {priority, max}])).
do_setup(Driver, Kernel, Node, Type, MyNode, LongOrShortNames, SetupTime) ->
- [Name, Address] = splitnode(Driver, Node, LongOrShortNames),
+ {Name, Address} = split_node(Driver, Node, LongOrShortNames),
case Driver:getaddr(Address) of
{ok, Ip} ->
Timer = trace(dist_util:start_timer(SetupTime)),
@@ -361,8 +527,12 @@ do_setup(Driver, Kernel, Node, Type, MyNode, LongOrShortNames, SetupTime) ->
Opts =
trace(
connect_options(
- [{server_name_indication, atom_to_list(Node)}
- |get_ssl_options(client)])),
+ %%
+ %% Use verify_server/3 to verify that
+ %% the server's certificate is for Node
+ %%
+ setup_verify_server(
+ get_ssl_options(client), Node))),
dist_util:reset_timer(Timer),
case ssl:connect(
Address, TcpPort,
@@ -370,7 +540,7 @@ do_setup(Driver, Kernel, Node, Type, MyNode, LongOrShortNames, SetupTime) ->
Driver:family(), nodelay()] ++ Opts,
net_kernel:connecttime()) of
{ok, #sslsocket{pid = DistCtrl} = SslSocket} ->
- monitor_pid(DistCtrl),
+ _ = monitor_pid(DistCtrl),
ok = ssl:controlling_process(SslSocket, self()),
HSData0 = hs_data_common(SslSocket),
HSData =
@@ -411,19 +581,81 @@ close(Socket) ->
gen_close(Driver, Socket) ->
trace(Driver:close(Socket)).
+%% {verify_fun,{fun ?MODULE:verify_server/3,_}} is used
+%% as a configuration marker that verify_server/3 shall be used.
+%%
+%% Replace the State in the first occurence of
+%% {verify_fun,{fun ?MODULE:verify_server/3,State}} and remove the rest.
+%% The inserted state is not accesible from a configuration file
+%% since it is dynamic and connection dependent.
+%%
+setup_verify_server(Opts, Node) ->
+ setup_verify_server(Opts, Node, true).
+%%
+setup_verify_server([], _Node, _) ->
+ [];
+setup_verify_server([Opt|Opts], Node, Once) ->
+ case Opt of
+ {verify_fun,{Fun,CertNodesFun}} ->
+ case Fun =:= fun ?MODULE:verify_server/3 of
+ true when not is_function(CertNodesFun, 1) ->
+ ?shutdown2(
+ Node,
+ {verify_server_bad_argument,CertNodesFun});
+ true when Once ->
+ [{verify_fun,{Fun,{CertNodesFun,Node}}}
+ |setup_verify_server(Opts, Node, false)];
+ true ->
+ setup_verify_server(Opts, Node, Once);
+ false ->
+ [Opt|setup_verify_server(Opts, Node, Once)]
+ end;
+ _ ->
+ [Opt|setup_verify_server(Opts, Node, Once)]
+ end.
+
+verify_server(_, {bad_cert,_} = Reason, _) ->
+ {fail,Reason};
+verify_server(_, {extension,_}, S) ->
+ {unknown,S};
+verify_server(_, valid, S) ->
+ {valid,S};
+verify_server(PeerCert, valid_peer, {CertNodesFun,Node} = S) ->
+ %%
+ %% Parse out all node names from the peer's certificate
+ %%
+ case CertNodesFun(PeerCert) of
+ undefined ->
+ %% Certificate allows all nodes
+ {valid,S};
+ [] ->
+ %% Certificate allows no nodes
+ {fail,cert_missing_node_name};
+ CertNodes ->
+ %% Check that the node we are connecting to
+ %% is in the peer certificate
+ case dist_util:is_allowed(Node, CertNodes) of
+ true ->
+ {valid,S};
+ false ->
+ {fail,wrong_nodes_in_cert}
+ end
+ end.
+
+
%% ------------------------------------------------------------
%% Do only accept new connection attempts from nodes at our
%% own LAN, if the check_ip environment parameter is true.
%% ------------------------------------------------------------
-check_ip(Driver, SslSocket) ->
+check_ip(Driver, Socket) ->
case application:get_env(check_ip) of
{ok, true} ->
- case get_ifs(SslSocket) of
+ case get_ifs(Socket) of
{ok, IFs, IP} ->
check_ip(Driver, IFs, IP);
Other ->
?shutdown2(
- no_node, trace({check_ip_failed, SslSocket, Other}))
+ no_node, trace({check_ip_failed, Socket, Other}))
end;
_ ->
true
@@ -437,7 +669,7 @@ check_ip(Driver, [{OwnIP, _, Netmask}|IFs], PeerIP) ->
check_ip(_Driver, [], PeerIP) ->
{false, PeerIP}.
-get_ifs(#sslsocket{fd = {gen_tcp, Socket, _}}) ->
+get_ifs(Socket) ->
case inet:peername(Socket) of
{ok, {IP, _}} ->
%% XXX this is seriously broken for IPv6
@@ -450,12 +682,87 @@ get_ifs(#sslsocket{fd = {gen_tcp, Socket, _}}) ->
end.
+%% Look in Extensions, in all subjectAltName:s
+%% to find node names in this certificate.
+%% Host names are picked up as a subjectAltName containing
+%% a dNSName, and the first subjectAltName containing
+%% a commonName is the node name.
+%%
+cert_nodes(
+ #'OTPCertificate'{
+ tbsCertificate = #'OTPTBSCertificate'{extensions = Extensions}}) ->
+ parse_extensions(Extensions).
+
+
+parse_extensions(Extensions) when is_list(Extensions) ->
+ parse_extensions(Extensions, [], []);
+parse_extensions(asn1_NOVALUE) ->
+ undefined. % Allow all nodes
+%%
+parse_extensions([], [], []) ->
+ undefined; % Allow all nodes
+parse_extensions([], Hosts, []) ->
+ lists:reverse(Hosts);
+parse_extensions([], [], Names) ->
+ [Name ++ "@" || Name <- lists:reverse(Names)];
+parse_extensions([], Hosts, Names) ->
+ [Name ++ "@" ++ Host ||
+ Host <- lists:reverse(Hosts),
+ Name <- lists:reverse(Names)];
+parse_extensions(
+ [#'Extension'{
+ extnID = ?'id-ce-subjectAltName',
+ extnValue = AltNames}
+ |Extensions],
+ Hosts, Names) ->
+ case parse_subject_altname(AltNames) of
+ none ->
+ parse_extensions(Extensions, Hosts, Names);
+ {host,Host} ->
+ parse_extensions(Extensions, [Host|Hosts], Names);
+ {name,Name} ->
+ parse_extensions(Extensions, Hosts, [Name|Names])
+ end;
+parse_extensions([_|Extensions], Hosts, Names) ->
+ parse_extensions(Extensions, Hosts, Names).
+
+parse_subject_altname([]) ->
+ none;
+parse_subject_altname([{dNSName,Host}|_AltNames]) ->
+ {host,Host};
+parse_subject_altname(
+ [{directoryName,{rdnSequence,[Rdn|_]}}|AltNames]) ->
+ %%
+ %% XXX Why is rdnSequence a sequence?
+ %% Should we parse all members?
+ %%
+ case parse_rdn(Rdn) of
+ none ->
+ parse_subject_altname(AltNames);
+ Name ->
+ {name,Name}
+ end;
+parse_subject_altname([_|AltNames]) ->
+ parse_subject_altname(AltNames).
+
+
+parse_rdn([]) ->
+ none;
+parse_rdn(
+ [#'AttributeTypeAndValue'{
+ type = ?'id-at-commonName',
+ value = {utf8String,CommonName}}|_]) ->
+ unicode:characters_to_list(CommonName);
+parse_rdn([_|Rdn]) ->
+ parse_rdn(Rdn).
+
+
%% If Node is illegal terminate the connection setup!!
-splitnode(Driver, Node, LongOrShortNames) ->
- case string:split(atom_to_list(Node), "@") of
- [Name, Host] when Host =/= [] ->
- check_node(Driver, Name, Node, Host, LongOrShortNames);
- [_] ->
+split_node(Driver, Node, LongOrShortNames) ->
+ case dist_util:split_node(Node) of
+ {node, Name, Host} ->
+ check_node(Driver, Node, Name, Host, LongOrShortNames);
+ {host, _} ->
error_logger:error_msg(
"** Nodename ~p illegal, no '@' character **~n",
[Node]),
@@ -466,12 +773,12 @@ splitnode(Driver, Node, LongOrShortNames) ->
?shutdown2(Node, trace({illegal_node_name, Node}))
end.
-check_node(Driver, Name, Node, Host, LongOrShortNames) ->
- case string:split(Host, ".") of
- [_] when LongOrShortNames == longnames ->
+check_node(Driver, Node, Name, Host, LongOrShortNames) ->
+ case string:split(Host, ".", all) of
+ [_] when LongOrShortNames =:= longnames ->
case Driver:parse_address(Host) of
{ok, _} ->
- [Name, Host];
+ {Name, Host};
_ ->
error_logger:error_msg(
"** System running to use "
@@ -480,7 +787,7 @@ check_node(Driver, Name, Node, Host, LongOrShortNames) ->
[Host]),
?shutdown2(Node, trace({not_longnames, Host}))
end;
- [_, _] when LongOrShortNames == shortnames ->
+ [_,_|_] when LongOrShortNames =:= shortnames ->
error_logger:error_msg(
"** System NOT running to use "
"fully qualified hostnames **~n"
@@ -488,19 +795,9 @@ check_node(Driver, Name, Node, Host, LongOrShortNames) ->
[Host]),
?shutdown2(Node, trace({not_shortnames, Host}));
_ ->
- [Name, Host]
+ {Name, Host}
end.
-split_node(Node) when is_atom(Node) ->
- case string:split(atom_to_list(Node), "@") of
- [Name, Host] when Name =/= [], Host =/= [] ->
- Host;
- _ ->
- false
- end;
-split_node(_) ->
- false.
-
%% -------------------------------------------------------------------------
connect_options(Opts) ->
@@ -545,70 +842,50 @@ get_ssl_dist_arguments(Type) ->
[{erl_dist, true}]
end.
-ssl_options(_,[]) ->
+
+ssl_options(_Type, []) ->
[];
-ssl_options(server, ["client_" ++ _, _Value |T]) ->
- ssl_options(server,T);
-ssl_options(client, ["server_" ++ _, _Value|T]) ->
- ssl_options(client,T);
-ssl_options(server, ["server_certfile", Value|T]) ->
- [{certfile, Value} | ssl_options(server,T)];
-ssl_options(client, ["client_certfile", Value | T]) ->
- [{certfile, Value} | ssl_options(client,T)];
-ssl_options(server, ["server_cacertfile", Value|T]) ->
- [{cacertfile, Value} | ssl_options(server,T)];
-ssl_options(client, ["client_cacertfile", Value|T]) ->
- [{cacertfile, Value} | ssl_options(client,T)];
-ssl_options(server, ["server_keyfile", Value|T]) ->
- [{keyfile, Value} | ssl_options(server,T)];
-ssl_options(client, ["client_keyfile", Value|T]) ->
- [{keyfile, Value} | ssl_options(client,T)];
-ssl_options(server, ["server_password", Value|T]) ->
- [{password, Value} | ssl_options(server,T)];
-ssl_options(client, ["client_password", Value|T]) ->
- [{password, Value} | ssl_options(client,T)];
-ssl_options(server, ["server_verify", Value|T]) ->
- [{verify, atomize(Value)} | ssl_options(server,T)];
-ssl_options(client, ["client_verify", Value|T]) ->
- [{verify, atomize(Value)} | ssl_options(client,T)];
-ssl_options(server, ["server_verify_fun", Value|T]) ->
- [{verify_fun, verify_fun(Value)} | ssl_options(server,T)];
-ssl_options(client, ["client_verify_fun", Value|T]) ->
- [{verify_fun, verify_fun(Value)} | ssl_options(client,T)];
-ssl_options(server, ["server_crl_check", Value|T]) ->
- [{crl_check, atomize(Value)} | ssl_options(server,T)];
-ssl_options(client, ["client_crl_check", Value|T]) ->
- [{crl_check, atomize(Value)} | ssl_options(client,T)];
-ssl_options(server, ["server_crl_cache", Value|T]) ->
- [{crl_cache, termify(Value)} | ssl_options(server,T)];
-ssl_options(client, ["client_crl_cache", Value|T]) ->
- [{crl_cache, termify(Value)} | ssl_options(client,T)];
-ssl_options(server, ["server_reuse_sessions", Value|T]) ->
- [{reuse_sessions, atomize(Value)} | ssl_options(server,T)];
-ssl_options(client, ["client_reuse_sessions", Value|T]) ->
- [{reuse_sessions, atomize(Value)} | ssl_options(client,T)];
-ssl_options(server, ["server_secure_renegotiate", Value|T]) ->
- [{secure_renegotiate, atomize(Value)} | ssl_options(server,T)];
-ssl_options(client, ["client_secure_renegotiate", Value|T]) ->
- [{secure_renegotiate, atomize(Value)} | ssl_options(client,T)];
-ssl_options(server, ["server_depth", Value|T]) ->
- [{depth, list_to_integer(Value)} | ssl_options(server,T)];
-ssl_options(client, ["client_depth", Value|T]) ->
- [{depth, list_to_integer(Value)} | ssl_options(client,T)];
-ssl_options(server, ["server_hibernate_after", Value|T]) ->
- [{hibernate_after, list_to_integer(Value)} | ssl_options(server,T)];
-ssl_options(client, ["client_hibernate_after", Value|T]) ->
- [{hibernate_after, list_to_integer(Value)} | ssl_options(client,T)];
-ssl_options(server, ["server_ciphers", Value|T]) ->
- [{ciphers, Value} | ssl_options(server,T)];
-ssl_options(client, ["client_ciphers", Value|T]) ->
- [{ciphers, Value} | ssl_options(client,T)];
-ssl_options(server, ["server_dhfile", Value|T]) ->
- [{dhfile, Value} | ssl_options(server,T)];
-ssl_options(server, ["server_fail_if_no_peer_cert", Value|T]) ->
- [{fail_if_no_peer_cert, atomize(Value)} | ssl_options(server,T)];
-ssl_options(Type, Opts) ->
- error(malformed_ssl_dist_opt, [Type, Opts]).
+ssl_options(client, ["client_" ++ Opt, Value | T] = Opts) ->
+ ssl_options(client, T, Opts, Opt, Value);
+ssl_options(server, ["server_" ++ Opt, Value | T] = Opts) ->
+ ssl_options(server, T, Opts, Opt, Value);
+ssl_options(Type, [_Opt, _Value | T]) ->
+ ssl_options(Type, T).
+%%
+ssl_options(Type, T, Opts, Opt, Value) ->
+ case ssl_option(Type, Opt) of
+ error ->
+ error(malformed_ssl_dist_opt, [Type, Opts]);
+ Fun ->
+ [{list_to_atom(Opt), Fun(Value)}|ssl_options(Type, T)]
+ end.
+
+ssl_option(server, Opt) ->
+ case Opt of
+ "dhfile" -> fun listify/1;
+ "fail_if_no_peer_cert" -> fun atomize/1;
+ _ -> ssl_option(client, Opt)
+ end;
+ssl_option(client, Opt) ->
+ case Opt of
+ "certfile" -> fun listify/1;
+ "cacertfile" -> fun listify/1;
+ "keyfile" -> fun listify/1;
+ "password" -> fun listify/1;
+ "verify" -> fun atomize/1;
+ "verify_fun" -> fun verify_fun/1;
+ "crl_check" -> fun atomize/1;
+ "crl_cache" -> fun termify/1;
+ "reuse_sessions" -> fun atomize/1;
+ "secure_renegotiate" -> fun atomize/1;
+ "depth" -> fun erlang:list_to_integer/1;
+ "hibernate_after" -> fun erlang:list_to_integer/1;
+ "ciphers" -> fun listify/1;
+ _ -> error
+ end.
+
+listify(List) when is_list(List) ->
+ List.
atomize(List) when is_list(List) ->
list_to_atom(List);
diff --git a/lib/ssl/src/ssl.appup.src b/lib/ssl/src/ssl.appup.src
index 4ad2a2f1fd..bfdd0c205b 100644
--- a/lib/ssl/src/ssl.appup.src
+++ b/lib/ssl/src/ssl.appup.src
@@ -1,7 +1,6 @@
%% -*- erlang -*-
{"%VSN%",
[
- {<<"8.2.4">>, [{load_module, ssl_cipher, soft_purge, soft_purge, []}]},
{<<"8\\..*">>, [{restart_application, ssl}]},
{<<"7\\..*">>, [{restart_application, ssl}]},
{<<"6\\..*">>, [{restart_application, ssl}]},
@@ -10,7 +9,6 @@
{<<"3\\..*">>, [{restart_application, ssl}]}
],
[
- {<<"8.2.4">>, [{load_module, ssl_cipher, soft_purge, soft_purge, []}]},
{<<"8\\..*">>, [{restart_application, ssl}]},
{<<"7\\..*">>, [{restart_application, ssl}]},
{<<"6\\..*">>, [{restart_application, ssl}]},
diff --git a/lib/ssl/src/ssl.erl b/lib/ssl/src/ssl.erl
index e5d487a663..5b6d92ebf4 100644
--- a/lib/ssl/src/ssl.erl
+++ b/lib/ssl/src/ssl.erl
@@ -32,7 +32,9 @@
%% Socket handling
-export([connect/3, connect/2, connect/4,
listen/2, transport_accept/1, transport_accept/2,
- ssl_accept/1, ssl_accept/2, ssl_accept/3,
+ handshake/1, handshake/2, handshake/3,
+ handshake_continue/3, handshake_cancel/1,
+ ssl_accept/1, ssl_accept/2, ssl_accept/3,
controlling_process/2, peername/1, peercert/1, sockname/1,
close/1, close/2, shutdown/2, recv/2, recv/3, send/2,
getopts/2, setopts/2, getstat/1, getstat/2
@@ -45,7 +47,7 @@
format_error/1, renegotiate/1, prf/5, negotiated_protocol/1,
connection_information/1, connection_information/2]).
%% Misc
--export([handle_options/2, tls_version/1]).
+-export([handle_options/2, tls_version/1, new_ssl_options/3]).
-include("ssl_api.hrl").
-include("ssl_internal.hrl").
@@ -170,23 +172,54 @@ transport_accept(#sslsocket{pid = {ListenSocket,
ok | {ok, #sslsocket{}} | {error, reason()}.
-spec ssl_accept(#sslsocket{} | port(), [ssl_option()] | [ssl_option()| transport_option()], timeout()) ->
- {ok, #sslsocket{}} | {error, reason()}.
+ ok | {ok, #sslsocket{}} | {error, reason()}.
%%
%% Description: Performs accept on an ssl listen socket. e.i. performs
%% ssl handshake.
%%--------------------------------------------------------------------
ssl_accept(ListenSocket) ->
- ssl_accept(ListenSocket, infinity).
+ ssl_accept(ListenSocket, [], infinity).
+ssl_accept(Socket, Timeout) when (is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity) ->
+ ssl_accept(Socket, [], Timeout);
+ssl_accept(ListenSocket, SslOptions) when is_port(ListenSocket) ->
+ ssl_accept(ListenSocket, SslOptions, infinity);
+ssl_accept(Socket, Timeout) ->
+ ssl_accept(Socket, [], Timeout).
+ssl_accept(Socket, SslOptions, Timeout) when is_port(Socket) ->
+ handshake(Socket, SslOptions, Timeout);
+ssl_accept(Socket, SslOptions, Timeout) ->
+ case handshake(Socket, SslOptions, Timeout) of
+ {ok, _} ->
+ ok;
+ Error ->
+ Error
+ end.
+%%--------------------------------------------------------------------
+-spec handshake(#sslsocket{}) -> {ok, #sslsocket{}} | {error, reason()}.
+-spec handshake(#sslsocket{} | port(), timeout()| [ssl_option()
+ | transport_option()]) ->
+ {ok, #sslsocket{}} | {error, reason()}.
-ssl_accept(#sslsocket{} = Socket, Timeout) when (is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity) ->
+-spec handshake(#sslsocket{} | port(), [ssl_option()] | [ssl_option()| transport_option()], timeout()) ->
+ {ok, #sslsocket{}} | {error, reason()}.
+%%
+%% Description: Performs accept on an ssl listen socket. e.i. performs
+%% ssl handshake.
+%%--------------------------------------------------------------------
+handshake(ListenSocket) ->
+ handshake(ListenSocket, infinity).
+
+handshake(#sslsocket{} = Socket, Timeout) when (is_integer(Timeout) andalso Timeout >= 0) or
+ (Timeout == infinity) ->
ssl_connection:handshake(Socket, Timeout);
-ssl_accept(ListenSocket, SslOptions) when is_port(ListenSocket) ->
- ssl_accept(ListenSocket, SslOptions, infinity).
+handshake(ListenSocket, SslOptions) when is_port(ListenSocket) ->
+ handshake(ListenSocket, SslOptions, infinity).
-ssl_accept(#sslsocket{} = Socket, [], Timeout) when (is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity)->
- ssl_accept(Socket, Timeout);
-ssl_accept(#sslsocket{fd = {_, _, _, Tracker}} = Socket, SslOpts, Timeout) when
+handshake(#sslsocket{} = Socket, [], Timeout) when (is_integer(Timeout) andalso Timeout >= 0) or
+ (Timeout == infinity)->
+ handshake(Socket, Timeout);
+handshake(#sslsocket{fd = {_, _, _, Tracker}} = Socket, SslOpts, Timeout) when
(is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity)->
try
{ok, EmOpts, _} = tls_socket:get_all_opts(Tracker),
@@ -195,7 +228,7 @@ ssl_accept(#sslsocket{fd = {_, _, _, Tracker}} = Socket, SslOpts, Timeout) when
catch
Error = {error, _Reason} -> Error
end;
-ssl_accept(#sslsocket{pid = Pid, fd = {_, _, _}} = Socket, SslOpts, Timeout) when
+handshake(#sslsocket{pid = Pid, fd = {_, _, _}} = Socket, SslOpts, Timeout) when
(is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity)->
try
{ok, EmOpts, _} = dtls_udp_listener:get_all_opts(Pid),
@@ -204,8 +237,8 @@ ssl_accept(#sslsocket{pid = Pid, fd = {_, _, _}} = Socket, SslOpts, Timeout) whe
catch
Error = {error, _Reason} -> Error
end;
-ssl_accept(Socket, SslOptions, Timeout) when is_port(Socket),
- (is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity) ->
+handshake(Socket, SslOptions, Timeout) when is_port(Socket),
+ (is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity) ->
{Transport,_,_,_} =
proplists:get_value(cb_info, SslOptions, {gen_tcp, tcp, tcp_closed, tcp_error}),
EmulatedOptions = tls_socket:emulated_options(),
@@ -215,13 +248,31 @@ ssl_accept(Socket, SslOptions, Timeout) when is_port(Socket),
{ok, #config{transport_info = CbInfo, ssl = SslOpts, emulated = EmOpts}} ->
ok = tls_socket:setopts(Transport, Socket, tls_socket:internal_inet_values()),
{ok, Port} = tls_socket:port(Transport, Socket),
- ssl_connection:ssl_accept(ConnetionCb, Port, Socket,
- {SslOpts,
- tls_socket:emulated_socket_options(EmOpts, #socket_options{}), undefined},
- self(), CbInfo, Timeout)
+ ssl_connection:handshake(ConnetionCb, Port, Socket,
+ {SslOpts,
+ tls_socket:emulated_socket_options(EmOpts, #socket_options{}), undefined},
+ self(), CbInfo, Timeout)
catch
Error = {error, _Reason} -> Error
end.
+
+%%--------------------------------------------------------------------
+-spec handshake_continue(#sslsocket{}, [ssl_option()], timeout()) ->
+ {ok, #sslsocket{}} | {error, reason()}.
+%%
+%%
+%% Description: Continues the handshke possible with newly supplied options.
+%%--------------------------------------------------------------------
+handshake_continue(Socket, SSLOptions, Timeout) ->
+ ssl_connection:handshake_continue(Socket, SSLOptions, Timeout).
+%%--------------------------------------------------------------------
+-spec handshake_cancel(#sslsocket{}) -> term().
+%%
+%% Description: Cancels the handshakes sending a close alert.
+%%--------------------------------------------------------------------
+handshake_cancel(Socket) ->
+ ssl_connection:handshake_cancel(Socket).
+
%%--------------------------------------------------------------------
-spec close(#sslsocket{}) -> term().
%%
@@ -885,7 +936,8 @@ handle_options(Opts0, Role, Host) ->
client, Role),
crl_check = handle_option(crl_check, Opts, false),
crl_cache = handle_option(crl_cache, Opts, {ssl_crl_cache, {internal, []}}),
- max_handshake_size = handle_option(max_handshake_size, Opts, ?DEFAULT_MAX_HANDSHAKE_SIZE)
+ max_handshake_size = handle_option(max_handshake_size, Opts, ?DEFAULT_MAX_HANDSHAKE_SIZE),
+ handshake = handle_option(handshake, Opts, full)
},
CbInfo = proplists:get_value(cb_info, Opts, default_cb_info(Protocol)),
@@ -901,8 +953,7 @@ handle_options(Opts0, Role, Host) ->
client_preferred_next_protocols, log_alert,
server_name_indication, honor_cipher_order, padding_check, crl_check, crl_cache,
fallback, signature_algs, eccs, honor_ecc_order, beast_mitigation,
- max_handshake_size],
-
+ max_handshake_size, handshake],
SockOpts = lists:foldl(fun(Key, PropList) ->
proplists:delete(Key, PropList)
end, Opts, SslOptions),
@@ -914,8 +965,6 @@ handle_options(Opts0, Role, Host) ->
inet_user = Sock, transport_info = CbInfo, connection_cb = ConnetionCb
}}.
-
-
handle_option(OptionName, Opts, Default, Role, Role) ->
handle_option(OptionName, Opts, Default);
handle_option(_, _, undefined = Value, _, _) ->
@@ -1143,6 +1192,10 @@ validate_option(protocol, Value = tls) ->
Value;
validate_option(protocol, Value = dtls) ->
Value;
+validate_option(handshake, hello = Value) ->
+ Value;
+validate_option(handshake, full = Value) ->
+ Value;
validate_option(Opt, Value) ->
throw({error, {options, {Opt, Value}}}).
diff --git a/lib/ssl/src/ssl_cipher.erl b/lib/ssl/src/ssl_cipher.erl
index 28b26fd358..0956d3501d 100644
--- a/lib/ssl/src/ssl_cipher.erl
+++ b/lib/ssl/src/ssl_cipher.erl
@@ -37,10 +37,10 @@
erl_suite_definition/1,
cipher_init/3, decipher/6, cipher/5, decipher_aead/6, cipher_aead/6,
suite/1, suites/1, all_suites/1, crypto_support_filters/0,
- ec_keyed_suites/0, chacha_suites/1, anonymous_suites/1, psk_suites/1, psk_suites_anon/1,
+ chacha_suites/1, anonymous_suites/1, psk_suites/1, psk_suites_anon/1,
srp_suites/0, srp_suites_anon/0,
rc4_suites/1, des_suites/1, rsa_suites/1, openssl_suite/1, openssl_suite_name/1,
- filter/2, filter_suites/1, filter_suites/2,
+ filter/3, filter_suites/1, filter_suites/2,
hash_algorithm/1, sign_algorithm/1, is_acceptable_hash/2, is_fallback/1,
random_bytes/1, calc_mac_hash/4,
is_stream_ciphersuite/1]).
@@ -2212,39 +2212,25 @@ openssl_suite_name(Cipher) ->
suite_definition(Cipher).
%%--------------------------------------------------------------------
--spec filter(undefined | binary(), [cipher_suite()]) -> [cipher_suite()].
+-spec filter(undefined | binary(), [cipher_suite()], ssl_record:ssl_version()) -> [cipher_suite()].
%%
%% Description: Select the cipher suites that can be used together with the
%% supplied certificate. (Server side functionality)
%%-------------------------------------------------------------------
-filter(undefined, Ciphers) ->
+filter(undefined, Ciphers, _) ->
Ciphers;
-filter(DerCert, Ciphers) ->
+filter(DerCert, Ciphers0, Version) ->
OtpCert = public_key:pkix_decode_cert(DerCert, otp),
SigAlg = OtpCert#'OTPCertificate'.signatureAlgorithm,
PubKeyInfo = OtpCert#'OTPCertificate'.tbsCertificate#'OTPTBSCertificate'.subjectPublicKeyInfo,
PubKeyAlg = PubKeyInfo#'OTPSubjectPublicKeyInfo'.algorithm,
- Ciphers1 =
- case ssl_certificate:public_key_type(PubKeyAlg#'PublicKeyAlgorithm'.algorithm) of
- rsa ->
- filter_keyuse(OtpCert, ((Ciphers -- dsa_signed_suites()) -- ec_keyed_suites()) -- ecdh_suites(),
- rsa_suites(), dhe_rsa_suites() ++ ecdhe_rsa_suites());
- dsa ->
- (Ciphers -- rsa_keyed_suites()) -- ec_keyed_suites();
- ec ->
- filter_keyuse(OtpCert, (Ciphers -- rsa_keyed_suites()) -- dsa_signed_suites(),
- [], ecdhe_ecdsa_suites())
- end,
-
- case public_key:pkix_sign_types(SigAlg#'SignatureAlgorithm'.algorithm) of
- {_, rsa} ->
- Ciphers1 -- ecdsa_signed_suites();
- {_, dsa} ->
- Ciphers1;
- {_, ecdsa} ->
- Ciphers1 -- rsa_signed_suites()
- end.
+ Ciphers = filter_suites_pubkey(
+ ssl_certificate:public_key_type(PubKeyAlg#'PublicKeyAlgorithm'.algorithm),
+ Ciphers0, Version, OtpCert),
+ {_, Sign} = public_key:pkix_sign_types(SigAlg#'SignatureAlgorithm'.algorithm),
+ filter_suites_signature(Sign, Ciphers, Version).
+
%%--------------------------------------------------------------------
-spec filter_suites([erl_cipher_suite()] | [cipher_suite()], map()) ->
[erl_cipher_suite()] | [cipher_suite()].
@@ -2676,141 +2662,184 @@ next_iv(Bin, IV) ->
<<_:FirstPart/binary, NextIV:IVSz/binary>> = Bin,
NextIV.
-rsa_signed_suites() ->
- dhe_rsa_suites() ++ rsa_suites() ++
- psk_rsa_suites() ++ srp_rsa_suites() ++
- ecdh_rsa_suites() ++ ecdhe_rsa_suites().
-
-rsa_keyed_suites() ->
- dhe_rsa_suites() ++ rsa_suites() ++
- psk_rsa_suites() ++ srp_rsa_suites() ++
- ecdhe_rsa_suites().
-
-dhe_rsa_suites() ->
- [?TLS_DHE_RSA_WITH_AES_256_CBC_SHA256,
- ?TLS_DHE_RSA_WITH_AES_256_CBC_SHA,
- ?TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA,
- ?TLS_DHE_RSA_WITH_AES_128_CBC_SHA256,
- ?TLS_DHE_RSA_WITH_AES_128_CBC_SHA,
- ?TLS_DHE_RSA_WITH_DES_CBC_SHA,
- ?TLS_DHE_RSA_WITH_AES_128_GCM_SHA256,
- ?TLS_DHE_RSA_WITH_AES_256_GCM_SHA384,
- ?TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256
- ].
-
-psk_rsa_suites() ->
- [?TLS_RSA_PSK_WITH_AES_256_GCM_SHA384,
- ?TLS_RSA_PSK_WITH_AES_128_GCM_SHA256,
- ?TLS_RSA_PSK_WITH_AES_256_CBC_SHA384,
- ?TLS_RSA_PSK_WITH_AES_128_CBC_SHA256,
- ?TLS_RSA_PSK_WITH_AES_256_CBC_SHA,
- ?TLS_RSA_PSK_WITH_AES_128_CBC_SHA,
- ?TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA,
- ?TLS_RSA_PSK_WITH_RC4_128_SHA].
-srp_rsa_suites() ->
- [?TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA,
- ?TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA,
- ?TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA].
-
-rsa_suites() ->
- [?TLS_RSA_WITH_AES_256_CBC_SHA256,
- ?TLS_RSA_WITH_AES_256_CBC_SHA,
- ?TLS_RSA_WITH_3DES_EDE_CBC_SHA,
- ?TLS_RSA_WITH_AES_128_CBC_SHA256,
- ?TLS_RSA_WITH_AES_128_CBC_SHA,
- ?TLS_RSA_WITH_RC4_128_SHA,
- ?TLS_RSA_WITH_RC4_128_MD5,
- ?TLS_RSA_WITH_DES_CBC_SHA,
- ?TLS_RSA_WITH_AES_128_GCM_SHA256,
- ?TLS_RSA_WITH_AES_256_GCM_SHA384].
-
-ecdh_rsa_suites() ->
- [?TLS_ECDH_RSA_WITH_NULL_SHA,
- ?TLS_ECDH_RSA_WITH_RC4_128_SHA,
- ?TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA,
- ?TLS_ECDH_RSA_WITH_AES_128_CBC_SHA,
- ?TLS_ECDH_RSA_WITH_AES_256_CBC_SHA,
- ?TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256,
- ?TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384,
- ?TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256,
- ?TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384].
-
-ecdhe_rsa_suites() ->
- [?TLS_ECDHE_RSA_WITH_NULL_SHA,
- ?TLS_ECDHE_RSA_WITH_RC4_128_SHA,
- ?TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
- ?TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
- ?TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
- ?TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
- ?TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384,
- ?TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
- ?TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
- ?TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256].
-
-dsa_signed_suites() ->
- dhe_dss_suites() ++ srp_dss_suites().
-
-dhe_dss_suites() ->
- [?TLS_DHE_DSS_WITH_AES_256_CBC_SHA256,
- ?TLS_DHE_DSS_WITH_AES_256_CBC_SHA,
- ?TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA,
- ?TLS_DHE_DSS_WITH_AES_128_CBC_SHA256,
- ?TLS_DHE_DSS_WITH_AES_128_CBC_SHA,
- ?TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA,
- ?TLS_DHE_DSS_WITH_AES_128_GCM_SHA256,
- ?TLS_DHE_DSS_WITH_AES_256_GCM_SHA384].
-
-srp_dss_suites() ->
- [?TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA,
- ?TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA,
- ?TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA].
-
-ec_keyed_suites() ->
- ecdh_ecdsa_suites() ++ ecdhe_ecdsa_suites()
- ++ ecdh_rsa_suites().
+filter_suites_pubkey(rsa, CiphersSuites0, Version, OtpCert) ->
+ KeyUses = key_uses(OtpCert),
+ CiphersSuites = filter_keyuse_suites(keyEncipherment, KeyUses,
+ (CiphersSuites0 -- ec_keyed_suites(CiphersSuites0))
+ -- dss_keyed_suites(CiphersSuites0),
+ rsa_suites_encipher(CiphersSuites0)),
+ filter_keyuse_suites(digitalSignature, KeyUses, CiphersSuites,
+ rsa_signed_suites(CiphersSuites, Version));
+filter_suites_pubkey(dsa, Ciphers, _, _OtpCert) ->
+ (Ciphers -- rsa_keyed_suites(Ciphers)) -- ec_keyed_suites(Ciphers);
+filter_suites_pubkey(ec, Ciphers, _, OtpCert) ->
+ Uses = key_uses(OtpCert),
+ filter_keyuse_suites(digitalSignature, Uses,
+ (Ciphers -- rsa_keyed_suites(Ciphers)) -- dss_keyed_suites(Ciphers),
+ ecdsa_sign_suites(Ciphers)).
+
+filter_suites_signature(rsa, Ciphers, Version) ->
+ Ciphers -- ecdsa_signed_suites(Ciphers, Version) -- dsa_signed_suites(Ciphers, Version);
+filter_suites_signature(dsa, Ciphers, Version) ->
+ Ciphers -- ecdsa_signed_suites(Ciphers, Version) -- rsa_signed_suites(Ciphers, Version);
+filter_suites_signature(ecdsa, Ciphers, Version) ->
+ Ciphers -- rsa_signed_suites(Ciphers, Version) -- dsa_signed_suites(Ciphers, Version).
+
+
+%% From RFC 5246 - Section 7.4.2. Server Certificate
+%% If the client provided a "signature_algorithms" extension, then all
+%% certificates provided by the server MUST be signed by a
+%% hash/signature algorithm pair that appears in that extension. Note
+%% that this implies that a certificate containing a key for one
+%% signature algorithm MAY be signed using a different signature
+%% algorithm (for instance, an RSA key signed with a DSA key). This is
+%% a departure from TLS 1.1, which required that the algorithms be the
+%% same.
+%% Note that this also implies that the DH_DSS, DH_RSA,
+%% ECDH_ECDSA, and ECDH_RSA key exchange algorithms do not restrict the
+%% algorithm used to sign the certificate. Fixed DH certificates MAY be
+%% signed with any hash/signature algorithm pair appearing in the
+%% extension. The names DH_DSS, DH_RSA, ECDH_ECDSA, and ECDH_RSA are
+%% historical.
+%% Note: DH_DSS and DH_RSA is not supported
+rsa_signed({3,N}) when N >= 3 ->
+ fun(rsa) -> true;
+ (dhe_rsa) -> true;
+ (ecdhe_rsa) -> true;
+ (rsa_psk) -> true;
+ (srp_rsa) -> true;
+ (_) -> false
+ end;
+rsa_signed(_) ->
+ fun(rsa) -> true;
+ (dhe_rsa) -> true;
+ (ecdhe_rsa) -> true;
+ (ecdh_rsa) -> true;
+ (rsa_psk) -> true;
+ (srp_rsa) -> true;
+ (_) -> false
+ end.
+%% Cert should be signed by RSA
+rsa_signed_suites(Ciphers, Version) ->
+ filter_suites(Ciphers, #{key_exchange_filters => [rsa_signed(Version)],
+ cipher_filters => [],
+ mac_filters => [],
+ prf_filters => []}).
+ecdsa_signed({3,N}) when N >= 3 ->
+ fun(ecdhe_ecdsa) -> true;
+ (_) -> false
+ end;
+ecdsa_signed(_) ->
+ fun(ecdhe_ecdsa) -> true;
+ (ecdh_ecdsa) -> true;
+ (_) -> false
+ end.
+
+%% Cert should be signed by ECDSA
+ecdsa_signed_suites(Ciphers, Version) ->
+ filter_suites(Ciphers, #{key_exchange_filters => [ecdsa_signed(Version)],
+ cipher_filters => [],
+ mac_filters => [],
+ prf_filters => []}).
+
+rsa_keyed(dhe_rsa) ->
+ true;
+rsa_keyed(rsa) ->
+ true;
+rsa_keyed(rsa_psk) ->
+ true;
+rsa_keyed(srp_rsa) ->
+ true;
+rsa_keyed(ecdhe_rsa) ->
+ true;
+rsa_keyed(_) ->
+ false.
-ecdsa_signed_suites() ->
- ecdh_ecdsa_suites() ++ ecdhe_ecdsa_suites().
+%% Certs key is an RSA key
+rsa_keyed_suites(Ciphers) ->
+ filter_suites(Ciphers, #{key_exchange_filters => [fun(Kex) -> rsa_keyed(Kex) end],
+ cipher_filters => [],
+ mac_filters => [],
+ prf_filters => []}).
+
+%% RSA Certs key can be used for encipherment
+rsa_suites_encipher(Ciphers) ->
+ filter_suites(Ciphers, #{key_exchange_filters => [fun(rsa) -> true;
+ (rsa_psk) -> true;
+ (_) -> false
+ end],
+ cipher_filters => [],
+ mac_filters => [],
+ prf_filters => []}).
+
+dss_keyed(dhe_dss) ->
+ true;
+dss_keyed(spr_dss) ->
+ true;
+dss_keyed(_) ->
+ false.
+
+%% Cert should be have DSS key (DSA)
+dss_keyed_suites(Ciphers) ->
+ filter_suites(Ciphers, #{key_exchange_filters => [fun(Kex) -> dss_keyed(Kex) end],
+ cipher_filters => [],
+ mac_filters => [],
+ prf_filters => []}).
+
+%% Cert should be signed by DSS (DSA)
+dsa_signed_suites(Ciphers, Version) ->
+ filter_suites(Ciphers, #{key_exchange_filters => [dsa_signed(Version)],
+ cipher_filters => [],
+ mac_filters => [],
+ prf_filters => []}).
+
+dsa_signed({3,N}) when N >= 3 ->
+ fun(dhe_dss) -> true;
+ (ecdhe_dss) -> true;
+ (_) -> false
+ end;
+dsa_signed(_) ->
+ fun(dhe_dss) -> true;
+ (ecdh_dss) -> true;
+ (ecdhe_dss) -> true;
+ (_) -> false
+ end.
-ecdh_suites() ->
- ecdh_rsa_suites() ++ ecdh_ecdsa_suites().
+ec_keyed(ecdh_ecdsa) ->
+ true;
+ec_keyed(ecdhe_ecdsa) ->
+ true;
+ec_keyed(ecdh_rsa) ->
+ true;
+ec_keyed(_) ->
+ false.
-ecdh_ecdsa_suites() ->
- [?TLS_ECDH_ECDSA_WITH_NULL_SHA,
- ?TLS_ECDH_ECDSA_WITH_RC4_128_SHA,
- ?TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA,
- ?TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA,
- ?TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA,
- ?TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256,
- ?TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384,
- ?TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256,
- ?TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384].
-
-ecdhe_ecdsa_suites() ->
- [?TLS_ECDHE_ECDSA_WITH_NULL_SHA,
- ?TLS_ECDHE_ECDSA_WITH_RC4_128_SHA,
- ?TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA,
- ?TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
- ?TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
- ?TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
- ?TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384,
- ?TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
- ?TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
- ?TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256].
-
-filter_keyuse(OtpCert, Ciphers, Suites, SignSuites) ->
+%% Certs key is an ECC key
+ec_keyed_suites(Ciphers) ->
+ filter_suites(Ciphers, #{key_exchange_filters => [fun(Kex) -> ec_keyed(Kex) end],
+ cipher_filters => [],
+ mac_filters => [],
+ prf_filters => []}).
+
+%% EC Certs key can be used for signing
+ecdsa_sign_suites(Ciphers)->
+ filter_suites(Ciphers, #{key_exchange_filters => [fun(ecdhe_ecdsa) -> true;
+ (_) -> false
+ end],
+ cipher_filters => [],
+ mac_filters => [],
+ prf_filters => []}).
+
+key_uses(OtpCert) ->
TBSCert = OtpCert#'OTPCertificate'.tbsCertificate,
TBSExtensions = TBSCert#'OTPTBSCertificate'.extensions,
Extensions = ssl_certificate:extensions_list(TBSExtensions),
case ssl_certificate:select_extension(?'id-ce-keyUsage', Extensions) of
undefined ->
- Ciphers;
- #'Extension'{extnValue = KeyUse} ->
- Result = filter_keyuse_suites(keyEncipherment,
- KeyUse, Ciphers, Suites),
- filter_keyuse_suites(digitalSignature,
- KeyUse, Result, SignSuites)
+ undefined;
+ #'Extension'{extnValue = KeyUses} ->
+ KeyUses
end.
filter_keyuse_suites(Use, KeyUse, CipherSuits, Suites) ->
diff --git a/lib/ssl/src/ssl_config.erl b/lib/ssl/src/ssl_config.erl
index 022fb7eac0..452a98e683 100644
--- a/lib/ssl/src/ssl_config.erl
+++ b/lib/ssl/src/ssl_config.erl
@@ -132,7 +132,13 @@ private_key(#'PrivateKeyInfo'{privateKeyAlgorithm =
#'PrivateKeyInfo_privateKeyAlgorithm'{algorithm = ?'id-dsa'},
privateKey = Key}) ->
public_key:der_decode('DSAPrivateKey', iolist_to_binary(Key));
-
+private_key(#'PrivateKeyInfo'{privateKeyAlgorithm =
+ #'PrivateKeyInfo_privateKeyAlgorithm'{algorithm = ?'id-ecPublicKey',
+ parameters = {asn1_OPENTYPE, Parameters}},
+ privateKey = Key}) ->
+ ECKey = public_key:der_decode('ECPrivateKey', iolist_to_binary(Key)),
+ ECParameters = public_key:der_decode('EcpkParameters', Parameters),
+ ECKey#'ECPrivateKey'{parameters = ECParameters};
private_key(Key) ->
Key.
diff --git a/lib/ssl/src/ssl_connection.erl b/lib/ssl/src/ssl_connection.erl
index f493c93726..ec034af44c 100644
--- a/lib/ssl/src/ssl_connection.erl
+++ b/lib/ssl/src/ssl_connection.erl
@@ -37,7 +37,9 @@
-include_lib("public_key/include/public_key.hrl").
%% Setup
--export([connect/8, ssl_accept/7, handshake/2, handshake/3,
+
+-export([connect/8, handshake/7, handshake/2, handshake/3,
+ handshake_continue/3, handshake_cancel/1,
socket_control/4, socket_control/5, start_or_recv_cancel_timer/2]).
%% User Events
@@ -57,11 +59,11 @@
%% Help functions for tls|dtls_connection.erl
-export([handle_session/7, ssl_config/3,
- prepare_connection/2, hibernate_after/3]).
+ prepare_connection/2, hibernate_after/3, map_extensions/1]).
%% General gen_statem state functions with extra callback argument
%% to determine if it is an SSL/TLS or DTLS gen_statem machine
--export([init/4, error/4, hello/4, abbreviated/4, certify/4, cipher/4,
+-export([init/4, error/4, hello/4, user_hello/4, abbreviated/4, certify/4, cipher/4,
connection/4, death_row/4, downgrade/4]).
%% gen_statem callbacks
@@ -93,7 +95,7 @@ connect(Connection, Host, Port, Socket, Options, User, CbInfo, Timeout) ->
{error, ssl_not_started}
end.
%%--------------------------------------------------------------------
--spec ssl_accept(tls_connection | dtls_connection,
+-spec handshake(tls_connection | dtls_connection,
inet:port_number(), port(),
{#ssl_options{}, #socket_options{}, undefined | pid()},
pid(), tuple(), timeout()) ->
@@ -102,7 +104,7 @@ connect(Connection, Host, Port, Socket, Options, User, CbInfo, Timeout) ->
%% Description: Performs accept on an ssl listen socket. e.i. performs
%% ssl handshake.
%%--------------------------------------------------------------------
-ssl_accept(Connection, Port, Socket, Opts, User, CbInfo, Timeout) ->
+handshake(Connection, Port, Socket, Opts, User, CbInfo, Timeout) ->
try Connection:start_fsm(server, "localhost", Port, Socket, Opts, User,
CbInfo, Timeout)
catch
@@ -111,32 +113,60 @@ ssl_accept(Connection, Port, Socket, Opts, User, CbInfo, Timeout) ->
end.
%%--------------------------------------------------------------------
--spec handshake(#sslsocket{}, timeout()) -> ok | {error, reason()}.
+-spec handshake(#sslsocket{}, timeout()) -> {ok, #sslsocket{}} |
+ {ok, #sslsocket{}, map()}| {error, reason()}.
%%
%% Description: Starts ssl handshake.
%%--------------------------------------------------------------------
-handshake(#sslsocket{pid = Pid}, Timeout) ->
+handshake(#sslsocket{pid = Pid} = Socket, Timeout) ->
case call(Pid, {start, Timeout}) of
connected ->
- ok;
+ {ok, Socket};
+ {ok, Ext} ->
+ {ok, Socket, Ext};
Error ->
Error
end.
%%--------------------------------------------------------------------
-spec handshake(#sslsocket{}, {#ssl_options{},#socket_options{}},
- timeout()) -> ok | {error, reason()}.
+ timeout()) -> {ok, #sslsocket{}} | {error, reason()}.
%%
%% Description: Starts ssl handshake with some new options
%%--------------------------------------------------------------------
-handshake(#sslsocket{pid = Pid}, SslOptions, Timeout) ->
+handshake(#sslsocket{pid = Pid} = Socket, SslOptions, Timeout) ->
case call(Pid, {start, SslOptions, Timeout}) of
connected ->
- ok;
+ {ok, Socket};
Error ->
Error
end.
+%%--------------------------------------------------------------------
+-spec handshake_continue(#sslsocket{}, [ssl_option()],
+ timeout()) -> {ok, #sslsocket{}}| {error, reason()}.
+%%
+%% Description: Continues handshake with new options
+%%--------------------------------------------------------------------
+handshake_continue(#sslsocket{pid = Pid} = Socket, SslOptions, Timeout) ->
+ case call(Pid, {handshake_continue, SslOptions, Timeout}) of
+ connected ->
+ {ok, Socket};
+ Error ->
+ Error
+ end.
+%%--------------------------------------------------------------------
+-spec handshake_cancel(#sslsocket{}) -> ok | {error, reason()}.
+%%
+%% Description: Cancels connection
+%%--------------------------------------------------------------------
+handshake_cancel(#sslsocket{pid = Pid}) ->
+ case call(Pid, cancel) of
+ closed ->
+ ok;
+ Error ->
+ Error
+ end.
%--------------------------------------------------------------------
-spec socket_control(tls_connection | dtls_connection, port(), pid(), atom()) ->
{ok, #sslsocket{}} | {error, reason()}.
@@ -527,6 +557,9 @@ handle_session(#server_hello{cipher_suite = CipherSuite,
-spec ssl_config(#ssl_options{}, client | server, #state{}) -> #state{}.
%%--------------------------------------------------------------------
ssl_config(Opts, Role, State) ->
+ ssl_config(Opts, Role, State, new).
+
+ssl_config(Opts, Role, State0, Type) ->
{ok, #{cert_db_ref := Ref,
cert_db_handle := CertDbHandle,
fileref_db_handle := FileRefHandle,
@@ -536,20 +569,26 @@ ssl_config(Opts, Role, State) ->
dh_params := DHParams,
own_certificate := OwnCert}} =
ssl_config:init(Opts, Role),
- Handshake = ssl_handshake:init_handshake_history(),
TimeStamp = erlang:monotonic_time(),
- Session = State#state.session,
- State#state{tls_handshake_history = Handshake,
- session = Session#session{own_certificate = OwnCert,
- time_stamp = TimeStamp},
- file_ref_db = FileRefHandle,
- cert_db_ref = Ref,
- cert_db = CertDbHandle,
- crl_db = CRLDbHandle,
- session_cache = CacheHandle,
- private_key = Key,
- diffie_hellman_params = DHParams,
- ssl_options = Opts}.
+ Session = State0#state.session,
+ State = State0#state{session = Session#session{own_certificate = OwnCert,
+ time_stamp = TimeStamp},
+ file_ref_db = FileRefHandle,
+ cert_db_ref = Ref,
+ cert_db = CertDbHandle,
+ crl_db = CRLDbHandle,
+ session_cache = CacheHandle,
+ private_key = Key,
+ diffie_hellman_params = DHParams,
+ ssl_options = Opts},
+ case Type of
+ new ->
+ Handshake = ssl_handshake:init_handshake_history(),
+ State#state{tls_handshake_history = Handshake};
+ continue ->
+ State
+ end.
+
%%====================================================================
%% gen_statem general state functions with connection cb argument
@@ -579,7 +618,8 @@ init({call, From}, {start, {Opts, EmOpts}, Timeout},
end,
State = ssl_config(SslOpts, Role, State0),
init({call, From}, {start, Timeout},
- State#state{ssl_options = SslOpts, socket_options = new_emulated(EmOpts, SockOpts)}, Connection)
+ State#state{ssl_options = SslOpts,
+ socket_options = new_emulated(EmOpts, SockOpts)}, Connection)
catch throw:Error ->
stop_and_reply(normal, {reply, From, {error, Error}}, State0)
end;
@@ -612,6 +652,23 @@ hello(info, Msg, State, _) ->
hello(Type, Msg, State, Connection) ->
handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection).
+user_hello({call, From}, cancel, #state{negotiated_version = Version} = State, _) ->
+ gen_statem:reply(From, ok),
+ handle_own_alert(?ALERT_REC(?FATAL, ?USER_CANCELED, user_canceled),
+ Version, ?FUNCTION_NAME, State);
+user_hello({call, From}, {handshake_continue, NewOptions, Timeout}, #state{hello = Hello,
+ role = Role,
+ start_or_recv_from = RecvFrom,
+ ssl_options = Options0} = State0, _Connection) ->
+ Timer = start_or_recv_cancel_timer(Timeout, RecvFrom),
+ Options = ssl:handle_options(NewOptions, Options0#ssl_options{handshake = full}),
+ State = ssl_config(Options, Role, State0, continue),
+ {next_state, hello, State#state{start_or_recv_from = From,
+ timer = Timer},
+ [{next_event, internal, Hello}]};
+user_hello(_, _, _, _) ->
+ {keep_state_and_data, [postpone]}.
+
%%--------------------------------------------------------------------
-spec abbreviated(gen_statem:event_type(),
#hello_request{} | #finished{} | term(),
@@ -770,6 +827,14 @@ certify(internal, #certificate_request{},
Alg == srp_dss; Alg == srp_rsa; Alg == srp_anon ->
handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE),
Version, ?FUNCTION_NAME, State);
+certify(internal, #certificate_request{},
+ #state{session = #session{own_certificate = undefined},
+ role = client} = State0, Connection) ->
+ %% The client does not have a certificate and will send an empty reply, the server may fail
+ %% or accept the connection by its own preference. No signature algorihms needed as there is
+ %% no certificate to verify.
+ {Record, State} = Connection:next_record(State0),
+ Connection:next_event(?FUNCTION_NAME, Record, State#state{client_certificate_requested = true});
certify(internal, #certificate_request{} = CertRequest,
#state{session = #session{own_certificate = Cert},
role = client,
@@ -1407,7 +1472,7 @@ connection_info(#state{sni_hostname = SNIHostname,
RecordCB = record_cb(Connection),
CipherSuiteDef = #{key_exchange := KexAlg} = ssl_cipher:suite_definition(CipherSuite),
IsNamedCurveSuite = lists:member(KexAlg,
- [ecdh_ecdsa, ecdhe_ecdsa, ecdh_anon]),
+ [ecdh_ecdsa, ecdhe_ecdsa, ecdh_rsa, ecdh_anon]),
CurveInfo = case ECCCurve of
{namedCurve, Curve} when IsNamedCurveSuite ->
[{ecc, {named_curve, pubkey_cert_records:namedCurves(Curve)}}];
@@ -1507,11 +1572,14 @@ handle_peer_cert(Role, PeerCert, PublicKeyInfo,
handle_peer_cert_key(client, _,
{?'id-ecPublicKey', #'ECPoint'{point = _ECPoint} = PublicKey,
PublicKeyParams},
- KeyAlg, State) when KeyAlg == ecdh_rsa;
- KeyAlg == ecdh_ecdsa ->
+ KeyAlg, #state{session = Session} = State) when KeyAlg == ecdh_rsa;
+ KeyAlg == ecdh_ecdsa ->
ECDHKey = public_key:generate_key(PublicKeyParams),
+ {namedCurve, Oid} = PublicKeyParams,
+ Curve = pubkey_cert_records:namedCurves(Oid), %% Need API function
PremasterSecret = ssl_handshake:premaster_secret(PublicKey, ECDHKey),
- master_secret(PremasterSecret, State#state{diffie_hellman_keys = ECDHKey});
+ master_secret(PremasterSecret, State#state{diffie_hellman_keys = ECDHKey,
+ session = Session#session{ecc = {named_curve, Curve}}});
%% We do currently not support cipher suites that use fixed DH.
%% If we want to implement that the following clause can be used
%% to extract DH parameters form cert.
@@ -2285,7 +2353,24 @@ hibernate_after(connection = StateName,
{next_state, StateName, State, [{timeout, HibernateAfter, hibernate} | Actions]};
hibernate_after(StateName, State, Actions) ->
{next_state, StateName, State, Actions}.
-
+
+map_extensions(#hello_extensions{renegotiation_info = RenegotiationInfo,
+ signature_algs = SigAlg,
+ alpn = Alpn,
+ next_protocol_negotiation = Next,
+ srp = SRP,
+ ec_point_formats = ECPointFmt,
+ elliptic_curves = ECCCurves,
+ sni = SNI}) ->
+ #{renegotiation_info => ssl_handshake:extension_value(RenegotiationInfo),
+ signature_algs => ssl_handshake:extension_value(SigAlg),
+ alpn => ssl_handshake:extension_value(Alpn),
+ srp => ssl_handshake:extension_value(SRP),
+ next_protocol => ssl_handshake:extension_value(Next),
+ ec_point_formats => ssl_handshake:extension_value(ECPointFmt),
+ elliptic_curves => ssl_handshake:extension_value(ECCCurves),
+ sni => ssl_handshake:extension_value(SNI)}.
+
terminate_alert(normal, Version, ConnectionStates, Connection) ->
Connection:encode_alert(?ALERT_REC(?WARNING, ?CLOSE_NOTIFY),
Version, ConnectionStates);
diff --git a/lib/ssl/src/ssl_connection.hrl b/lib/ssl/src/ssl_connection.hrl
index f9d2149170..a61ff747ae 100644
--- a/lib/ssl/src/ssl_connection.hrl
+++ b/lib/ssl/src/ssl_connection.hrl
@@ -61,7 +61,7 @@
client_certificate_requested = false :: boolean(),
key_algorithm :: ssl_cipher:key_algo(),
hashsign_algorithm = {undefined, undefined},
- cert_hashsign_algorithm,
+ cert_hashsign_algorithm = {undefined, undefined},
public_key_info :: ssl_handshake:public_key_info() | 'undefined',
private_key :: public_key:private_key() | secret_printout() | 'undefined',
diffie_hellman_params:: #'DHParameter'{} | undefined | secret_printout(),
@@ -77,7 +77,8 @@
renegotiation :: undefined | {boolean(), From::term() | internal | peer},
start_or_recv_from :: term(),
timer :: undefined | reference(), % start_or_recive_timer
- %%send_queue :: queue:queue(),
+ %%send_queue :: queue:queue(),
+ hello, %%:: #client_hello{} | #server_hello{},
terminated = false ::boolean(),
allow_renegotiate = true ::boolean(),
expecting_next_protocol_negotiation = false ::boolean(),
@@ -88,11 +89,11 @@
sni_hostname = undefined,
downgrade,
flight_buffer = [] :: list() | map(), %% Buffer of TLS/DTLS records, used during the TLS handshake
- %% to when possible pack more than on TLS record into the
- %% underlaying packet format. Introduced by DTLS - RFC 4347.
- %% The mecahnism is also usefull in TLS although we do not
- %% need to worry about packet loss in TLS. In DTLS we need to track DTLS handshake seqnr
- flight_state = reliable, %% reliable | {retransmit, integer()}| {waiting, ref(), integer()} - last two is used in DTLS over udp.
+ %% to when possible pack more than one TLS record into the
+ %% underlaying packet format. Introduced by DTLS - RFC 4347.
+ %% The mecahnism is also usefull in TLS although we do not
+ %% need to worry about packet loss in TLS. In DTLS we need to track DTLS handshake seqnr
+ flight_state = reliable, %% reliable | {retransmit, integer()}| {waiting, ref(), integer()} - last two is used in DTLS over udp.
protocol_specific = #{} :: map()
}).
-define(DEFAULT_DIFFIE_HELLMAN_PARAMS,
diff --git a/lib/ssl/src/ssl_handshake.erl b/lib/ssl/src/ssl_handshake.erl
index e1f813ea95..090e7b69b7 100644
--- a/lib/ssl/src/ssl_handshake.erl
+++ b/lib/ssl/src/ssl_handshake.erl
@@ -53,7 +53,7 @@
-export([certify/7, certificate_verify/6, verify_signature/5,
master_secret/4, server_key_exchange_hash/2, verify_connection/6,
init_handshake_history/0, update_handshake_history/2, verify_server_key/5,
- select_version/3
+ select_version/3, extension_value/1
]).
%% Encode
@@ -139,8 +139,8 @@ certificate(OwnCert, CertDbHandle, CertDbRef, server) ->
case ssl_certificate:certificate_chain(OwnCert, CertDbHandle, CertDbRef) of
{ok, _, Chain} ->
#certificate{asn1_certificates = Chain};
- {error, _} ->
- ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, server_has_no_suitable_certificates)
+ {error, Error} ->
+ ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, {server_has_no_suitable_certificates, Error})
end.
%%--------------------------------------------------------------------
@@ -772,11 +772,12 @@ available_suites(UserSuites, Version) ->
lists:filtermap(fun(Suite) -> lists:member(Suite, VersionSuites) end, UserSuites).
available_suites(ServerCert, UserSuites, Version, undefined, Curve) ->
- ssl_cipher:filter(ServerCert, available_suites(UserSuites, Version))
- -- unavailable_ecc_suites(Curve);
+ Suites = ssl_cipher:filter(ServerCert, available_suites(UserSuites, Version), Version),
+ filter_unavailable_ecc_suites(Curve, Suites);
available_suites(ServerCert, UserSuites, Version, HashSigns, Curve) ->
Suites = available_suites(ServerCert, UserSuites, Version, undefined, Curve),
- filter_hashsigns(Suites, [ssl_cipher:suite_definition(Suite) || Suite <- Suites], HashSigns, []).
+ filter_hashsigns(Suites, [ssl_cipher:suite_definition(Suite) || Suite <- Suites], HashSigns,
+ Version, []).
available_signature_algs(undefined, _) ->
undefined;
@@ -814,7 +815,7 @@ prf({3,0}, _, _, _, _, _) ->
prf({3,_N}, PRFAlgo, Secret, Label, Seed, WantedLength) ->
{ok, tls_v1:prf(PRFAlgo, Secret, Label, Seed, WantedLength)}.
-select_session(SuggestedSessionId, CipherSuites, HashSigns, Compressions, Port, #session{ecc = ECCCurve} =
+select_session(SuggestedSessionId, CipherSuites, HashSigns, Compressions, Port, #session{ecc = ECCCurve0} =
Session, Version,
#ssl_options{ciphers = UserSuites, honor_cipher_order = HonorCipherOrder} = SslOpts,
Cache, CacheCb, Cert) ->
@@ -823,10 +824,12 @@ select_session(SuggestedSessionId, CipherSuites, HashSigns, Compressions, Port,
Cache, CacheCb),
case Resumed of
undefined ->
- Suites = available_suites(Cert, UserSuites, Version, HashSigns, ECCCurve),
- CipherSuite = select_cipher_suite(CipherSuites, Suites, HonorCipherOrder),
+ Suites = available_suites(Cert, UserSuites, Version, HashSigns, ECCCurve0),
+ CipherSuite0 = select_cipher_suite(CipherSuites, Suites, HonorCipherOrder),
+ {ECCCurve, CipherSuite} = cert_curve(Cert, ECCCurve0, CipherSuite0),
Compression = select_compression(Compressions),
{new, Session#session{session_id = SessionId,
+ ecc = ECCCurve,
cipher_suite = CipherSuite,
compression_method = Compression}};
_ ->
@@ -1066,11 +1069,11 @@ select_hashsign(#hash_sign_algos{hash_sign_algos = HashSigns}, Cert, KeyExAlgo,
TBSCert#'OTPTBSCertificate'.subjectPublicKeyInfo,
Sign = sign_algo(SignAlgo),
- SubSing = sign_algo(SubjAlgo),
-
- case lists:filter(fun({_, S} = Algos) when S == Sign ->
+ SubSign = sign_algo(SubjAlgo),
+
+ case lists:filter(fun({_, S} = Algos) when S == SubSign ->
is_acceptable_hash_sign(Algos, Sign,
- SubSing, KeyExAlgo, SupportedHashSigns);
+ SubSign, KeyExAlgo, SupportedHashSigns);
(_) ->
false
end, HashSigns) of
@@ -1091,12 +1094,6 @@ select_hashsign(_, Cert, _, _, Version) ->
%%
%% Description: Handles signature algorithms selection for certificate requests (client)
%%--------------------------------------------------------------------
-select_hashsign(#certificate_request{}, undefined, _, {Major, Minor}) when Major >= 3 andalso Minor >= 3->
- %% There client does not have a certificate and will send an empty reply, the server may fail
- %% or accept the connection by its own preference. No signature algorihms needed as there is
- %% no certificate to verify.
- {undefined, undefined};
-
select_hashsign(#certificate_request{hashsign_algorithms = #hash_sign_algos{hash_sign_algos = HashSigns},
certificate_types = Types}, Cert, SupportedHashSigns,
{Major, Minor}) when Major >= 3 andalso Minor >= 3->
@@ -1166,6 +1163,25 @@ srp_user(#ssl_options{srp_identity = {UserName, _}}) ->
srp_user(_) ->
undefined.
+extension_value(undefined) ->
+ undefined;
+extension_value(#sni{hostname = HostName}) ->
+ HostName;
+extension_value(#ec_point_formats{ec_point_format_list = List}) ->
+ List;
+extension_value(#elliptic_curves{elliptic_curve_list = List}) ->
+ List;
+extension_value(#hash_sign_algos{hash_sign_algos = Algos}) ->
+ Algos;
+extension_value(#alpn{extension_data = Data}) ->
+ Data;
+extension_value(#next_protocol_negotiation{extension_data = Data}) ->
+ Data;
+extension_value(#srp{username = Name}) ->
+ Name;
+extension_value(#renegotiation_info{renegotiated_connection = Data}) ->
+ Data.
+
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
@@ -2062,25 +2078,26 @@ handle_psk_identity(_PSKIdentity, LookupFun)
handle_psk_identity(PSKIdentity, {Fun, UserState}) ->
Fun(psk, PSKIdentity, UserState).
-filter_hashsigns([], [], _, Acc) ->
- lists:reverse(Acc);
-filter_hashsigns([Suite | Suites], [#{key_exchange := KeyExchange} | Algos], HashSigns,
- Acc) when KeyExchange == dhe_ecdsa;
- KeyExchange == ecdhe_ecdsa ->
- do_filter_hashsigns(ecdsa, Suite, Suites, Algos, HashSigns, Acc);
-filter_hashsigns([Suite | Suites], [#{key_exchange := KeyExchange} | Algos], HashSigns,
+filter_hashsigns([], [], _, _, Acc) ->
+ lists:reverse(Acc);
+filter_hashsigns([Suite | Suites], [#{key_exchange := KeyExchange} | Algos], HashSigns, Version,
+ Acc) when KeyExchange == dhe_ecdsa;
+ KeyExchange == ecdhe_ecdsa ->
+ do_filter_hashsigns(ecdsa, Suite, Suites, Algos, HashSigns, Version, Acc);
+filter_hashsigns([Suite | Suites], [#{key_exchange := KeyExchange} | Algos], HashSigns, Version,
Acc) when KeyExchange == rsa;
KeyExchange == dhe_rsa;
KeyExchange == ecdhe_rsa;
KeyExchange == srp_rsa;
KeyExchange == rsa_psk ->
- do_filter_hashsigns(rsa, Suite, Suites, Algos, HashSigns, Acc);
-filter_hashsigns([Suite | Suites], [#{key_exchange := KeyExchange} | Algos], HashSigns, Acc) when
+ do_filter_hashsigns(rsa, Suite, Suites, Algos, HashSigns, Version, Acc);
+filter_hashsigns([Suite | Suites], [#{key_exchange := KeyExchange} | Algos], HashSigns, Version, Acc) when
KeyExchange == dhe_dss;
KeyExchange == srp_dss ->
- do_filter_hashsigns(dsa, Suite, Suites, Algos, HashSigns, Acc);
-filter_hashsigns([Suite | Suites], [#{key_exchange := KeyExchange} | Algos], HashSigns, Acc) when
+ do_filter_hashsigns(dsa, Suite, Suites, Algos, HashSigns, Version, Acc);
+filter_hashsigns([Suite | Suites], [#{key_exchange := KeyExchange} | Algos], HashSigns, Verion,
+ Acc) when
KeyExchange == dh_dss;
KeyExchange == dh_rsa;
KeyExchange == dh_ecdsa;
@@ -2089,8 +2106,9 @@ filter_hashsigns([Suite | Suites], [#{key_exchange := KeyExchange} | Algos], Has
%% Fixed DH certificates MAY be signed with any hash/signature
%% algorithm pair appearing in the hash_sign extension. The names
%% DH_DSS, DH_RSA, ECDH_ECDSA, and ECDH_RSA are historical.
- filter_hashsigns(Suites, Algos, HashSigns, [Suite| Acc]);
-filter_hashsigns([Suite | Suites], [#{key_exchange := KeyExchange} | Algos], HashSigns, Acc) when
+ filter_hashsigns(Suites, Algos, HashSigns, Verion, [Suite| Acc]);
+filter_hashsigns([Suite | Suites], [#{key_exchange := KeyExchange} | Algos], HashSigns, Version,
+ Acc) when
KeyExchange == dh_anon;
KeyExchange == ecdh_anon;
KeyExchange == srp_anon;
@@ -2098,20 +2116,28 @@ filter_hashsigns([Suite | Suites], [#{key_exchange := KeyExchange} | Algos], Has
KeyExchange == dhe_psk;
KeyExchange == ecdhe_psk ->
%% In this case hashsigns is not used as the kexchange is anonaymous
- filter_hashsigns(Suites, Algos, HashSigns, [Suite| Acc]).
+ filter_hashsigns(Suites, Algos, HashSigns, Version, [Suite| Acc]).
-do_filter_hashsigns(SignAlgo, Suite, Suites, Algos, HashSigns, Acc) ->
+do_filter_hashsigns(SignAlgo, Suite, Suites, Algos, HashSigns, Version, Acc) ->
case lists:keymember(SignAlgo, 2, HashSigns) of
true ->
- filter_hashsigns(Suites, Algos, HashSigns, [Suite| Acc]);
+ filter_hashsigns(Suites, Algos, HashSigns, Version, [Suite| Acc]);
false ->
- filter_hashsigns(Suites, Algos, HashSigns, Acc)
+ filter_hashsigns(Suites, Algos, HashSigns, Version, Acc)
end.
-unavailable_ecc_suites(no_curve) ->
- ssl_cipher:ec_keyed_suites();
-unavailable_ecc_suites(_) ->
- [].
+filter_unavailable_ecc_suites(no_curve, Suites) ->
+ ECCSuites = ssl_cipher:filter_suites(Suites, #{key_exchange_filters => [fun(ecdh_ecdsa) -> true;
+ (ecdhe_ecdsa) -> true;
+ (ecdh_rsa) -> true;
+ (_) -> false
+ end],
+ cipher_filters => [],
+ mac_filters => [],
+ prf_filters => []}),
+ Suites -- ECCSuites;
+filter_unavailable_ecc_suites(_, Suites) ->
+ Suites.
%%-------------Extension handling --------------------------------
handle_renegotiation_extension(Role, RecordCB, Version, Info, Random, NegotiatedCipherSuite,
@@ -2207,8 +2233,11 @@ sign_algo(Alg) ->
is_acceptable_hash_sign(Algos, _, _, KeyExAlgo, SupportedHashSigns) when
KeyExAlgo == dh_dss;
KeyExAlgo == dh_rsa;
- KeyExAlgo == dh_ecdsa ->
- %% dh_* could be called only dh in TLS-1.2
+ KeyExAlgo == ecdh_ecdsa;
+ KeyExAlgo == ecdh_rsa;
+ KeyExAlgo == ecdh_ecdsa
+ ->
+ %% *dh_* could be called only *dh in TLS-1.2
is_acceptable_hash_sign(Algos, SupportedHashSigns);
is_acceptable_hash_sign(Algos, rsa, ecdsa, ecdh_rsa, SupportedHashSigns) ->
is_acceptable_hash_sign(Algos, SupportedHashSigns);
@@ -2423,3 +2452,27 @@ handle_renegotiation_info(_RecordCB, ConnectionStates, SecureRenegotation) ->
{false, false} ->
{ok, ConnectionStates}
end.
+
+cert_curve(_, _, no_suite) ->
+ {no_curve, no_suite};
+cert_curve(Cert, ECCCurve0, CipherSuite) ->
+ case ssl_cipher:suite_definition(CipherSuite) of
+ #{key_exchange := Kex} when Kex == ecdh_ecdsa;
+ Kex == ecdh_rsa ->
+ OtpCert = public_key:pkix_decode_cert(Cert, otp),
+ TBSCert = OtpCert#'OTPCertificate'.tbsCertificate,
+ #'OTPSubjectPublicKeyInfo'{algorithm = AlgInfo}
+ = TBSCert#'OTPTBSCertificate'.subjectPublicKeyInfo,
+ {namedCurve, Oid} = AlgInfo#'PublicKeyAlgorithm'.parameters,
+ try pubkey_cert_records:namedCurves(Oid) of
+ Curve ->
+ {{named_curve, Curve}, CipherSuite}
+ catch
+ _:_ ->
+ {no_curve, no_suite}
+ end;
+ _ ->
+ {ECCCurve0, CipherSuite}
+ end.
+
+
diff --git a/lib/ssl/src/ssl_internal.hrl b/lib/ssl/src/ssl_internal.hrl
index d354910f33..5df00de0e5 100644
--- a/lib/ssl/src/ssl_internal.hrl
+++ b/lib/ssl/src/ssl_internal.hrl
@@ -144,7 +144,8 @@
signature_algs,
eccs,
honor_ecc_order :: boolean(),
- max_handshake_size :: integer()
+ max_handshake_size :: integer(),
+ handshake
}).
-record(socket_options,
diff --git a/lib/ssl/src/tls_connection.erl b/lib/ssl/src/tls_connection.erl
index c35378f18f..ef84c5320e 100644
--- a/lib/ssl/src/tls_connection.erl
+++ b/lib/ssl/src/tls_connection.erl
@@ -62,7 +62,7 @@
%% gen_statem state functions
-export([init/3, error/3, downgrade/3, %% Initiation and take down states
- hello/3, certify/3, cipher/3, abbreviated/3, %% Handshake states
+ hello/3, user_hello/3, certify/3, cipher/3, abbreviated/3, %% Handshake states
connection/3, death_row/3]).
%% gen_statem callbacks
-export([callback_mode/0, terminate/3, code_change/4, format_status/2]).
@@ -80,8 +80,7 @@ start_fsm(Role, Host, Port, Socket, {#ssl_options{erl_dist = false},_, Tracker}
{ok, Pid} = tls_connection_sup:start_child([Role, Host, Port, Socket,
Opts, User, CbInfo]),
{ok, SslSocket} = ssl_connection:socket_control(?MODULE, Socket, Pid, CbModule, Tracker),
- ok = ssl_connection:handshake(SslSocket, Timeout),
- {ok, SslSocket}
+ ssl_connection:handshake(SslSocket, Timeout)
catch
error:{badmatch, {error, _} = Error} ->
Error
@@ -94,8 +93,7 @@ start_fsm(Role, Host, Port, Socket, {#ssl_options{erl_dist = true},_, Tracker} =
{ok, Pid} = tls_connection_sup:start_child_dist([Role, Host, Port, Socket,
Opts, User, CbInfo]),
{ok, SslSocket} = ssl_connection:socket_control(?MODULE, Socket, Pid, CbModule, Tracker),
- ok = ssl_connection:handshake(SslSocket, Timeout),
- {ok, SslSocket}
+ ssl_connection:handshake(SslSocket, Timeout)
catch
error:{badmatch, {error, _} = Error} ->
Error
@@ -454,6 +452,16 @@ error(_, _, _) ->
#state{}) ->
gen_statem:state_function_result().
%%--------------------------------------------------------------------
+hello(internal, #client_hello{extensions = Extensions} = Hello, #state{ssl_options = #ssl_options{handshake = hello},
+ start_or_recv_from = From} = State) ->
+ {next_state, user_hello, State#state{start_or_recv_from = undefined,
+ hello = Hello},
+ [{reply, From, {ok, ssl_connection:map_extensions(Extensions)}}]};
+hello(internal, #server_hello{extensions = Extensions} = Hello, #state{ssl_options = #ssl_options{handshake = hello},
+ start_or_recv_from = From} = State) ->
+ {next_state, user_hello, State#state{start_or_recv_from = undefined,
+ hello = Hello},
+ [{reply, From, {ok, ssl_connection:map_extensions(Extensions)}}]};
hello(internal, #client_hello{client_version = ClientVersion} = Hello,
#state{connection_states = ConnectionStates0,
port = Port, session = #session{own_certificate = Cert} = Session0,
@@ -463,7 +471,6 @@ hello(internal, #client_hello{client_version = ClientVersion} = Hello,
negotiated_protocol = CurrentProtocol,
key_algorithm = KeyExAlg,
ssl_options = SslOpts} = State) ->
-
case tls_handshake:hello(Hello, SslOpts, {Port, Session0, Cache, CacheCb,
ConnectionStates0, Cert, KeyExAlg}, Renegotiation) of
#alert{} = Alert ->
@@ -482,7 +489,7 @@ hello(internal, #client_hello{client_version = ClientVersion} = Hello,
session = Session,
negotiated_protocol = Protocol})
end;
-hello(internal, #server_hello{} = Hello,
+hello(internal, #server_hello{} = Hello,
#state{connection_states = ConnectionStates0,
negotiated_version = ReqVersion,
role = client,
@@ -500,6 +507,9 @@ hello(info, Event, State) ->
hello(Type, Event, State) ->
gen_handshake(?FUNCTION_NAME, Type, Event, State).
+user_hello(Type, Event, State) ->
+ gen_handshake(?FUNCTION_NAME, Type, Event, State).
+
%%--------------------------------------------------------------------
-spec abbreviated(gen_statem:event_type(), term(), #state{}) ->
gen_statem:state_function_result().
@@ -746,7 +756,7 @@ gen_handshake(StateName, Type, Event,
malformed_handshake_data),
Version, StateName, State)
end.
-
+
gen_info(Event, connection = StateName, #state{negotiated_version = Version} = State) ->
try handle_info(Event, StateName, State) of
Result ->