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.erl3
-rw-r--r--lib/ssl/src/inet_tls_dist.erl3
-rw-r--r--lib/ssl/src/ssl.app.src2
-rw-r--r--lib/ssl/src/ssl.erl112
-rw-r--r--lib/ssl/src/ssl_alert.erl14
-rw-r--r--lib/ssl/src/ssl_alert.hrl4
-rw-r--r--lib/ssl/src/ssl_cipher.erl8
-rw-r--r--lib/ssl/src/ssl_connection.erl118
-rw-r--r--lib/ssl/src/ssl_crl.erl16
-rw-r--r--lib/ssl/src/ssl_handshake.erl101
-rw-r--r--lib/ssl/src/ssl_handshake.hrl3
-rw-r--r--lib/ssl/src/ssl_internal.hrl6
-rw-r--r--lib/ssl/src/ssl_manager.erl11
-rw-r--r--lib/ssl/src/ssl_record.erl43
-rw-r--r--lib/ssl/src/ssl_record.hrl4
-rw-r--r--lib/ssl/src/ssl_socket.erl7
-rw-r--r--lib/ssl/src/ssl_tls_dist_proxy.erl33
-rw-r--r--lib/ssl/src/tls_connection.erl103
-rw-r--r--lib/ssl/src/tls_handshake.erl50
19 files changed, 381 insertions, 260 deletions
diff --git a/lib/ssl/src/dtls_connection.erl b/lib/ssl/src/dtls_connection.erl
index 82d6faee42..60a61bc901 100644
--- a/lib/ssl/src/dtls_connection.erl
+++ b/lib/ssl/src/dtls_connection.erl
@@ -416,7 +416,8 @@ encode_change_cipher(#change_cipher_spec{}, Version, ConnectionStates) ->
initial_state(Role, Host, Port, Socket, {SSLOptions, SocketOptions}, User,
{CbModule, DataTag, CloseTag, ErrorTag}) ->
- ConnectionStates = ssl_record:init_connection_states(Role),
+ #ssl_options{beast_mitigation = BeastMitigation} = SSLOptions,
+ ConnectionStates = ssl_record:init_connection_states(Role, BeastMitigation),
SessionCacheCb = case application:get_env(ssl, session_cb) of
{ok, Cb} when is_atom(Cb) ->
diff --git a/lib/ssl/src/inet_tls_dist.erl b/lib/ssl/src/inet_tls_dist.erl
index 3481e89af0..0da4b3587f 100644
--- a/lib/ssl/src/inet_tls_dist.erl
+++ b/lib/ssl/src/inet_tls_dist.erl
@@ -87,7 +87,8 @@ do_setup(Driver, Kernel, Node, Type, MyNode, LongOrShortNames, SetupTime) ->
case inet:getaddr(Address, Driver:family()) of
{ok, Ip} ->
Timer = dist_util:start_timer(SetupTime),
- case erl_epmd:port_please(Name, Ip) of
+ ErlEpmd = net_kernel:epmd_module(),
+ case ErlEpmd:port_please(Name, Ip) of
{port, TcpPort, Version} ->
?trace("port_please(~p) -> version ~p~n",
[Node,Version]),
diff --git a/lib/ssl/src/ssl.app.src b/lib/ssl/src/ssl.app.src
index 1a2bf90ccf..937a3b1bd1 100644
--- a/lib/ssl/src/ssl.app.src
+++ b/lib/ssl/src/ssl.app.src
@@ -54,7 +54,7 @@
{applications, [crypto, public_key, kernel, stdlib]},
{env, []},
{mod, {ssl_app, []}},
- {runtime_dependencies, ["stdlib-2.0","public_key-1.0","kernel-3.0",
+ {runtime_dependencies, ["stdlib-3.0","public_key-1.0","kernel-3.0",
"erts-7.0","crypto-3.3", "inets-5.10.7"]}]}.
diff --git a/lib/ssl/src/ssl.erl b/lib/ssl/src/ssl.erl
index dbbb25025c..d2aeb3258f 100644
--- a/lib/ssl/src/ssl.erl
+++ b/lib/ssl/src/ssl.erl
@@ -34,7 +34,8 @@
listen/2, transport_accept/1, transport_accept/2,
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
+ close/1, close/2, shutdown/2, recv/2, recv/3, send/2,
+ getopts/2, setopts/2, getstat/1, getstat/2
]).
%% SSL/TLS protocol handling
-export([cipher_suites/0, cipher_suites/1,
@@ -97,7 +98,7 @@ connect(Socket, SslOptions) when is_port(Socket) ->
connect(Socket, SslOptions, infinity).
connect(Socket, SslOptions0, Timeout) when is_port(Socket),
- (is_integer(Timeout) andalso Timeout > 0) or (Timeout == infinity) ->
+ (is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity) ->
{Transport,_,_,_} = proplists:get_value(cb_info, SslOptions0,
{gen_tcp, tcp, tcp_closed, tcp_error}),
EmulatedOptions = ssl_socket:emulated_options(),
@@ -123,7 +124,7 @@ connect(Socket, SslOptions0, Timeout) when is_port(Socket),
connect(Host, Port, Options) ->
connect(Host, Port, Options, infinity).
-connect(Host, Port, Options, Timeout) when (is_integer(Timeout) andalso Timeout > 0) or (Timeout == infinity) ->
+connect(Host, Port, Options, Timeout) when (is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity) ->
try handle_options(Options, client) of
{ok, Config} ->
do_connect(Host,Port,Config,Timeout)
@@ -173,7 +174,7 @@ transport_accept(#sslsocket{pid = {ListenSocket,
#config{transport_info = {Transport,_,_, _} =CbInfo,
connection_cb = ConnectionCb,
ssl = SslOpts,
- emulated = Tracker}}}, Timeout) when (is_integer(Timeout) andalso Timeout > 0) or (Timeout == infinity) ->
+ emulated = Tracker}}}, Timeout) when (is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity) ->
case Transport:accept(ListenSocket, Timeout) of
{ok, Socket} ->
{ok, EmOpts} = ssl_socket:get_emulated_opts(Tracker),
@@ -206,25 +207,25 @@ transport_accept(#sslsocket{pid = {ListenSocket,
ssl_accept(ListenSocket) ->
ssl_accept(ListenSocket, infinity).
-ssl_accept(#sslsocket{} = Socket, Timeout) when (is_integer(Timeout) andalso Timeout > 0) or (Timeout == infinity) ->
+ssl_accept(#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) when is_port(ListenSocket) ->
ssl_accept(ListenSocket, SslOptions, infinity).
-ssl_accept(#sslsocket{} = Socket, [], Timeout) when (is_integer(Timeout) andalso Timeout > 0) or (Timeout == infinity)->
+ssl_accept(#sslsocket{} = Socket, [], Timeout) when (is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity)->
ssl_accept(#sslsocket{} = Socket, Timeout);
-ssl_accept(#sslsocket{fd = {_, _, _, Tracker}} = Socket, SslOpts0, Timeout) when
- (is_integer(Timeout) andalso Timeout > 0) or (Timeout == infinity)->
- try
- {ok, EmOpts, InheritedSslOpts} = ssl_socket:get_all_opts(Tracker),
+ssl_accept(#sslsocket{fd = {_, _, _, Tracker}} = Socket, SslOpts0, Timeout) when
+ (is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity)->
+ try
+ {ok, EmOpts, InheritedSslOpts} = ssl_socket:get_all_opts(Tracker),
SslOpts = handle_options(SslOpts0, InheritedSslOpts),
ssl_connection:handshake(Socket, {SslOpts, emulated_socket_options(EmOpts, #socket_options{})}, Timeout)
catch
Error = {error, _Reason} -> Error
end;
ssl_accept(Socket, SslOptions, Timeout) when is_port(Socket),
- (is_integer(Timeout) andalso Timeout > 0) or (Timeout == infinity) ->
+ (is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity) ->
{Transport,_,_,_} =
proplists:get_value(cb_info, SslOptions, {gen_tcp, tcp, tcp_closed, tcp_error}),
EmulatedOptions = ssl_socket:emulated_options(),
@@ -252,17 +253,17 @@ close(#sslsocket{pid = {ListenSocket, #config{transport_info={Transport,_, _, _}
Transport:close(ListenSocket).
%%--------------------------------------------------------------------
--spec close(#sslsocket{}, integer() | {pid(), integer()}) -> term().
+-spec close(#sslsocket{}, timeout() | {pid(), integer()}) -> term().
%%
%% Description: Close an ssl connection
%%--------------------------------------------------------------------
-close(#sslsocket{pid = TLSPid},
- {Pid, Timeout} = DownGrade) when is_pid(TLSPid),
- is_pid(Pid),
- (is_integer(Timeout) andalso Timeout > 0) or (Timeout == infinity) ->
+close(#sslsocket{pid = TLSPid},
+ {Pid, Timeout} = DownGrade) when is_pid(TLSPid),
+ is_pid(Pid),
+ (is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity) ->
ssl_connection:close(TLSPid, {close, DownGrade});
-close(#sslsocket{pid = TLSPid}, Timeout) when is_pid(TLSPid),
- (is_integer(Timeout) andalso Timeout > 0) or (Timeout == infinity) ->
+close(#sslsocket{pid = TLSPid}, Timeout) when is_pid(TLSPid),
+ (is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity) ->
ssl_connection:close(TLSPid, {close, Timeout});
close(#sslsocket{pid = {ListenSocket, #config{transport_info={Transport,_, _, _}}}}, _) ->
Transport:close(ListenSocket).
@@ -286,7 +287,7 @@ send(#sslsocket{pid = {ListenSocket, #config{transport_info={Transport, _, _, _}
recv(Socket, Length) ->
recv(Socket, Length, infinity).
recv(#sslsocket{pid = Pid}, Length, Timeout) when is_pid(Pid),
- (is_integer(Timeout) andalso Timeout > 0) or (Timeout == infinity)->
+ (is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity)->
ssl_connection:recv(Pid, Length, Timeout);
recv(#sslsocket{pid = {Listen,
#config{transport_info = {Transport, _, _, _}}}}, _,_) when is_port(Listen)->
@@ -400,24 +401,23 @@ negotiated_next_protocol(Socket) ->
end.
%%--------------------------------------------------------------------
+-spec cipher_suites() -> [ssl_cipher:erl_cipher_suite()] | [string()].
+%%--------------------------------------------------------------------
+cipher_suites() ->
+ cipher_suites(erlang).
+%%--------------------------------------------------------------------
-spec cipher_suites(erlang | openssl | all) -> [ssl_cipher:erl_cipher_suite()] |
[string()].
%% Description: Returns all supported cipher suites.
%%--------------------------------------------------------------------
cipher_suites(erlang) ->
- Version = tls_record:highest_protocol_version([]),
- ssl_cipher:filter_suites([ssl_cipher:erl_suite_definition(S)
- || S <- ssl_cipher:suites(Version)]);
+ [ssl_cipher:erl_suite_definition(Suite) || Suite <- available_suites(default)];
+
cipher_suites(openssl) ->
- Version = tls_record:highest_protocol_version([]),
- [ssl_cipher:openssl_suite_name(S)
- || S <- ssl_cipher:filter_suites(ssl_cipher:suites(Version))];
+ [ssl_cipher:openssl_suite_name(Suite) || Suite <- available_suites(default)];
+
cipher_suites(all) ->
- Version = tls_record:highest_protocol_version([]),
- ssl_cipher:filter_suites([ssl_cipher:erl_suite_definition(S)
- || S <-ssl_cipher:all_suites(Version)]).
-cipher_suites() ->
- cipher_suites(erlang).
+ [ssl_cipher:erl_suite_definition(Suite) || Suite <- available_suites(all)].
%%--------------------------------------------------------------------
-spec getopts(#sslsocket{}, [gen_tcp:option_name()]) ->
@@ -470,6 +470,32 @@ setopts(#sslsocket{}, Options) ->
{error, {options,{not_a_proplist, Options}}}.
%%---------------------------------------------------------------
+-spec getstat(Socket) ->
+ {ok, OptionValues} | {error, inet:posix()} when
+ Socket :: #sslsocket{},
+ OptionValues :: [{inet:stat_option(), integer()}].
+%%
+%% Description: Get all statistic options for a socket.
+%%--------------------------------------------------------------------
+getstat(Socket) ->
+ getstat(Socket, inet:stats()).
+
+%%---------------------------------------------------------------
+-spec getstat(Socket, Options) ->
+ {ok, OptionValues} | {error, inet:posix()} when
+ Socket :: #sslsocket{},
+ Options :: [inet:stat_option()],
+ OptionValues :: [{inet:stat_option(), integer()}].
+%%
+%% Description: Get one or more statistic options for a socket.
+%%--------------------------------------------------------------------
+getstat(#sslsocket{pid = {Listen, #config{transport_info = {Transport, _, _, _}}}}, Options) when is_port(Listen), is_list(Options) ->
+ ssl_socket:getstat(Transport, Listen, Options);
+
+getstat(#sslsocket{pid = Pid, fd = {Transport, Socket, _, _}}, Options) when is_pid(Pid), is_list(Options) ->
+ ssl_socket:getstat(Transport, Socket, Options).
+
+%%---------------------------------------------------------------
-spec shutdown(#sslsocket{}, read | write | read_write) -> ok | {error, reason()}.
%%
%% Description: Same as gen_tcp:shutdown/2
@@ -584,6 +610,16 @@ format_error(Error) ->
%%%--------------------------------------------------------------
%%% Internal functions
%%%--------------------------------------------------------------------
+
+%% Possible filters out suites not supported by crypto
+available_suites(default) ->
+ Version = tls_record:highest_protocol_version([]),
+ ssl_cipher:filter_suites(ssl_cipher:suites(Version));
+
+available_suites(all) ->
+ Version = tls_record:highest_protocol_version([]),
+ ssl_cipher:filter_suites(ssl_cipher:all_suites(Version)).
+
do_connect(Address, Port,
#config{transport_info = CbInfo, inet_user = UserOpts, ssl = SslOpts,
emulated = EmOpts, inet_ssl = SocketOpts, connection_cb = ConnetionCb},
@@ -716,13 +752,15 @@ handle_options(Opts0, Role) ->
server, Role),
protocol = proplists:get_value(protocol, Opts, tls),
padding_check = proplists:get_value(padding_check, Opts, true),
+ beast_mitigation = handle_option(beast_mitigation, Opts, one_n_minus_one),
fallback = handle_option(fallback, Opts,
proplists:get_value(fallback, Opts,
default_option_role(client,
false, Role)),
client, Role),
crl_check = handle_option(crl_check, Opts, false),
- crl_cache = handle_option(crl_cache, Opts, {ssl_crl_cache, {internal, []}})
+ crl_cache = handle_option(crl_cache, Opts, {ssl_crl_cache, {internal, []}}),
+ v2_hello_compatible = handle_option(v2_hello_compatible, Opts, false)
},
CbInfo = proplists:get_value(cb_info, Opts, {gen_tcp, tcp, tcp_closed, tcp_error}),
@@ -737,7 +775,7 @@ handle_options(Opts0, Role) ->
alpn_preferred_protocols, next_protocols_advertised,
client_preferred_next_protocols, log_alert,
server_name_indication, honor_cipher_order, padding_check, crl_check, crl_cache,
- fallback, signature_algs],
+ fallback, signature_algs, beast_mitigation, v2_hello_compatible],
SockOpts = lists:foldl(fun(Key, PropList) ->
proplists:delete(Key, PropList)
@@ -977,6 +1015,12 @@ validate_option(crl_check, Value) when (Value == best_effort) or (Value == peer)
Value;
validate_option(crl_cache, {Cb, {_Handle, Options}} = Value) when is_atom(Cb) and is_list(Options) ->
Value;
+validate_option(beast_mitigation, Value) when Value == one_n_minus_one orelse
+ Value == zero_n orelse
+ Value == disabled ->
+ Value;
+validate_option(v2_hello_compatible, Value) when is_boolean(Value) ->
+ Value;
validate_option(Opt, Value) ->
throw({error, {options, {Opt, Value}}}).
diff --git a/lib/ssl/src/ssl_alert.erl b/lib/ssl/src/ssl_alert.erl
index 3e35e24527..db71b16d80 100644
--- a/lib/ssl/src/ssl_alert.erl
+++ b/lib/ssl/src/ssl_alert.erl
@@ -73,10 +73,14 @@ reason_code(#alert{description = Description}, _) ->
%%
%% Description: Returns the error string for given alert.
%%--------------------------------------------------------------------
-
-alert_txt(#alert{level = Level, description = Description, where = {Mod,Line}}) ->
+alert_txt(#alert{level = Level, description = Description, where = {Mod,Line}, reason = undefined}) ->
Mod ++ ":" ++ integer_to_list(Line) ++ ":" ++
- level_txt(Level) ++" "++ description_txt(Description).
+ level_txt(Level) ++" "++ description_txt(Description);
+alert_txt(#alert{reason = Reason} = Alert) ->
+ BaseTxt = alert_txt(Alert#alert{reason = undefined}),
+ FormatDepth = 9, % Some limit on printed representation of an error
+ ReasonTxt = lists:flatten(io_lib:format("~P", [Reason, FormatDepth])),
+ BaseTxt ++ " - " ++ ReasonTxt.
%%--------------------------------------------------------------------
%%% Internal functions
@@ -85,7 +89,7 @@ alert_txt(#alert{level = Level, description = Description, where = {Mod,Line}})
%% It is very unlikely that an correct implementation will send more than one alert at the time
%% So it there is more than 10 warning alerts we consider it an error
decode(<<?BYTE(Level), ?BYTE(_), _/binary>>, _, N) when Level == ?WARNING, N > ?MAX_ALERTS ->
- ?ALERT_REC(?FATAL, ?DECODE_ERROR);
+ ?ALERT_REC(?FATAL, ?DECODE_ERROR, too_many_remote_alerts);
decode(<<?BYTE(Level), ?BYTE(Description), Rest/binary>>, Acc, N) when Level == ?WARNING ->
Alert = ?ALERT_REC(Level, Description),
decode(Rest, [Alert | Acc], N + 1);
@@ -93,7 +97,7 @@ decode(<<?BYTE(Level), ?BYTE(Description), _Rest/binary>>, Acc, _) when Level ==
Alert = ?ALERT_REC(Level, Description),
lists:reverse([Alert | Acc]); %% No need to decode rest fatal alert will end the connection
decode(<<?BYTE(_Level), _/binary>>, _, _) ->
- ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER);
+ ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER, failed_to_decode_remote_alert);
decode(<<>>, Acc, _) ->
lists:reverse(Acc, []).
diff --git a/lib/ssl/src/ssl_alert.hrl b/lib/ssl/src/ssl_alert.hrl
index 8c4bd08d31..38facb964f 100644
--- a/lib/ssl/src/ssl_alert.hrl
+++ b/lib/ssl/src/ssl_alert.hrl
@@ -109,6 +109,7 @@
-define(NO_APPLICATION_PROTOCOL, 120).
-define(ALERT_REC(Level,Desc), #alert{level=Level,description=Desc,where={?FILE, ?LINE}}).
+-define(ALERT_REC(Level,Desc,Reason), #alert{level=Level,description=Desc,where={?FILE, ?LINE},reason=Reason}).
-define(MAX_ALERTS, 10).
@@ -116,6 +117,7 @@
-record(alert, {
level,
description,
- where = {?FILE, ?LINE}
+ where = {?FILE, ?LINE},
+ reason
}).
-endif. % -ifdef(ssl_alert).
diff --git a/lib/ssl/src/ssl_cipher.erl b/lib/ssl/src/ssl_cipher.erl
index dc0a0c2cc4..e935c033c7 100644
--- a/lib/ssl/src/ssl_cipher.erl
+++ b/lib/ssl/src/ssl_cipher.erl
@@ -214,7 +214,7 @@ decipher(?RC4, HashSz, CipherState = #cipher_state{state = State0}, Fragment, _,
%% alerts may permit certain attacks against CBC mode as used in
%% TLS [CBCATT]. It is preferable to uniformly use the
%% bad_record_mac alert to hide the specific type of the error."
- ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC)
+ ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC, decryption_failed)
end;
decipher(?DES, HashSz, CipherState, Fragment, Version, PaddingCheck) ->
@@ -272,7 +272,7 @@ block_decipher(Fun, #cipher_state{key=Key, iv=IV} = CipherState0,
%% alerts may permit certain attacks against CBC mode as used in
%% TLS [CBCATT]. It is preferable to uniformly use the
%% bad_record_mac alert to hide the specific type of the error."
- ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC)
+ ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC, decryption_failed)
end.
aead_ciphertext_to_state(chacha20_poly1305, SeqNo, _IV, AAD0, Fragment, _Version) ->
@@ -296,11 +296,11 @@ aead_decipher(Type, #cipher_state{key = Key, iv = IV} = CipherState,
Content when is_binary(Content) ->
{Content, CipherState};
_ ->
- ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC)
+ ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC, decryption_failed)
end
catch
_:_ ->
- ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC)
+ ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC, decryption_failed)
end.
%%--------------------------------------------------------------------
diff --git a/lib/ssl/src/ssl_connection.erl b/lib/ssl/src/ssl_connection.erl
index 26c371a8ea..90e0810241 100644
--- a/lib/ssl/src/ssl_connection.erl
+++ b/lib/ssl/src/ssl_connection.erl
@@ -465,6 +465,14 @@ certify(internal, #certificate{asn1_certificates = []},
Connection:next_record(State0#state{client_certificate_requested = false}),
Connection:next_event(certify, Record, State);
+certify(internal, #certificate{},
+ #state{role = server,
+ negotiated_version = Version,
+ ssl_options = #ssl_options{verify = verify_none}} =
+ State, Connection) ->
+ Alert = ?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE, unrequested_certificate),
+ Connection:handle_own_alert(Alert, Version, certify, State);
+
certify(internal, #certificate{} = Cert,
#state{negotiated_version = Version,
role = Role,
@@ -786,12 +794,24 @@ downgrade(Type, Event, State, Connection) ->
%% Event handling functions called by state functions to handle
%% common or unexpected events for the state.
%%--------------------------------------------------------------------
+handle_common_event(internal, {handshake, {#hello_request{} = Handshake, _}}, connection = StateName,
+ #state{role = client} = State, _) ->
+ %% Should not be included in handshake history
+ {next_state, StateName, State#state{renegotiation = {true, peer}}, [{next_event, internal, Handshake}]};
+handle_common_event(internal, {handshake, {#hello_request{}, _}}, StateName, #state{role = client}, _)
+ when StateName =/= connection ->
+ {keep_state_and_data};
+handle_common_event(internal, {handshake, {Handshake, Raw}}, StateName,
+ #state{tls_handshake_history = Hs0} = State0, Connection) ->
+ %% This function handles client SNI hello extension when Handshake is
+ %% a client_hello, which needs to be determined by the connection callback.
+ %% In other cases this is a noop
+ State = Connection:handle_sni_extension(Handshake, State0),
+ HsHist = ssl_handshake:update_handshake_history(Hs0, Raw),
+ {next_state, StateName, State#state{tls_handshake_history = HsHist},
+ [{next_event, internal, Handshake}]};
handle_common_event(internal, {tls_record, TLSRecord}, StateName, State, Connection) ->
Connection:handle_common_event(internal, TLSRecord, StateName, State);
-handle_common_event(internal, #hello_request{}, StateName, #state{role = client} = State0, Connection)
- when StateName =:= connection ->
- {Record, State} = Connection:next_record(State0),
- Connection:next_event(StateName, Record, State);
handle_common_event(timeout, hibernate, _, _, _) ->
{keep_state_and_data, [hibernate]};
handle_common_event(internal, {application_data, Data}, StateName, State0, Connection) ->
@@ -875,48 +895,14 @@ handle_call({get_opts, OptTags}, From, _,
socket_options = SockOpts}, _) ->
OptsReply = get_socket_opts(Transport, Socket, OptTags, SockOpts, []),
{keep_state_and_data, [{reply, From, OptsReply}]};
-handle_call({set_opts, Opts0}, From, connection = StateName0,
+handle_call({set_opts, Opts0}, From, StateName,
#state{socket_options = Opts1,
- protocol_cb = Connection,
socket = Socket,
- transport_cb = Transport,
- user_data_buffer = Buffer} = State0, _) ->
+ transport_cb = Transport} = State0, _) ->
{Reply, Opts} = set_socket_opts(Transport, Socket, Opts0, Opts1, []),
- State1 = State0#state{socket_options = Opts},
- if
- Opts#socket_options.active =:= false ->
- hibernate_after(StateName0, State1, [{reply, From, Reply}]);
- Buffer =:= <<>>, Opts1#socket_options.active =:= false ->
- %% Need data, set active once
- {Record, State2} = Connection:next_record_if_active(State1),
- %% Note: Renogotiation may cause StateName0 =/= StateName
- case Connection:next_event(StateName0, Record, State2) of
- {next_state, StateName, State} ->
- hibernate_after(StateName, State, [{reply, From, Reply}]);
- {next_state, StateName, State, Actions} ->
- hibernate_after(StateName, State, [{reply, From, Reply} | Actions]);
- {stop, Reason, State} ->
- {stop, Reason, State}
- end;
- Buffer =:= <<>> ->
- %% Active once already set
- hibernate_after(StateName0, State1, [{reply, From, Reply}]);
- true ->
- case Connection:read_application_data(<<>>, State1) of
- {stop, Reason, State} ->
- {stop, Reason, State};
- {Record, State2} ->
- %% Note: Renogotiation may cause StateName0 =/= StateName
- case Connection:next_event(StateName0, Record, State2) of
- {next_state, StateName, State} ->
- hibernate_after(StateName, State, [{reply, From, Reply}]);
- {next_state, StateName, State, Actions} ->
- hibernate_after(StateName, State, [{reply, From, Reply} | Actions]);
- {stop, _, _} = Stop ->
- Stop
- end
- end
- end;
+ State = State0#state{socket_options = Opts},
+ handle_active_option(Opts#socket_options.active, StateName, From, Reply, State);
+
handle_call(renegotiate, From, StateName, _, _) when StateName =/= connection ->
{keep_state_and_data, [{reply, From, {error, already_renegotiating}}]};
handle_call({prf, Secret, Label, Seed, WantedLength}, From, _,
@@ -1522,7 +1508,7 @@ rsa_key_exchange(Version, PremasterSecret, PublicKeyInfo = {Algorithm, _, _})
{premaster_secret, PremasterSecret,
PublicKeyInfo});
rsa_key_exchange(_, _, _) ->
- throw (?ALERT_REC(?FATAL,?HANDSHAKE_FAILURE)).
+ throw (?ALERT_REC(?FATAL,?HANDSHAKE_FAILURE, pub_key_is_not_rsa)).
rsa_psk_key_exchange(Version, PskIdentity, PremasterSecret,
PublicKeyInfo = {Algorithm, _, _})
@@ -1539,7 +1525,7 @@ rsa_psk_key_exchange(Version, PskIdentity, PremasterSecret,
{psk_premaster_secret, PskIdentity, PremasterSecret,
PublicKeyInfo});
rsa_psk_key_exchange(_, _, _, _) ->
- throw (?ALERT_REC(?FATAL,?HANDSHAKE_FAILURE)).
+ throw (?ALERT_REC(?FATAL,?HANDSHAKE_FAILURE, pub_key_is_not_rsa)).
request_client_cert(#state{ssl_options = #ssl_options{verify = verify_peer,
signature_algs = SupportedHashSigns},
@@ -1876,9 +1862,12 @@ start_or_recv_cancel_timer(infinity, _RecvFrom) ->
start_or_recv_cancel_timer(Timeout, RecvFrom) ->
erlang:send_after(Timeout, self(), {cancel_start_or_recv, RecvFrom}).
-hibernate_after(StateName, #state{ssl_options=#ssl_options{hibernate_after = HibernateAfter}} = State,
+hibernate_after(connection = StateName,
+ #state{ssl_options=#ssl_options{hibernate_after = HibernateAfter}} = State,
Actions) ->
- {next_state, StateName, State, [{timeout, HibernateAfter, hibernate} | Actions]}.
+ {next_state, StateName, State, [{timeout, HibernateAfter, hibernate} | Actions]};
+hibernate_after(StateName, State, Actions) ->
+ {next_state, StateName, State, Actions}.
terminate_alert(normal, Version, ConnectionStates) ->
ssl_alert:encode(?ALERT_REC(?WARNING, ?CLOSE_NOTIFY),
@@ -2032,4 +2021,39 @@ ssl_options_list([ciphers = Key | Keys], [Value | Values], Acc) ->
ssl_options_list([Key | Keys], [Value | Values], Acc) ->
ssl_options_list(Keys, Values, [{Key, Value} | Acc]).
+handle_active_option(false, connection = StateName, To, Reply, State) ->
+ hibernate_after(StateName, State, [{reply, To, Reply}]);
+
+handle_active_option(_, connection = StateName0, To, Reply, #state{protocol_cb = Connection,
+ user_data_buffer = <<>>} = State0) ->
+ %% Need data, set active once
+ {Record, State1} = Connection:next_record_if_active(State0),
+ %% Note: Renogotiation may cause StateName0 =/= StateName
+ case Connection:next_event(StateName0, Record, State1) of
+ {next_state, StateName, State} ->
+ hibernate_after(StateName, State, [{reply, To, Reply}]);
+ {next_state, StateName, State, Actions} ->
+ hibernate_after(StateName, State, [{reply, To, Reply} | Actions]);
+ {stop, Reason, State} ->
+ {stop, Reason, State}
+ end;
+handle_active_option(_, StateName, To, Reply, #state{user_data_buffer = <<>>} = State) ->
+ %% Active once already set
+ {next_state, StateName, State, [{reply, To, Reply}]};
+%% user_data_buffer =/= <<>>
+handle_active_option(_, StateName0, To, Reply, #state{protocol_cb = Connection} = State0) ->
+ case Connection:read_application_data(<<>>, State0) of
+ {stop, Reason, State} ->
+ {stop, Reason, State};
+ {Record, State1} ->
+ %% Note: Renogotiation may cause StateName0 =/= StateName
+ case Connection:next_event(StateName0, Record, State1) of
+ {next_state, StateName, State} ->
+ hibernate_after(StateName, State, [{reply, To, Reply}]);
+ {next_state, StateName, State, Actions} ->
+ hibernate_after(StateName, State, [{reply, To, Reply} | Actions]);
+ {stop, _, _} = Stop ->
+ Stop
+ end
+ end.
diff --git a/lib/ssl/src/ssl_crl.erl b/lib/ssl/src/ssl_crl.erl
index faf5007b16..d9f21e04ac 100644
--- a/lib/ssl/src/ssl_crl.erl
+++ b/lib/ssl/src/ssl_crl.erl
@@ -39,13 +39,12 @@ trusted_cert_and_path(CRL, {SerialNumber, Issuer},{Db, DbRef} = DbHandle) ->
end;
trusted_cert_and_path(CRL, issuer_not_found, {Db, DbRef} = DbHandle) ->
- try find_issuer(CRL, DbHandle) of
- OtpCert ->
+ case find_issuer(CRL, DbHandle) of
+ {ok, OtpCert} ->
{ok, Root, Chain} = ssl_certificate:certificate_chain(OtpCert, Db, DbRef),
- {ok, Root, lists:reverse(Chain)}
- catch
- throw:_ ->
- {error, issuer_not_found}
+ {ok, Root, lists:reverse(Chain)};
+ {error, issuer_not_found} ->
+ {ok, unknown_crl_ca, []}
end.
find_issuer(CRL, {Db,_}) ->
@@ -61,11 +60,10 @@ find_issuer(CRL, {Db,_}) ->
issuer_not_found ->
{error, issuer_not_found}
catch
- {ok, IssuerCert} ->
- IssuerCert
+ {ok, _} = Result ->
+ Result
end.
-
verify_crl_issuer(CRL, ErlCertCandidate, Issuer, NotIssuer) ->
TBSCert = ErlCertCandidate#'OTPCertificate'.tbsCertificate,
case public_key:pkix_normalize_name(TBSCert#'OTPTBSCertificate'.subject) of
diff --git a/lib/ssl/src/ssl_handshake.erl b/lib/ssl/src/ssl_handshake.erl
index 598d4e4112..0787e151c0 100644
--- a/lib/ssl/src/ssl_handshake.erl
+++ b/lib/ssl/src/ssl_handshake.erl
@@ -167,7 +167,7 @@ certificate(OwnCert, CertDbHandle, CertDbRef, server) ->
{ok, _, Chain} ->
#certificate{asn1_certificates = Chain};
{error, _} ->
- ?ALERT_REC(?FATAL, ?INTERNAL_ERROR)
+ ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, server_has_no_suitable_certificates)
end.
%%--------------------------------------------------------------------
@@ -195,7 +195,7 @@ client_certificate_verify(OwnCert, MasterSecret, Version,
PrivateKey, {Handshake, _}) ->
case public_key:pkix_is_fixed_dh_cert(OwnCert) of
true ->
- ?ALERT_REC(?FATAL, ?UNSUPPORTED_CERTIFICATE);
+ ?ALERT_REC(?FATAL, ?UNSUPPORTED_CERTIFICATE, fixed_diffie_hellman_prohibited);
false ->
Hashes =
calc_certificate_verify(Version, HashAlgo, MasterSecret, Handshake),
@@ -353,7 +353,7 @@ verify_server_key(#server_key_params{params_bin = EncParams,
%% Description: Checks that the certificate_verify message is valid.
%%--------------------------------------------------------------------
certificate_verify(_, _, _, undefined, _, _) ->
- ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE);
+ ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, invalid_certificate_verify_message);
certificate_verify(Signature, PublicKeyInfo, Version,
HashSign = {HashAlgo, _}, MasterSecret, {_, Handshake}) ->
@@ -417,7 +417,7 @@ certify(#certificate{asn1_certificates = ASN1Certs}, CertDbHandle, CertDbRef,
catch
error:_ ->
%% ASN-1 decode of certificate somehow failed
- ?ALERT_REC(?FATAL, ?CERTIFICATE_UNKNOWN)
+ ?ALERT_REC(?FATAL, ?CERTIFICATE_UNKNOWN, failed_to_decode_certificate)
end.
%%--------------------------------------------------------------------
@@ -605,7 +605,7 @@ select_hashsign(#hash_sign_algos{hash_sign_algos = HashSigns}, Cert, KeyExAlgo,
false
end, HashSigns) of
[] ->
- ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY);
+ ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_signature_algorithm);
[HashSign | _] ->
HashSign
end;
@@ -664,11 +664,8 @@ master_secret(RecordCB, Version, #session{master_secret = Mastersecret},
try master_secret(RecordCB, Version, Mastersecret, SecParams,
ConnectionStates, Role)
catch
- exit:Reason ->
- Report = io_lib:format("Key calculation failed due to ~p",
- [Reason]),
- error_logger:error_report(Report),
- ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE)
+ exit:_ ->
+ ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, key_calculation_failure)
end;
master_secret(RecordCB, Version, PremasterSecret, ConnectionStates, Role) ->
@@ -683,11 +680,8 @@ master_secret(RecordCB, Version, PremasterSecret, ConnectionStates, Role) ->
ClientRandom, ServerRandom),
SecParams, ConnectionStates, Role)
catch
- exit:Reason ->
- Report = io_lib:format("Master secret calculation failed"
- " due to ~p", [Reason]),
- error_logger:error_report(Report),
- ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE)
+ exit:_ ->
+ ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, master_secret_calculation_failure)
end.
%%-------------Encode/Decode --------------------------------
@@ -958,8 +952,8 @@ decode_handshake(_Version, ?CLIENT_KEY_EXCHANGE, PKEPMS) ->
#client_key_exchange{exchange_keys = PKEPMS};
decode_handshake(_Version, ?FINISHED, VerifyData) ->
#finished{verify_data = VerifyData};
-decode_handshake(_, _, _) ->
- throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE)).
+decode_handshake(_, Message, _) ->
+ throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, {unknown_or_malformed_handshake, Message})).
%%--------------------------------------------------------------------
-spec decode_hello_extensions({client, binary()} | binary()) -> #hello_extensions{}.
@@ -1031,8 +1025,8 @@ dec_server_key(<<?UINT16(NLen), N:NLen/binary,
params_bin = BinMsg,
hashsign = HashSign,
signature = Signature};
-dec_server_key(_, _, _) ->
- throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE)).
+dec_server_key(_, KeyExchange, _) ->
+ throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, {unknown_or_malformed_key_exchange, KeyExchange})).
%%--------------------------------------------------------------------
-spec decode_suites('2_bytes'|'3_bytes', binary()) -> list().
@@ -1253,8 +1247,12 @@ handle_server_hello_extensions(RecordCB, Random, CipherSuite, Compression,
Protocol ->
{ConnectionStates, npn, Protocol}
end;
- _ -> %% {error, _Reason} or a list of 0/2+ protocols.
- ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE)
+ {error, Reason} ->
+ ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, Reason);
+ [] ->
+ ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, no_protocols_in_server_hello);
+ [_|_] ->
+ ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, too_many_protocols_in_server_hello)
end.
select_version(RecordCB, ClientVersion, Versions) ->
@@ -1316,14 +1314,14 @@ handle_renegotiation_info(_RecordCB, client, #renegotiation_info{renegotiated_co
true ->
{ok, ConnectionStates};
false ->
- ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE)
+ ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, client_renegotiation)
end;
handle_renegotiation_info(_RecordCB, server, #renegotiation_info{renegotiated_connection = ClientVerify},
ConnectionStates, true, _, CipherSuites) ->
case is_member(?TLS_EMPTY_RENEGOTIATION_INFO_SCSV, CipherSuites) of
true ->
- ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE);
+ ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, {server_renegotiation, empty_renegotiation_info_scsv});
false ->
CS = ssl_record:current_connection_state(ConnectionStates, read),
Data = CS#connection_state.client_verify_data,
@@ -1331,7 +1329,7 @@ handle_renegotiation_info(_RecordCB, server, #renegotiation_info{renegotiated_co
true ->
{ok, ConnectionStates};
false ->
- ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE)
+ ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, server_renegotiation)
end
end;
@@ -1341,7 +1339,7 @@ handle_renegotiation_info(RecordCB, client, undefined, ConnectionStates, true, S
handle_renegotiation_info(RecordCB, server, undefined, ConnectionStates, true, SecureRenegotation, CipherSuites) ->
case is_member(?TLS_EMPTY_RENEGOTIATION_INFO_SCSV, CipherSuites) of
true ->
- ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE);
+ ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, {server_renegotiation, empty_renegotiation_info_scsv});
false ->
handle_renegotiation_info(RecordCB, ConnectionStates, SecureRenegotation)
end.
@@ -1350,7 +1348,7 @@ handle_renegotiation_info(_RecordCB, ConnectionStates, SecureRenegotation) ->
CS = ssl_record:current_connection_state(ConnectionStates, read),
case {SecureRenegotation, CS#connection_state.secure_renegotiation} of
{_, true} ->
- ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE);
+ ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, already_secure);
{true, false} ->
?ALERT_REC(?FATAL, ?NO_RENEGOTIATION);
{false, false} ->
@@ -1523,8 +1521,8 @@ path_validation_alert({bad_cert, selfsigned_peer}) ->
?ALERT_REC(?FATAL, ?BAD_CERTIFICATE);
path_validation_alert({bad_cert, unknown_ca}) ->
?ALERT_REC(?FATAL, ?UNKNOWN_CA);
-path_validation_alert(_) ->
- ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE).
+path_validation_alert(Reason) ->
+ ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, Reason).
encrypted_premaster_secret(Secret, RSAPublicKey) ->
try
@@ -1533,18 +1531,27 @@ encrypted_premaster_secret(Secret, RSAPublicKey) ->
rsa_pkcs1_padding}]),
#encrypted_premaster_secret{premaster_secret = PreMasterSecret}
catch
- _:_->
- throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE))
+ _:_->
+ throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, premaster_encryption_failed))
end.
-digitally_signed({3, Minor}, Hash, HashAlgo, Key) when Minor >= 3 ->
+digitally_signed(Version, Hashes, HashAlgo, PrivateKey) ->
+ try do_digitally_signed(Version, Hashes, HashAlgo, PrivateKey) of
+ Signature ->
+ Signature
+ catch
+ error:badkey->
+ throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, bad_key(PrivateKey)))
+ end.
+
+do_digitally_signed({3, Minor}, Hash, HashAlgo, Key) when Minor >= 3 ->
public_key:sign({digest, Hash}, HashAlgo, Key);
-digitally_signed(_Version, Hash, HashAlgo, #'DSAPrivateKey'{} = Key) ->
+do_digitally_signed(_Version, Hash, HashAlgo, #'DSAPrivateKey'{} = Key) ->
public_key:sign({digest, Hash}, HashAlgo, Key);
-digitally_signed(_Version, Hash, _HashAlgo, #'RSAPrivateKey'{} = Key) ->
+do_digitally_signed(_Version, Hash, _HashAlgo, #'RSAPrivateKey'{} = Key) ->
public_key:encrypt_private(Hash, Key,
[{rsa_pad, rsa_pkcs1_padding}]);
-digitally_signed(_Version, Hash, HashAlgo, Key) ->
+do_digitally_signed(_Version, Hash, HashAlgo, Key) ->
public_key:sign({digest, Hash}, HashAlgo, Key).
calc_certificate_verify({3, 0}, HashAlgo, MasterSecret, Handshake) ->
@@ -1751,12 +1758,12 @@ dec_client_key(PKEPMS, ?KEY_EXCHANGE_RSA, {3, 0}) ->
dec_client_key(<<?UINT16(_), PKEPMS/binary>>, ?KEY_EXCHANGE_RSA, _) ->
#encrypted_premaster_secret{premaster_secret = PKEPMS};
dec_client_key(<<>>, ?KEY_EXCHANGE_DIFFIE_HELLMAN, _) ->
- throw(?ALERT_REC(?FATAL, ?UNSUPPORTED_CERTIFICATE));
+ throw(?ALERT_REC(?FATAL, ?UNSUPPORTED_CERTIFICATE, empty_dh_public));
dec_client_key(<<?UINT16(DH_YLen), DH_Y:DH_YLen/binary>>,
?KEY_EXCHANGE_DIFFIE_HELLMAN, _) ->
#client_diffie_hellman_public{dh_public = DH_Y};
dec_client_key(<<>>, ?KEY_EXCHANGE_EC_DIFFIE_HELLMAN, _) ->
- throw(?ALERT_REC(?FATAL, ?UNSUPPORTED_CERTIFICATE));
+ throw(?ALERT_REC(?FATAL, ?UNSUPPORTED_CERTIFICATE, empty_dh_public));
dec_client_key(<<?BYTE(DH_YLen), DH_Y:DH_YLen/binary>>,
?KEY_EXCHANGE_EC_DIFFIE_HELLMAN, _) ->
#client_ec_diffie_hellman_public{dh_public = DH_Y};
@@ -1800,7 +1807,7 @@ dec_server_key_signature(Params, <<?UINT16(0)>>, _) ->
dec_server_key_signature(Params, <<?UINT16(Len), Signature:Len/binary>>, _) ->
{Params, undefined, Signature};
dec_server_key_signature(_, _, _) ->
- throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE)).
+ throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, failed_to_decrypt_server_key_sign)).
dec_hello_extensions(<<>>, Acc) ->
Acc;
@@ -1955,8 +1962,8 @@ key_exchange_alg(_) ->
%%-------------Extension handling --------------------------------
%% Receive protocols, choose one from the list, return it.
-handle_alpn_extension(_, {error, _Reason}) ->
- ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE);
+handle_alpn_extension(_, {error, Reason}) ->
+ ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, Reason);
handle_alpn_extension([], _) ->
?ALERT_REC(?FATAL, ?NO_APPLICATION_PROTOCOL);
handle_alpn_extension([ServerProtocol|Tail], ClientProtocols) ->
@@ -1976,7 +1983,7 @@ handle_next_protocol(#next_protocol_negotiation{} = NextProtocols,
true ->
select_next_protocol(decode_next_protocols(NextProtocols), NextProtocolSelector);
false ->
- ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE) % unexpected next protocol extension
+ ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, unexpected_next_protocol_extension)
end.
@@ -1996,17 +2003,17 @@ handle_next_protocol_on_server(#next_protocol_negotiation{extension_data = <<>>}
Protocols;
handle_next_protocol_on_server(_Hello, _Renegotiation, _SSLOpts) ->
- ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE). % unexpected next protocol extension
+ ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, unexpected_next_protocol_extension).
next_protocol_extension_allowed(NextProtocolSelector, Renegotiating) ->
NextProtocolSelector =/= undefined andalso not Renegotiating.
-select_next_protocol({error, _Reason}, _NextProtocolSelector) ->
- ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE);
+select_next_protocol({error, Reason}, _NextProtocolSelector) ->
+ ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, Reason);
select_next_protocol(Protocols, NextProtocolSelector) ->
case NextProtocolSelector(Protocols) of
?NO_PROTOCOL ->
- ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE);
+ ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, no_next_protocol);
Protocol when is_binary(Protocol) ->
Protocol
end.
@@ -2169,3 +2176,9 @@ is_acceptable_hash_sign(_,_,_,_) ->
is_acceptable_hash_sign(Algos, SupportedHashSigns) ->
lists:member(Algos, SupportedHashSigns).
+bad_key(#'DSAPrivateKey'{}) ->
+ unacceptable_dsa_key;
+bad_key(#'RSAPrivateKey'{}) ->
+ unacceptable_rsa_key;
+bad_key(#'ECPrivateKey'{}) ->
+ unacceptable_ecdsa_key.
diff --git a/lib/ssl/src/ssl_handshake.hrl b/lib/ssl/src/ssl_handshake.hrl
index e7b118de10..fde92035a2 100644
--- a/lib/ssl/src/ssl_handshake.hrl
+++ b/lib/ssl/src/ssl_handshake.hrl
@@ -53,7 +53,8 @@
-define(NUM_OF_SESSION_ID_BYTES, 32). % TSL 1.1 & SSL 3
-define(NUM_OF_PREMASTERSECRET_BYTES, 48).
-define(DEFAULT_DIFFIE_HELLMAN_GENERATOR, 2).
--define(DEFAULT_DIFFIE_HELLMAN_PRIME, 16#FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFF).
+-define(DEFAULT_DIFFIE_HELLMAN_PRIME,
+ 16#FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% Handsake protocol - RFC 4346 section 7.4
diff --git a/lib/ssl/src/ssl_internal.hrl b/lib/ssl/src/ssl_internal.hrl
index 076e663cd4..c19c1787ff 100644
--- a/lib/ssl/src/ssl_internal.hrl
+++ b/lib/ssl/src/ssl_internal.hrl
@@ -133,10 +133,14 @@
%% the client?
honor_cipher_order = false :: boolean(),
padding_check = true :: boolean(),
+ %%Should we use 1/n-1 or 0/n splitting to mitigate BEAST, or disable
+ %%mitigation entirely?
+ beast_mitigation = one_n_minus_one :: one_n_minus_one | zero_n | disabled,
fallback = false :: boolean(),
crl_check :: boolean() | peer | best_effort,
crl_cache,
- signature_algs
+ signature_algs,
+ v2_hello_compatible :: boolean()
}).
-record(socket_options,
diff --git a/lib/ssl/src/ssl_manager.erl b/lib/ssl/src/ssl_manager.erl
index 60b4fbe995..c7dcbaabe9 100644
--- a/lib/ssl/src/ssl_manager.erl
+++ b/lib/ssl/src/ssl_manager.erl
@@ -67,6 +67,7 @@
-define(CLEAN_SESSION_DB, 60000).
-define(CLEAN_CERT_DB, 500).
-define(DEFAULT_MAX_SESSION_CACHE, 1000).
+-define(LOAD_MITIGATION, 10).
%%====================================================================
%% API
@@ -196,10 +197,12 @@ register_session(Port, Session) ->
%%--------------------------------------------------------------------
-spec invalidate_session(host(), inet:port_number(), #session{}) -> ok.
invalidate_session(Host, Port, Session) ->
+ load_mitigation(),
cast({invalidate_session, Host, Port, Session}).
-spec invalidate_session(inet:port_number(), #session{}) -> ok.
invalidate_session(Port, Session) ->
+ load_mitigation(),
cast({invalidate_session, Port, Session}).
-spec invalidate_pem(File::binary()) -> ok.
@@ -719,3 +722,11 @@ invalidate_session_cache(undefined, CacheCb, Cache) ->
start_session_validator(Cache, CacheCb, {invalidate_before, erlang:monotonic_time()}, undefined);
invalidate_session_cache(Pid, _CacheCb, _Cache) ->
Pid.
+
+load_mitigation() ->
+ MSec = rand:uniform(?LOAD_MITIGATION),
+ receive
+ after
+ MSec ->
+ continue
+ end.
diff --git a/lib/ssl/src/ssl_record.erl b/lib/ssl/src/ssl_record.erl
index 866bfcef7e..0a086f5eeb 100644
--- a/lib/ssl/src/ssl_record.erl
+++ b/lib/ssl/src/ssl_record.erl
@@ -30,7 +30,7 @@
-include("ssl_alert.hrl").
%% Connection state handling
--export([init_connection_states/1,
+-export([init_connection_states/2,
current_connection_state/2, pending_connection_state/2,
activate_pending_connection_state/2,
set_security_params/3,
@@ -62,15 +62,16 @@
%%====================================================================
%%--------------------------------------------------------------------
--spec init_connection_states(client | server) -> #connection_states{}.
+-spec init_connection_states(client | server, one_n_minus_one | zero_n | disabled ) ->
+ #connection_states{}.
%%
%% Description: Creates a connection_states record with appropriate
%% values for the initial SSL connection setup.
%%--------------------------------------------------------------------
-init_connection_states(Role) ->
+init_connection_states(Role, BeastMitigation) ->
ConnectionEnd = record_protocol_role(Role),
- Current = initial_connection_state(ConnectionEnd),
- Pending = empty_connection_state(ConnectionEnd),
+ Current = initial_connection_state(ConnectionEnd, BeastMitigation),
+ Pending = empty_connection_state(ConnectionEnd, BeastMitigation),
#connection_states{current_read = Current,
pending_read = Pending,
current_write = Current,
@@ -119,9 +120,10 @@ activate_pending_connection_state(States =
read) ->
NewCurrent = Pending#connection_state{epoch = dtls_next_epoch(Current),
sequence_number = 0},
+ BeastMitigation = Pending#connection_state.beast_mitigation,
SecParams = Pending#connection_state.security_parameters,
ConnectionEnd = SecParams#security_parameters.connection_end,
- EmptyPending = empty_connection_state(ConnectionEnd),
+ EmptyPending = empty_connection_state(ConnectionEnd, BeastMitigation),
SecureRenegotation = NewCurrent#connection_state.secure_renegotiation,
NewPending = EmptyPending#connection_state{secure_renegotiation = SecureRenegotation},
States#connection_states{current_read = NewCurrent,
@@ -134,9 +136,10 @@ activate_pending_connection_state(States =
write) ->
NewCurrent = Pending#connection_state{epoch = dtls_next_epoch(Current),
sequence_number = 0},
+ BeastMitigation = Pending#connection_state.beast_mitigation,
SecParams = Pending#connection_state.security_parameters,
ConnectionEnd = SecParams#security_parameters.connection_end,
- EmptyPending = empty_connection_state(ConnectionEnd),
+ EmptyPending = empty_connection_state(ConnectionEnd, BeastMitigation),
SecureRenegotation = NewCurrent#connection_state.secure_renegotiation,
NewPending = EmptyPending#connection_state{secure_renegotiation = SecureRenegotation},
States#connection_states{current_write = NewCurrent,
@@ -314,12 +317,13 @@ set_pending_cipher_state(#connection_states{pending_read = Read,
encode_handshake(Frag, Version,
#connection_states{current_write =
#connection_state{
+ beast_mitigation = BeastMitigation,
security_parameters =
#security_parameters{bulk_cipher_algorithm = BCA}}} =
ConnectionStates) ->
case iolist_size(Frag) of
N when N > ?MAX_PLAIN_TEXT_LENGTH ->
- Data = split_bin(iolist_to_binary(Frag), ?MAX_PLAIN_TEXT_LENGTH, Version, BCA),
+ Data = split_bin(iolist_to_binary(Frag), ?MAX_PLAIN_TEXT_LENGTH, Version, BCA, BeastMitigation),
encode_iolist(?HANDSHAKE, Data, Version, ConnectionStates);
_ ->
encode_plain_text(?HANDSHAKE, Version, Frag, ConnectionStates)
@@ -352,10 +356,11 @@ encode_change_cipher_spec(Version, ConnectionStates) ->
%%--------------------------------------------------------------------
encode_data(Frag, Version,
#connection_states{current_write = #connection_state{
+ beast_mitigation = BeastMitigation,
security_parameters =
#security_parameters{bulk_cipher_algorithm = BCA}}} =
ConnectionStates) ->
- Data = split_bin(Frag, ?MAX_PLAIN_TEXT_LENGTH, Version, BCA),
+ Data = split_bin(Frag, ?MAX_PLAIN_TEXT_LENGTH, Version, BCA, BeastMitigation),
encode_iolist(?APPLICATION_DATA, Data, Version, ConnectionStates).
uncompress(?NULL, Data, CS) ->
@@ -447,9 +452,10 @@ decipher_aead(Version, CipherFragment,
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
-empty_connection_state(ConnectionEnd) ->
+empty_connection_state(ConnectionEnd, BeastMitigation) ->
SecParams = empty_security_params(ConnectionEnd),
- #connection_state{security_parameters = SecParams}.
+ #connection_state{security_parameters = SecParams,
+ beast_mitigation = BeastMitigation}.
empty_security_params(ConnectionEnd = ?CLIENT) ->
#security_parameters{connection_end = ConnectionEnd,
@@ -478,10 +484,11 @@ record_protocol_role(client) ->
record_protocol_role(server) ->
?SERVER.
-initial_connection_state(ConnectionEnd) ->
+initial_connection_state(ConnectionEnd, BeastMitigation) ->
#connection_state{security_parameters =
initial_security_params(ConnectionEnd),
- sequence_number = 0
+ sequence_number = 0,
+ beast_mitigation = BeastMitigation
}.
initial_security_params(ConnectionEnd) ->
@@ -506,11 +513,17 @@ encode_iolist(Type, Data, Version, ConnectionStates0) ->
%% 1/n-1 splitting countermeasure Rizzo/Duong-Beast, RC4 chiphers are
%% not vulnerable to this attack.
-split_bin(<<FirstByte:8, Rest/binary>>, ChunkSize, Version, BCA) when
+split_bin(<<FirstByte:8, Rest/binary>>, ChunkSize, Version, BCA, one_n_minus_one) when
BCA =/= ?RC4 andalso ({3, 1} == Version orelse
{3, 0} == Version) ->
do_split_bin(Rest, ChunkSize, [[FirstByte]]);
-split_bin(Bin, ChunkSize, _, _) ->
+%% 0/n splitting countermeasure for clients that are incompatible with 1/n-1
+%% splitting.
+split_bin(Bin, ChunkSize, Version, BCA, zero_n) when
+ BCA =/= ?RC4 andalso ({3, 1} == Version orelse
+ {3, 0} == Version) ->
+ do_split_bin(Bin, ChunkSize, [[<<>>]]);
+split_bin(Bin, ChunkSize, _, _, _) ->
do_split_bin(Bin, ChunkSize, []).
do_split_bin(<<>>, _, Acc) ->
diff --git a/lib/ssl/src/ssl_record.hrl b/lib/ssl/src/ssl_record.hrl
index d34d144343..87fde35258 100644
--- a/lib/ssl/src/ssl_record.hrl
+++ b/lib/ssl/src/ssl_record.hrl
@@ -40,7 +40,9 @@
%% RFC 5746
secure_renegotiation,
client_verify_data,
- server_verify_data
+ server_verify_data,
+ %% How to do BEAST mitigation?
+ beast_mitigation
}).
-record(connection_states, {
diff --git a/lib/ssl/src/ssl_socket.erl b/lib/ssl/src/ssl_socket.erl
index 95a70a4602..b2aea2ba9c 100644
--- a/lib/ssl/src/ssl_socket.erl
+++ b/lib/ssl/src/ssl_socket.erl
@@ -24,7 +24,7 @@
-include("ssl_internal.hrl").
-include("ssl_api.hrl").
--export([socket/5, setopts/3, getopts/3, peername/2, sockname/2, port/2]).
+-export([socket/5, setopts/3, getopts/3, getstat/3, peername/2, sockname/2, port/2]).
-export([emulated_options/0, internal_inet_values/0, default_inet_values/0,
init/1, start_link/3, terminate/2, inherit_tracker/3, get_emulated_opts/1,
set_emulated_opts/2, get_all_opts/1, handle_call/3, handle_cast/2,
@@ -74,6 +74,11 @@ getopts(gen_tcp, Socket, Options) ->
getopts(Transport, Socket, Options) ->
Transport:getopts(Socket, Options).
+getstat(gen_tcp, Socket, Options) ->
+ inet:getstat(Socket, Options);
+getstat(Transport, Socket, Options) ->
+ Transport:getstat(Socket, Options).
+
peername(gen_tcp, Socket) ->
inet:peername(Socket);
peername(Transport, Socket) ->
diff --git a/lib/ssl/src/ssl_tls_dist_proxy.erl b/lib/ssl/src/ssl_tls_dist_proxy.erl
index 4651687fe6..a920f54ed2 100644
--- a/lib/ssl/src/ssl_tls_dist_proxy.erl
+++ b/lib/ssl/src/ssl_tls_dist_proxy.erl
@@ -116,7 +116,8 @@ handle_call({listen, Driver, Name}, _From, State) ->
{ok, TcpAddress} = get_tcp_address(Socket),
{ok, WorldTcpAddress} = get_tcp_address(World),
{_,Port} = WorldTcpAddress#net_address.address,
- case erl_epmd:register_node(Name, Port) of
+ ErlEpmd = net_kernel:epmd_module(),
+ case ErlEpmd:register_node(Name, Port) of
{ok, Creation} ->
{reply, {ok, {Socket, TcpAddress, Creation}},
State#state{listen={Socket, World}}};
@@ -401,6 +402,18 @@ 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]) ->
@@ -425,14 +438,28 @@ 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(_,_) ->
- exit(malformed_ssl_dist_opt).
+ssl_options(Type, Opts) ->
+ error(malformed_ssl_dist_opt, [Type, Opts]).
atomize(List) when is_list(List) ->
list_to_atom(List);
atomize(Atom) when is_atom(Atom) ->
Atom.
+termify(String) when is_list(String) ->
+ {ok, Tokens, _} = erl_scan:string(String ++ "."),
+ {ok, Term} = erl_parse:parse_term(Tokens),
+ Term.
+
+verify_fun(Value) ->
+ case termify(Value) of
+ {Mod, Func, State} when is_atom(Mod), is_atom(Func) ->
+ Fun = fun Mod:Func/3,
+ {Fun, State};
+ _ ->
+ error(malformed_ssl_dist_opt, [Value])
+ end.
+
flush_old_controller(Pid, Socket) ->
receive
{tcp, Socket, Data} ->
diff --git a/lib/ssl/src/tls_connection.erl b/lib/ssl/src/tls_connection.erl
index 208edc644a..eaf2dd002d 100644
--- a/lib/ssl/src/tls_connection.erl
+++ b/lib/ssl/src/tls_connection.erl
@@ -50,7 +50,7 @@
%% Handshake handling
-export([renegotiate/2, send_handshake/2, send_change_cipher/2,
- reinit_handshake_data/1]).
+ reinit_handshake_data/1, handle_sni_extension/2]).
%% Alert and close handling
-export([send_alert/2, handle_own_alert/4, handle_close_alert/3,
@@ -228,16 +228,16 @@ error(_, _, _) ->
gen_statem:state_function_result().
%%--------------------------------------------------------------------
hello(internal, #client_hello{client_version = ClientVersion,
- extensions = #hello_extensions{ec_point_formats = EcPointFormats,
- elliptic_curves = EllipticCurves}} = Hello,
- State = #state{connection_states = ConnectionStates0,
- port = Port, session = #session{own_certificate = Cert} = Session0,
- renegotiation = {Renegotiation, _},
- session_cache = Cache,
- session_cache_cb = CacheCb,
- negotiated_protocol = CurrentProtocol,
- key_algorithm = KeyExAlg,
- ssl_options = SslOpts}) ->
+ extensions = #hello_extensions{ec_point_formats = EcPointFormats,
+ elliptic_curves = EllipticCurves}} = Hello,
+ #state{connection_states = ConnectionStates0,
+ port = Port, session = #session{own_certificate = Cert} = Session0,
+ renegotiation = {Renegotiation, _},
+ session_cache = Cache,
+ session_cache_cb = CacheCb,
+ 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
@@ -311,7 +311,7 @@ cipher(Type, Event, State) ->
connection(info, Event, State) ->
handle_info(Event, connection, State);
connection(internal, #hello_request{},
- #state{host = Host, port = Port,
+ #state{role = client, host = Host, port = Port,
session = #session{own_certificate = Cert} = Session0,
session_cache = Cache, session_cache_cb = CacheCb,
ssl_options = SslOpts,
@@ -326,14 +326,16 @@ connection(internal, #hello_request{},
= Hello#client_hello.session_id}}),
next_event(hello, Record, State);
connection(internal, #client_hello{} = Hello,
- #state{role = server, allow_renegotiate = true} = State) ->
+ #state{role = server, allow_renegotiate = true} = State0) ->
%% Mitigate Computational DoS attack
%% http://www.educatedguesswork.org/2011/10/ssltls_and_computational_dos.html
%% http://www.thc.org/thc-ssl-dos/ Rather than disabling client
%% initiated renegotiation we will disallow many client initiated
%% renegotiations immediately after each other.
erlang:send_after(?WAIT_TO_ALLOW_RENEGOTIATION, self(), allow_renegotiate),
- {next_state, hello, State#state{allow_renegotiate = false}, [{next_event, internal, Hello}]};
+ {Record, State} = next_record(State0#state{allow_renegotiate = false,
+ renegotiation = {true, peer}}),
+ next_event(hello, Record, State, [{next_event, internal, Hello}]);
connection(internal, #client_hello{},
#state{role = server, allow_renegotiate = false} = State0) ->
Alert = ?ALERT_REC(?WARNING, ?NO_RENEGOTIATION),
@@ -397,40 +399,14 @@ handle_common_event(internal, #alert{} = Alert, StateName,
handle_common_event(internal, #ssl_tls{type = ?HANDSHAKE, fragment = Data},
StateName, #state{protocol_buffers =
#protocol_buffers{tls_handshake_buffer = Buf0} = Buffers,
- negotiated_version = Version} = State0) ->
-
- Handle =
- fun({#hello_request{} = Packet, _}, {connection, HState}) ->
- %% This message should not be included in handshake
- %% message hashes. Starts new handshake (renegotiation)
- Hs0 = ssl_handshake:init_handshake_history(),
- {HState#state{tls_handshake_history = Hs0,
- renegotiation = {true, peer}},
- {next_event, internal, Packet}};
- ({#hello_request{}, _}, {next_state, _SName, HState}) ->
- %% This message should not be included in handshake
- %% message hashes. Already in negotiation so it will be ignored!
- {HState, []};
- ({#client_hello{} = Packet, Raw}, {connection, HState0}) ->
- HState = handle_sni_extension(Packet, HState0),
- Version = Packet#client_hello.client_version,
- Hs0 = ssl_handshake:init_handshake_history(),
- Hs1 = ssl_handshake:update_handshake_history(Hs0, Raw),
- {HState#state{tls_handshake_history = Hs1,
- renegotiation = {true, peer}},
- {next_event, internal, Packet}};
-
- ({Packet, Raw}, {_SName, HState0 = #state{tls_handshake_history=Hs0}}) ->
- HState = handle_sni_extension(Packet, HState0),
- Hs1 = ssl_handshake:update_handshake_history(Hs0, Raw),
- {HState#state{tls_handshake_history=Hs1}, {next_event, internal, Packet}}
- end,
+ negotiated_version = Version,
+ ssl_options = Options} = State0) ->
try
- {Packets, Buf} = tls_handshake:get_tls_handshake(Version,Data,Buf0),
- State1 = State0#state{protocol_buffers =
- Buffers#protocol_buffers{tls_packets = Packets,
- tls_handshake_buffer = Buf}},
- {State, Events} = tls_handshake_events(Handle, StateName, State1, []),
+ {Packets, Buf} = tls_handshake:get_tls_handshake(Version,Data,Buf0, Options),
+ State =
+ State0#state{protocol_buffers =
+ Buffers#protocol_buffers{tls_handshake_buffer = Buf}},
+ Events = tls_handshake_events(Packets),
case StateName of
connection ->
ssl_connection:hibernate_after(StateName, State, Events);
@@ -496,7 +472,8 @@ decode_alerts(Bin) ->
initial_state(Role, Host, Port, Socket, {SSLOptions, SocketOptions, Tracker}, User,
{CbModule, DataTag, CloseTag, ErrorTag}) ->
- ConnectionStates = ssl_record:init_connection_states(Role),
+ #ssl_options{beast_mitigation = BeastMitigation} = SSLOptions,
+ ConnectionStates = ssl_record:init_connection_states(Role, BeastMitigation),
SessionCacheCb = case application:get_env(ssl, session_cb) of
{ok, Cb} when is_atom(Cb) ->
@@ -778,24 +755,12 @@ send_or_reply(_, Pid, _From, Data) ->
send_user(Pid, Msg) ->
Pid ! Msg.
-tls_handshake_events(Handle, StateName,
- #state{protocol_buffers =
- #protocol_buffers{tls_packets = [Packet]} = Buffers} = State0, Acc) ->
- {State, Event} = Handle(Packet, {StateName,
- State0#state{protocol_buffers =
- Buffers#protocol_buffers{tls_packets = []}}}),
- {State, lists:reverse([Event |Acc])};
-tls_handshake_events(Handle, StateName,
- #state{protocol_buffers =
- #protocol_buffers{tls_packets =
- [Packet | Packets]} = Buffers} = State0, Acc) ->
- {State, Event} = Handle(Packet, {StateName, State0#state{protocol_buffers =
- Buffers#protocol_buffers{tls_packets =
- Packets}}}),
- tls_handshake_events(Handle, StateName, State, [Event | Acc]);
-
-tls_handshake_events(_Handle, _, #state{}, _) ->
- throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE)).
+tls_handshake_events([]) ->
+ throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, malformed_handshake));
+tls_handshake_events(Packets) ->
+ lists:map(fun(Packet) ->
+ {next_event, internal, {handshake, Packet}}
+ end, Packets).
write_application_data(Data0, From,
#state{socket = Socket,
@@ -922,7 +887,7 @@ alert_user(Transport, Tracker, Socket,_, _, _, From, Alert, Role) ->
alert_user(Transport, Tracker, Socket, From, Alert, Role) ->
alert_user(Transport, Tracker, Socket, false, no_pid, From, Alert, Role).
-alert_user(_, _, _, false = Active, Pid, From, Alert, Role) ->
+alert_user(_, _, _, false = Active, Pid, From, Alert, Role) when From =/= undefined ->
%% If there is an outstanding ssl_accept | recv
%% From will be defined and send_or_reply will
%% send the appropriate error message.
@@ -1064,5 +1029,5 @@ handle_sni_extension(#client_hello{extensions = HelloExtensions}, State0) ->
}
end
end;
-handle_sni_extension(_, State0) ->
- State0.
+handle_sni_extension(_, State) ->
+ State.
diff --git a/lib/ssl/src/tls_handshake.erl b/lib/ssl/src/tls_handshake.erl
index f34eebb0e4..397f963ad5 100644
--- a/lib/ssl/src/tls_handshake.erl
+++ b/lib/ssl/src/tls_handshake.erl
@@ -33,7 +33,7 @@
-include_lib("public_key/include/public_key.hrl").
-export([client_hello/8, hello/4,
- get_tls_handshake/3, encode_handshake/2, decode_handshake/3]).
+ get_tls_handshake/4, encode_handshake/2, decode_handshake/4]).
-type tls_handshake() :: #client_hello{} | ssl_handshake:ssl_handshake().
@@ -133,17 +133,17 @@ encode_handshake(Package, Version) ->
[MsgType, ?uint24(Len), Bin].
%%--------------------------------------------------------------------
--spec get_tls_handshake(tls_record:tls_version(), binary(), binary() | iolist()) ->
+-spec get_tls_handshake(tls_record:tls_version(), binary(), binary() | iolist(), #ssl_options{}) ->
{[tls_handshake()], binary()}.
%%
%% Description: Given buffered and new data from ssl_record, collects
%% and returns it as a list of handshake messages, also returns leftover
%% data.
%%--------------------------------------------------------------------
-get_tls_handshake(Version, Data, <<>>) ->
- get_tls_handshake_aux(Version, Data, []);
-get_tls_handshake(Version, Data, Buffer) ->
- get_tls_handshake_aux(Version, list_to_binary([Buffer, Data]), []).
+get_tls_handshake(Version, Data, <<>>, Options) ->
+ get_tls_handshake_aux(Version, Data, Options, []);
+get_tls_handshake(Version, Data, Buffer, Options) ->
+ get_tls_handshake_aux(Version, list_to_binary([Buffer, Data]), Options, []).
%%--------------------------------------------------------------------
%%% Internal functions
@@ -167,7 +167,7 @@ handle_client_hello(Version, #client_hello{session_id = SugesstedId,
SslOpts, Cache, CacheCb, Cert),
case CipherSuite of
no_suite ->
- ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY);
+ ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_ciphers);
_ ->
{KeyExAlg,_,_,_} = ssl_cipher:suite_definition(CipherSuite),
case ssl_handshake:select_hashsign(ClientHashSigns, Cert, KeyExAlg, SupportedHashSigns, Version) of
@@ -184,24 +184,24 @@ handle_client_hello(Version, #client_hello{session_id = SugesstedId,
end.
get_tls_handshake_aux(Version, <<?BYTE(Type), ?UINT24(Length),
- Body:Length/binary,Rest/binary>>, Acc) ->
+ Body:Length/binary,Rest/binary>>, #ssl_options{v2_hello_compatible = V2Hello} = Opts, Acc) ->
Raw = <<?BYTE(Type), ?UINT24(Length), Body/binary>>,
- Handshake = decode_handshake(Version, Type, Body),
- get_tls_handshake_aux(Version, Rest, [{Handshake,Raw} | Acc]);
-get_tls_handshake_aux(_Version, Data, Acc) ->
+ Handshake = decode_handshake(Version, Type, Body, V2Hello),
+ get_tls_handshake_aux(Version, Rest, Opts, [{Handshake,Raw} | Acc]);
+get_tls_handshake_aux(_Version, Data, _, Acc) ->
{lists:reverse(Acc), Data}.
-decode_handshake(_, ?HELLO_REQUEST, <<>>) ->
+decode_handshake(_, ?HELLO_REQUEST, <<>>, _) ->
#hello_request{};
%% Client hello v2.
%% The server must be able to receive such messages, from clients that
%% are willing to use ssl v3 or higher, but have ssl v2 compatibility.
decode_handshake(_Version, ?CLIENT_HELLO, <<?BYTE(Major), ?BYTE(Minor),
- ?UINT16(CSLength), ?UINT16(0),
- ?UINT16(CDLength),
- CipherSuites:CSLength/binary,
- ChallengeData:CDLength/binary>>) ->
+ ?UINT16(CSLength), ?UINT16(0),
+ ?UINT16(CDLength),
+ CipherSuites:CSLength/binary,
+ ChallengeData:CDLength/binary>>, true) ->
#client_hello{client_version = {Major, Minor},
random = ssl_v2:client_random(ChallengeData, CDLength),
session_id = 0,
@@ -209,12 +209,18 @@ decode_handshake(_Version, ?CLIENT_HELLO, <<?BYTE(Major), ?BYTE(Minor),
compression_methods = [?NULL],
extensions = #hello_extensions{}
};
+decode_handshake(_Version, ?CLIENT_HELLO, <<?BYTE(_), ?BYTE(_),
+ ?UINT16(CSLength), ?UINT16(0),
+ ?UINT16(CDLength),
+ _CipherSuites:CSLength/binary,
+ _ChallengeData:CDLength/binary>>, false) ->
+ throw(?ALERT_REC(?FATAL, ?PROTOCOL_VERSION, ssl_v2_client_hello_no_supported));
decode_handshake(_Version, ?CLIENT_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary,
- ?BYTE(SID_length), Session_ID:SID_length/binary,
- ?UINT16(Cs_length), CipherSuites:Cs_length/binary,
- ?BYTE(Cm_length), Comp_methods:Cm_length/binary,
- Extensions/binary>>) ->
-
+ ?BYTE(SID_length), Session_ID:SID_length/binary,
+ ?UINT16(Cs_length), CipherSuites:Cs_length/binary,
+ ?BYTE(Cm_length), Comp_methods:Cm_length/binary,
+ Extensions/binary>>, _) ->
+
DecodedExtensions = ssl_handshake:decode_hello_extensions({client, Extensions}),
#client_hello{
@@ -226,7 +232,7 @@ decode_handshake(_Version, ?CLIENT_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:3
extensions = DecodedExtensions
};
-decode_handshake(Version, Tag, Msg) ->
+decode_handshake(Version, Tag, Msg, _) ->
ssl_handshake:decode_handshake(Version, Tag, Msg).
enc_handshake(#hello_request{}, _Version) ->