aboutsummaryrefslogtreecommitdiffstats
path: root/lib/ssl/src/ssl_connection.erl
diff options
context:
space:
mode:
Diffstat (limited to 'lib/ssl/src/ssl_connection.erl')
-rw-r--r--lib/ssl/src/ssl_connection.erl194
1 files changed, 115 insertions, 79 deletions
diff --git a/lib/ssl/src/ssl_connection.erl b/lib/ssl/src/ssl_connection.erl
index 5b754c16bc..0f0072ba34 100644
--- a/lib/ssl/src/ssl_connection.erl
+++ b/lib/ssl/src/ssl_connection.erl
@@ -41,7 +41,7 @@
socket_control/4, socket_control/5]).
%% User Events
--export([send/2, recv/3, close/1, shutdown/2,
+-export([send/2, recv/3, close/2, shutdown/2,
new_user/2, get_opts/2, set_opts/2, session_info/1,
peer_certificate/1, renegotiation/1, negotiated_protocol/1, prf/5,
connection_information/1
@@ -171,18 +171,19 @@ connection_information(Pid) when is_pid(Pid) ->
sync_send_all_state_event(Pid, connection_information).
%%--------------------------------------------------------------------
--spec close(pid()) -> ok | {error, reason()}.
+-spec close(pid(), {close, Timeout::integer() |
+ {NewController::pid(), Timeout::integer()}}) ->
+ ok | {ok, port()} | {error, reason()}.
%%
%% Description: Close an ssl connection
%%--------------------------------------------------------------------
-close(ConnectionPid) ->
- case sync_send_all_state_event(ConnectionPid, close) of
+close(ConnectionPid, How) ->
+ case sync_send_all_state_event(ConnectionPid, How) of
{error, closed} ->
ok;
Other ->
Other
end.
-
%%--------------------------------------------------------------------
-spec shutdown(pid(), atom()) -> ok | {error, reason()}.
%%
@@ -303,13 +304,9 @@ hello(#hello_request{}, #state{role = client} = State0, Connection) ->
{Record, State} = Connection:next_record(State0),
Connection:next_state(hello, hello, Record, State);
-hello({common_client_hello, Type, ServerHelloExt, NegotiatedHashSign},
+hello({common_client_hello, Type, ServerHelloExt},
State, Connection) ->
- do_server_hello(Type, ServerHelloExt,
- %% Note NegotiatedHashSign is only negotiated for real if
- %% if TLS version is at least TLS-1.2
- State#state{hashsign_algorithm = NegotiatedHashSign}, Connection);
-
+ do_server_hello(Type, ServerHelloExt, State, Connection);
hello(timeout, State, _) ->
{next_state, hello, State, hibernate};
@@ -441,7 +438,8 @@ certify(#server_key_exchange{exchange_keys = Keys},
Alg == srp_dss; Alg == srp_rsa; Alg == srp_anon ->
Params = ssl_handshake:decode_server_key(Keys, Alg, Version),
- HashSign = negotiated_hashsign(Params#server_key_params.hashsign, Alg, Version),
+ %% Use negotiated value if TLS-1.2 otherwhise return default
+ HashSign = negotiated_hashsign(Params#server_key_params.hashsign, Alg, PubKeyInfo, Version),
case is_anonymous(Alg) of
true ->
calculate_secret(Params#server_key_params.params,
@@ -463,11 +461,18 @@ certify(#server_key_exchange{} = Msg,
certify(#certificate_request{hashsign_algorithms = HashSigns},
#state{session = #session{own_certificate = Cert},
- negotiated_version = Version} = State0, Connection) ->
- HashSign = ssl_handshake:select_hashsign(HashSigns, Cert, Version),
- {Record, State} = Connection:next_record(State0#state{client_certificate_requested = true}),
- Connection:next_state(certify, certify, Record,
- State#state{cert_hashsign_algorithm = HashSign});
+ key_algorithm = KeyExAlg,
+ ssl_options = #ssl_options{signature_algs = SupportedHashSigns},
+ negotiated_version = Version} = State0, Connection) ->
+
+ case ssl_handshake:select_hashsign(HashSigns, Cert, KeyExAlg, SupportedHashSigns, Version) of
+ #alert {} = Alert ->
+ Connection:handle_own_alert(Alert, Version, certify, State0);
+ NegotiatedHashSign ->
+ {Record, State} = Connection:next_record(State0#state{client_certificate_requested = true}),
+ Connection:next_state(certify, certify, Record,
+ State#state{cert_hashsign_algorithm = NegotiatedHashSign})
+ end;
%% PSK and RSA_PSK might bypass the Server-Key-Exchange
certify(#server_hello_done{},
@@ -575,13 +580,15 @@ cipher(#hello_request{}, State0, Connection) ->
cipher(#certificate_verify{signature = Signature, hashsign_algorithm = CertHashSign},
#state{role = server,
- public_key_info = {Algo, _, _} =PublicKeyInfo,
+ key_algorithm = KexAlg,
+ public_key_info = PublicKeyInfo,
negotiated_version = Version,
session = #session{master_secret = MasterSecret},
tls_handshake_history = Handshake
} = State0, Connection) ->
-
- HashSign = ssl_handshake:select_hashsign_algs(CertHashSign, Algo, Version),
+
+ %% Use negotiated value if TLS-1.2 otherwhise return default
+ HashSign = negotiated_hashsign(CertHashSign, KexAlg, PublicKeyInfo, Version),
case ssl_handshake:certificate_verify(Signature, PublicKeyInfo,
Version, HashSign, MasterSecret, Handshake) of
valid ->
@@ -706,12 +713,12 @@ handle_sync_event({start, Timeout}, StartFrom, StateName, #state{role = Role, s
{stop, normal, {error, Error}, State0}
end;
-handle_sync_event(close, _, StateName, #state{protocol_cb = Connection} = State) ->
- %% Run terminate before returning
- %% so that the reuseaddr inet-option will work
- %% as intended.
- (catch Connection:terminate(user_close, StateName, State)),
- {stop, normal, ok, State#state{terminated = true}};
+handle_sync_event({close, _} = Close, _, StateName, #state{protocol_cb = Connection} = State) ->
+ %% Run terminate before returning so that the reuseaddr
+ %% inet-option and possible downgrade will work as intended.
+ Result = Connection:terminate(Close, StateName, State),
+ {stop, normal, Result, State#state{terminated = true}};
+
handle_sync_event({shutdown, How0}, _, StateName,
#state{transport_cb = Transport,
negotiated_version = Version,
@@ -814,7 +821,8 @@ handle_sync_event({prf, Secret, Label, Seed, WantedLength}, _, StateName,
SecParams = ConnectionState#connection_state.security_parameters,
#security_parameters{master_secret = MasterSecret,
client_random = ClientRandom,
- server_random = ServerRandom} = SecParams,
+ server_random = ServerRandom,
+ prf_algorithm = PRFAlgorithm} = SecParams,
Reply = try
SecretToUse = case Secret of
_ when is_binary(Secret) -> Secret;
@@ -825,7 +833,7 @@ handle_sync_event({prf, Secret, Label, Seed, WantedLength}, _, StateName,
(client_random, Acc) -> [ClientRandom|Acc];
(server_random, Acc) -> [ServerRandom|Acc]
end, [], Seed)),
- ssl_handshake:prf(Version, SecretToUse, Label, SeedToUse, WantedLength)
+ ssl_handshake:prf(Version, PRFAlgorithm, SecretToUse, Label, SeedToUse, WantedLength)
catch
exit:_ -> {error, badarg};
error:Reason -> {error, Reason}
@@ -835,15 +843,22 @@ handle_sync_event(session_info, _, StateName,
#state{session = #session{session_id = Id,
cipher_suite = Suite}} = State) ->
{reply, [{session_id, Id},
- {cipher_suite, ssl:suite_definition(Suite)}],
+ {cipher_suite, ssl_cipher:erl_suite_definition(Suite)}],
StateName, State, get_timeout(State)};
handle_sync_event(peer_certificate, _, StateName,
#state{session = #session{peer_certificate = Cert}}
= State) ->
{reply, {ok, Cert}, StateName, State, get_timeout(State)};
-handle_sync_event(connection_information, _, StateName, #state{sni_hostname = SNIHostname, session = #session{cipher_suite = CipherSuite}, negotiated_version = Version} = State) ->
- {reply, {ok, [{protocol, tls_record:protocol_version(Version)}, {cipher_suite, ssl:suite_definition(CipherSuite)}, {sni_hostname, SNIHostname}]}, StateName, State, get_timeout(State)}.
+handle_sync_event(connection_information, _, StateName, State) ->
+ Info = connection_info(State),
+ {reply, {ok, Info}, StateName, State, get_timeout(State)}.
+connection_info(#state{sni_hostname = SNIHostname,
+ session = #session{cipher_suite = CipherSuite},
+ negotiated_version = Version, ssl_options = Opts}) ->
+ [{protocol, tls_record:protocol_version(Version)},
+ {cipher_suite, ssl_cipher:erl_suite_definition(CipherSuite)},
+ {sni_hostname, SNIHostname}] ++ ssl_options_list(Opts).
handle_info({ErrorTag, Socket, econnaborted}, StateName,
#state{socket = Socket, transport_cb = Transport,
@@ -901,41 +916,46 @@ terminate(_, _, #state{terminated = true}) ->
%% we want to guarantee that Transport:close has been called
%% when ssl:close/1 returns.
ok;
-terminate({shutdown, transport_closed}, StateName, #state{send_queue = SendQueue,
- renegotiation = Renegotiate} = State) ->
- handle_unrecv_data(StateName, State),
+terminate({shutdown, transport_closed} = Reason,
+ _StateName, #state{send_queue = SendQueue, protocol_cb = Connection,
+ socket = Socket, transport_cb = Transport,
+ renegotiation = Renegotiate} = State) ->
handle_trusted_certs_db(State),
notify_senders(SendQueue),
- notify_renegotiater(Renegotiate);
-
-terminate({shutdown, own_alert}, _StateName, #state{send_queue = SendQueue,
- renegotiation = Renegotiate} = State) ->
+ notify_renegotiater(Renegotiate),
+ Connection:close(Reason, Socket, Transport, undefined, undefined);
+terminate({shutdown, own_alert}, _StateName, #state{send_queue = SendQueue, protocol_cb = Connection,
+ socket = Socket, transport_cb = Transport,
+ renegotiation = Renegotiate} = State) ->
handle_trusted_certs_db(State),
notify_senders(SendQueue),
- notify_renegotiater(Renegotiate);
+ notify_renegotiater(Renegotiate),
+ case application:get_env(ssl, alert_timeout) of
+ {ok, Timeout} when is_integer(Timeout) ->
+ Connection:close({timeout, Timeout}, Socket, Transport, undefined, undefined);
+ _ ->
+ Connection:close({timeout, ?DEFAULT_TIMEOUT}, Socket, Transport, undefined, undefined)
+ end;
terminate(Reason, connection, #state{negotiated_version = Version,
protocol_cb = Connection,
- connection_states = ConnectionStates,
+ connection_states = ConnectionStates0,
+ ssl_options = #ssl_options{padding_check = Check},
transport_cb = Transport, socket = Socket,
send_queue = SendQueue, renegotiation = Renegotiate} = State) ->
handle_trusted_certs_db(State),
notify_senders(SendQueue),
notify_renegotiater(Renegotiate),
- BinAlert = terminate_alert(Reason, Version, ConnectionStates),
+ {BinAlert, ConnectionStates} = terminate_alert(Reason, Version, ConnectionStates0),
Transport:send(Socket, BinAlert),
- case Connection of
- tls_connection ->
- tls_connection:workaround_transport_delivery_problems(Socket, Transport);
- _ ->
- ok
- end;
-terminate(_Reason, _StateName, #state{transport_cb = Transport,
+ Connection:close(Reason, Socket, Transport, ConnectionStates, Check);
+
+terminate(Reason, _StateName, #state{transport_cb = Transport, protocol_cb = Connection,
socket = Socket, send_queue = SendQueue,
renegotiation = Renegotiate} = State) ->
handle_trusted_certs_db(State),
notify_senders(SendQueue),
notify_renegotiater(Renegotiate),
- Transport:close(Socket).
+ Connection:close(Reason, Socket, Transport, undefined, undefined).
format_status(normal, [_, State]) ->
[{data, [{"StateData", State}]}];
@@ -968,7 +988,7 @@ ssl_config(Opts, Role, State) ->
{ok, Ref, CertDbHandle, FileRefHandle, CacheHandle, CRLDbInfo, OwnCert, Key, DHParams} =
ssl_config:init(Opts, Role),
Handshake = ssl_handshake:init_handshake_history(),
- TimeStamp = calendar:datetime_to_gregorian_seconds({date(), time()}),
+ TimeStamp = erlang:monotonic_time(),
Session = State#state.session,
State#state{tls_handshake_history = Handshake,
session = Session#session{own_certificate = OwnCert,
@@ -1435,7 +1455,8 @@ rsa_psk_key_exchange(Version, PskIdentity, PremasterSecret, PublicKeyInfo = {Alg
rsa_psk_key_exchange(_, _, _, _) ->
throw (?ALERT_REC(?FATAL,?HANDSHAKE_FAILURE)).
-request_client_cert(#state{ssl_options = #ssl_options{verify = verify_peer},
+request_client_cert(#state{ssl_options = #ssl_options{verify = verify_peer,
+ signature_algs = SupportedHashSigns},
connection_states = ConnectionStates0,
cert_db = CertDbHandle,
cert_db_ref = CertDbRef,
@@ -1443,7 +1464,9 @@ request_client_cert(#state{ssl_options = #ssl_options{verify = verify_peer},
#connection_state{security_parameters =
#security_parameters{cipher_suite = CipherSuite}} =
ssl_record:pending_connection_state(ConnectionStates0, read),
- Msg = ssl_handshake:certificate_request(CipherSuite, CertDbHandle, CertDbRef, Version),
+ HashSigns = ssl_handshake:available_signature_algs(SupportedHashSigns, Version, [Version]),
+ Msg = ssl_handshake:certificate_request(CipherSuite, CertDbHandle, CertDbRef,
+ HashSigns, Version),
State = Connection:send_handshake(Msg, State0),
State#state{client_certificate_requested = true};
@@ -1758,37 +1781,24 @@ get_timeout(#state{ssl_options=#ssl_options{hibernate_after = undefined}}) ->
get_timeout(#state{ssl_options=#ssl_options{hibernate_after = HibernateAfter}}) ->
HibernateAfter.
-terminate_alert(Reason, Version, ConnectionStates) when Reason == normal;
- Reason == user_close ->
- {BinAlert, _} = ssl_alert:encode(?ALERT_REC(?WARNING, ?CLOSE_NOTIFY),
- Version, ConnectionStates),
- BinAlert;
-terminate_alert({shutdown, _}, Version, ConnectionStates) ->
- {BinAlert, _} = ssl_alert:encode(?ALERT_REC(?WARNING, ?CLOSE_NOTIFY),
- Version, ConnectionStates),
- BinAlert;
+terminate_alert(normal, Version, ConnectionStates) ->
+ ssl_alert:encode(?ALERT_REC(?WARNING, ?CLOSE_NOTIFY),
+ Version, ConnectionStates);
+terminate_alert({Reason, _}, Version, ConnectionStates) when Reason == close;
+ Reason == shutdown ->
+ ssl_alert:encode(?ALERT_REC(?WARNING, ?CLOSE_NOTIFY),
+ Version, ConnectionStates);
terminate_alert(_, Version, ConnectionStates) ->
- {BinAlert, _} = ssl_alert:encode(?ALERT_REC(?FATAL, ?INTERNAL_ERROR),
- Version, ConnectionStates),
- BinAlert.
-
-handle_unrecv_data(StateName, #state{socket = Socket, transport_cb = Transport,
- protocol_cb = Connection} = State) ->
- ssl_socket:setopts(Transport, Socket, [{active, false}]),
- case Transport:recv(Socket, 0, 0) of
- {error, closed} ->
- ok;
- {ok, Data} ->
- Connection:handle_close_alert(Data, StateName, State)
- end.
+ ssl_alert:encode(?ALERT_REC(?FATAL, ?INTERNAL_ERROR),
+ Version, ConnectionStates).
handle_trusted_certs_db(#state{ssl_options = #ssl_options{cacertfile = <<>>, cacerts = []}}) ->
%% No trusted certs specified
ok;
handle_trusted_certs_db(#state{cert_db_ref = Ref,
cert_db = CertDb,
- ssl_options = #ssl_options{cacertfile = <<>>}}) ->
+ ssl_options = #ssl_options{cacertfile = <<>>}}) when CertDb =/= undefined ->
%% Certs provided as DER directly can not be shared
%% with other connections and it is safe to delete them when the connection ends.
ssl_pkix_db:remove_trusted_certs(Ref, CertDb);
@@ -1881,14 +1891,40 @@ make_premaster_secret({MajVer, MinVer}, rsa) ->
make_premaster_secret(_, _) ->
undefined.
-negotiated_hashsign(undefined, Alg, Version) ->
+negotiated_hashsign(undefined, KexAlg, PubKeyInfo, Version) ->
%% Not negotiated choose default
- case is_anonymous(Alg) of
+ case is_anonymous(KexAlg) of
true ->
{null, anon};
false ->
- ssl_handshake:select_hashsign_algs(Alg, Version)
+ {PubAlg, _, _} = PubKeyInfo,
+ ssl_handshake:select_hashsign_algs(undefined, PubAlg, Version)
end;
-negotiated_hashsign(HashSign = {_, _}, _, _) ->
+negotiated_hashsign(HashSign = {_, _}, _, _, _) ->
HashSign.
+ssl_options_list(SslOptions) ->
+ Fileds = record_info(fields, ssl_options),
+ Values = tl(tuple_to_list(SslOptions)),
+ ssl_options_list(Fileds, Values, []).
+
+ssl_options_list([],[], Acc) ->
+ lists:reverse(Acc);
+%% Skip internal options, only return user options
+ssl_options_list([protocol | Keys], [_ | Values], Acc) ->
+ ssl_options_list(Keys, Values, Acc);
+ssl_options_list([erl_dist | Keys], [_ | Values], Acc) ->
+ ssl_options_list(Keys, Values, Acc);
+ssl_options_list([renegotiate_at | Keys], [_ | Values], Acc) ->
+ ssl_options_list(Keys, Values, Acc);
+ssl_options_list([ciphers = Key | Keys], [Value | Values], Acc) ->
+ ssl_options_list(Keys, Values,
+ [{Key, lists:map(
+ fun(Suite) ->
+ ssl_cipher:erl_suite_definition(Suite)
+ end, Value)}
+ | Acc]);
+ssl_options_list([Key | Keys], [Value | Values], Acc) ->
+ ssl_options_list(Keys, Values, [{Key, Value} | Acc]).
+
+