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.erl269
1 files changed, 103 insertions, 166 deletions
diff --git a/lib/ssl/src/ssl_connection.erl b/lib/ssl/src/ssl_connection.erl
index e04e2dea42..8ff001b172 100644
--- a/lib/ssl/src/ssl_connection.erl
+++ b/lib/ssl/src/ssl_connection.erl
@@ -87,7 +87,7 @@
user_data_buffer, % binary()
%% tls_buffer, % Keeps a lookahead one packet if available
log_alert, % boolean()
- renegotiation, % boolean()
+ renegotiation, % {boolean(), From | internal | peer}
recv_during_renegotiation, %boolean()
send_queue % queue()
}).
@@ -222,7 +222,7 @@ peer_certificate(ConnectionPid) ->
%% Description:
%%--------------------------------------------------------------------
renegotiation(ConnectionPid) ->
- send_all_state_event(ConnectionPid, renegotiate).
+ sync_send_all_state_event(ConnectionPid, renegotiate).
%%====================================================================
%% ssl_connection_sup API
@@ -383,32 +383,33 @@ abbreviated(Finished = #finished{},
#state{role = server,
negotiated_version = Version,
tls_handshake_hashes = Hashes,
- session = #session{master_secret = MasterSecret}} = State) ->
+ session = #session{master_secret = MasterSecret}} =
+ State0) ->
case ssl_handshake:verify_connection(Version, Finished, client,
MasterSecret, Hashes) of
verified ->
- ack_connection(State),
+ State = ack_connection(State0),
next_state_connection(State);
#alert{} = Alert ->
- handle_own_alert(Alert, Version, abbreviated, State),
- {stop, normal, State}
+ handle_own_alert(Alert, Version, abbreviated, State0),
+ {stop, normal, State0}
end;
abbreviated(Finished = #finished{},
#state{role = client, tls_handshake_hashes = Hashes0,
session = #session{master_secret = MasterSecret},
- negotiated_version = Version} = State) ->
+ negotiated_version = Version} = State0) ->
case ssl_handshake:verify_connection(Version, Finished, server,
MasterSecret, Hashes0) of
verified ->
- {ConnectionStates, Hashes} = finalize_client_handshake(State),
- ack_connection(State),
+ {ConnectionStates, Hashes} = finalize_client_handshake(State0),
+ State = ack_connection(State0),
next_state_connection(State#state{tls_handshake_hashes = Hashes,
connection_states =
ConnectionStates});
#alert{} = Alert ->
- handle_own_alert(Alert, Version, abbreviated, State),
- {stop, normal, State}
+ handle_own_alert(Alert, Version, abbreviated, State0),
+ {stop, normal, State0}
end.
certify(socket_control, #state{role = server} = State) ->
@@ -435,11 +436,13 @@ certify(#certificate{asn1_certificates = []},
certify(#certificate{} = Cert,
#state{negotiated_version = Version,
+ role = Role,
cert_db_ref = CertDbRef,
ssl_options = Opts} = State) ->
case ssl_handshake:certify(Cert, CertDbRef, Opts#ssl_options.depth,
Opts#ssl_options.verify,
- Opts#ssl_options.verify_fun) of
+ Opts#ssl_options.verify_fun,
+ Opts#ssl_options.validate_extensions_fun, Role) of
{PeerCert, PublicKeyInfo} ->
handle_peer_cert(PeerCert, PublicKeyInfo,
State#state{client_certificate_requested = false});
@@ -464,7 +467,7 @@ certify(#server_key_exchange{} = KeyExchangeMsg,
certify(#server_key_exchange{},
State = #state{role = client, negotiated_version = Version,
key_algorithm = Alg})
- when Alg == rsa; Alg == dh_dss; Alg == dh_rsa ->
+ when Alg == rsa; Alg == dh_dss; Alg == dh_rsa ->
Alert = ?ALERT_REC(?FATAL, ?UNEXPECTED_MESSAGE),
handle_own_alert(Alert, Version, certify_server_key_exchange, State),
{stop, normal, State};
@@ -606,19 +609,19 @@ cipher(#certificate_verify{signature = Signature},
end;
cipher(#finished{} = Finished,
- State = #state{negotiated_version = Version,
- host = Host,
- port = Port,
- role = Role,
- session = #session{master_secret = MasterSecret}
- = Session0,
- tls_handshake_hashes = Hashes}) ->
-
+ #state{negotiated_version = Version,
+ host = Host,
+ port = Port,
+ role = Role,
+ session = #session{master_secret = MasterSecret}
+ = Session0,
+ tls_handshake_hashes = Hashes} = State0) ->
+
case ssl_handshake:verify_connection(Version, Finished,
opposite_role(Role),
MasterSecret, Hashes) of
verified ->
- ack_connection(State),
+ State = ack_connection(State0),
Session = register_session(Role, Host, Port, Session0),
case Role of
client ->
@@ -627,15 +630,15 @@ cipher(#finished{} = Finished,
{NewConnectionStates, NewHashes} =
finalize_server_handshake(State#state{
session = Session}),
- NewState =
- State#state{connection_states = NewConnectionStates,
- session = Session,
- tls_handshake_hashes = NewHashes},
- next_state_connection(NewState)
+ next_state_connection(State#state{connection_states =
+ NewConnectionStates,
+ session = Session,
+ tls_handshake_hashes =
+ NewHashes})
end;
#alert{} = Alert ->
- handle_own_alert(Alert, Version, cipher, State),
- {stop, normal, State}
+ handle_own_alert(Alert, Version, cipher, State0),
+ {stop, normal, State0}
end.
connection(socket_control, #state{role = server} = State) ->
@@ -655,8 +658,7 @@ connection(#hello_request{}, State = #state{host = Host, port = Port,
Transport:send(Socket, BinMsg),
{next_state, hello, next_record(State#state{connection_states =
ConnectionStates1,
- tls_handshake_hashes = Hashes1,
- renegotiation = true})};
+ tls_handshake_hashes = Hashes1})};
connection(#client_hello{} = Hello, #state{role = server} = State) ->
hello(Hello, State).
@@ -681,16 +683,17 @@ handle_event(#ssl_tls{type = ?HANDSHAKE, fragment = Data},
%% This message should not be included in handshake
%% message hashes. Starts new handshake (renegotiation)
Hs0 = ssl_handshake:init_hashes(),
- ?MODULE:SName(Packet, State#state{tls_handshake_hashes=Hs0});
+ ?MODULE:SName(Packet, State#state{tls_handshake_hashes=Hs0,
+ renegotiation = {true, peer}});
({#hello_request{} = Packet, _}, {next_state, SName, State}) ->
%% This message should not be included in handshake
- %% message hashes. If allready in negotiation it will be ignored!
+ %% message hashes. Already in negotiation so it will be ignored!
?MODULE:SName(Packet, State);
({#client_hello{} = Packet, Raw}, {next_state, connection = SName, State}) ->
Hs0 = ssl_handshake:init_hashes(),
Hs1 = ssl_handshake:update_hashes(Hs0, Raw),
?MODULE:SName(Packet, State#state{tls_handshake_hashes=Hs1,
- renegotiation = true});
+ renegotiation = {true, peer}});
({Packet, Raw}, {next_state, SName, State = #state{tls_handshake_hashes=Hs0}}) ->
Hs1 = ssl_handshake:update_hashes(Hs0, Raw),
?MODULE:SName(Packet, State#state{tls_handshake_hashes=Hs1});
@@ -758,18 +761,23 @@ handle_event(#alert{level = ?WARNING, description = ?CLOSE_NOTIFY} = Alert,
_, #state{from = From, role = Role} = State) ->
alert_user(From, Alert, Role),
{stop, normal, State};
-handle_event(#alert{level = ?WARNING} = Alert, StateName,
+
+handle_event(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert, StateName,
+ #state{log_alert = Log, renegotiation = {true, internal}} = State) ->
+ log_alert(Log, StateName, Alert),
+ {stop, normal, State};
+
+handle_event(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert, StateName,
+ #state{log_alert = Log, renegotiation = {true, From}} = State) ->
+ log_alert(Log, StateName, Alert),
+ gen_fsm:reply(From, {error, renegotiation_rejected}),
+ {next_state, connection, next_record(State)};
+
+handle_event(#alert{level = ?WARNING, description = ?USER_CANCELED} = Alert, StateName,
#state{log_alert = Log} = State) ->
log_alert(Log, StateName, Alert),
-%%TODO: Could be user_canceled or no_negotiation should the latter be
- %% treated as fatal?!
- {next_state, StateName, next_record(State)};
-
-handle_event(renegotiate, connection, State) ->
- renegotiate(State);
-handle_event(renegotiate, StateName, State) ->
- %% Already in renegotiate ignore
- {next_state, StateName, State}.
+ {next_state, StateName, next_record(State)}.
+
%%--------------------------------------------------------------------
%% Function:
%% handle_sync_event(Event, From, StateName,
@@ -812,7 +820,8 @@ handle_sync_event({application_data, Data0}, From, connection,
ok
end,
renegotiate(State#state{connection_states = ConnectionStates,
- send_queue = queue:in_r({From, RestData}, SendQueue)})
+ send_queue = queue:in_r({From, RestData}, SendQueue),
+ renegotiation = {true, internal}})
end
catch throw:Error ->
{reply, Error, connection, State}
@@ -892,6 +901,12 @@ handle_sync_event({set_opts, Opts0}, _From, StateName,
end
end;
+handle_sync_event(renegotiate, From, connection, State) ->
+ renegotiate(State#state{renegotiation = {true, From}});
+
+handle_sync_event(renegotiate, _, StateName, State) ->
+ {reply, {error, already_renegotiating}, StateName, State};
+
handle_sync_event(info, _, StateName,
#state{negotiated_version = Version,
session = #session{cipher_suite = Suite}} = State) ->
@@ -940,34 +955,6 @@ handle_info({Protocol, _, Data}, StateName, State =
{stop, normal, State}
end;
-%% %% This is the code for {packet,ssl} removed because it was slower
-%% %% than handling it in erlang.
-%% handle_info(Data = #ssl_tls{}, StateName,
-%% State = #state{tls_buffer = Buffer,
-%% socket = Socket,
-%% connection_states = ConnectionStates0}) ->
-%% case Buffer of
-%% buffer ->
-%% {next_state, StateName, State#state{tls_buffer = [Data]}};
-%% continue ->
-%% inet:setopts(Socket, [{active,once}]),
-%% {Plain, ConnectionStates} =
-%% ssl_record:decode_cipher_text(Data, ConnectionStates0),
-%% gen_fsm:send_all_state_event(self(), Plain),
-%% {next_state, StateName,
-%% State#state{tls_buffer = buffer,
-%% connection_states = ConnectionStates}};
-%% List when is_list(List) ->
-%% {next_state, StateName,
-%% State#state{tls_buffer = Buffer ++ [Data]}}
-%% end;
-
-%% handle_info(CloseMsg = {_, Socket}, StateName0,
-%% #state{socket = Socket,tls_buffer = [Msg]} = State0) ->
-%% %% Hmm we have a ssl_tls msg buffered, handle that first
-%% %% and it proberbly is a close alert
-%% {next_state, StateName0, State0#state{tls_buffer=[Msg,{ssl_close,CloseMsg}]}};
-
handle_info({CloseTag, Socket}, _StateName,
#state{socket = Socket, close_tag = CloseTag,
negotiated_version = Version, host = Host,
@@ -1001,18 +988,23 @@ handle_info(A, StateName, State) ->
%% necessary cleaning up. When it returns, the gen_fsm terminates with
%% Reason. The return value is ignored.
%%--------------------------------------------------------------------
-terminate(_Reason, connection, _S=#state{negotiated_version = Version,
+terminate(_Reason, connection, #state{negotiated_version = Version,
connection_states = ConnectionStates,
transport_cb = Transport,
- socket = Socket}) ->
+ socket = Socket, send_queue = SendQueue,
+ renegotiation = Renegotiate}) ->
+ notify_senders(SendQueue),
+ notify_renegotiater(Renegotiate),
{BinAlert, _} = encode_alert(?ALERT_REC(?WARNING,?CLOSE_NOTIFY),
Version, ConnectionStates),
Transport:send(Socket, BinAlert),
Transport:close(Socket);
-terminate(_Reason, _StateName, _S=#state{transport_cb = Transport,
- socket = Socket}) ->
- Transport:close(Socket),
- ok.
+terminate(_Reason, _StateName, #state{transport_cb = Transport,
+ socket = Socket, send_queue = SendQueue,
+ renegotiation = Renegotiate}) ->
+ notify_senders(SendQueue),
+ notify_renegotiater(Renegotiate),
+ Transport:close(Socket).
%%--------------------------------------------------------------------
%% Function:
@@ -1161,37 +1153,9 @@ sync_send_all_state_event(FsmPid, Event, Timeout) ->
alert_event(Alert) ->
send_all_state_event(self(), Alert).
-
-%% TODO: This clause is not yet supported. Do we need
-%% to support it, not supported by openssl!
-handle_peer_cert(PeerCert, {Algorithm, _, _} = PublicKeyInfo,
- #state{negotiated_version = Version,
- session = Session} =
- State0) when Algorithm == dh_rsa;
- Algorithm == dh_dss ->
- case public_key:pkix_is_fixed_dh_cert(PeerCert) of
- true ->
- %% TODO: extract DH-params from cert and save
- %% Keys and SharedSecret in state.
- %% {P, G, ServerPublicDhKey}= extract .....
- %% Keys = {ClientDhPublicKey, _} =
- %% public_key:gen_key(#'DHParameter'{prime = P, base = G}),
- %%SharedSecret = dh_shared_secret(ServerPublicDhKey,
- %% ClientDhPublicKey,
- %% [P, G]),
- %% ssl_handshake:master_secret ...
- State = State0#state{session =
- Session#session{peer_certificate
- = PeerCert},
- public_key_info = PublicKeyInfo},
- {next_state, certify, next_record(State)};
-
- false ->
- Alert = ?ALERT_REC(?FATAL,?HANDSHAKE_FAILURE),
- handle_own_alert(Alert, Version, certify_certificate, State0),
- {stop, normal, State0}
- end;
-
+%% We do currently not support cipher suites that use fixed DH.
+%% If we want to implement that we should add a code
+%% here to extract DH parameters form cert.
handle_peer_cert(PeerCert, PublicKeyInfo,
#state{session = Session} = State0) ->
State = State0#state{session =
@@ -1374,11 +1338,11 @@ key_exchange(#state{role = server, key_algorithm = Algo} = State)
Algo == dh_rsa ->
State;
-key_exchange(#state{role = server, key_algorithm = rsa_export} = State) ->
+%key_exchange(#state{role = server, key_algorithm = rsa_export} = State) ->
%% TODO when the public key in the server certificate is
%% less than or equal to 512 bits in length dont send key_exchange
%% but do it otherwise
- State;
+% State;
key_exchange(#state{role = server, key_algorithm = Algo,
diffie_hellman_params = Params,
@@ -1411,20 +1375,10 @@ key_exchange(#state{role = server, key_algorithm = Algo,
diffie_hellman_keys = Keys,
tls_handshake_hashes = Hashes1};
-%% TODO: Not yet supported should be by default disabled when supported.
-%% key_exchange(#state{role = server, key_algorithm = dh_anon,
-%% connection_states = ConnectionStates0,
-%% negotiated_version = Version,
-%% tls_handshake_hashes = Hashes0,
-%% socket = Socket,
-%% transport_cb = Transport
-%% } = State) ->
-%% Msg = ssl_handshake:key_exchange(server, anonymous),
-%% {BinMsg, ConnectionStates1, Hashes1} =
-%% encode_handshake(Msg, Version, ConnectionStates0, Hashes0),
-%% Transport:send(Socket, BinMsg),
-%% State#state{connection_states = ConnectionStates1,
-%% tls_handshake_hashes = Hashes1};
+
+%% key_algorithm = dh_anon is not supported. Should be by default disabled
+%% if support is implemented and then we need a key_exchange clause for it
+%% here.
key_exchange(#state{role = client,
connection_states = ConnectionStates0,
@@ -1609,9 +1563,6 @@ handle_server_key(
?ALERT_REC(?FATAL,?HANDSHAKE_FAILURE)
end.
-%%handle_clinet_key(_KeyExchangeMsg, State) ->
-%% State.
-
verify_dh_params(Signed, Hash, {?rsaEncryption, PubKey, _PubKeyparams}) ->
case public_key:decrypt_public(Signed, PubKey,
[{rsa_pad, rsa_pkcs1_padding}]) of
@@ -1815,34 +1766,6 @@ opposite_role(server) ->
send_user(Pid, Msg) ->
Pid ! Msg.
-%% %% This is the code for {packet,ssl} removed because it was slower
-%% %% than handling it in erlang.
-%% next_record(#state{socket = Socket,
-%% tls_buffer = [Msg|Rest],
-%% connection_states = ConnectionStates0} = State) ->
-%% Buffer =
-%% case Rest of
-%% [] ->
-%% inet:setopts(Socket, [{active,once}]),
-%% buffer;
-%% _ -> Rest
-%% end,
-%% case Msg of
-%% #ssl_tls{} ->
-%% {Plain, ConnectionStates} =
-%% ssl_record:decode_cipher_text(Msg, ConnectionStates0),
-%% gen_fsm:send_all_state_event(self(), Plain),
-%% State#state{tls_buffer=Buffer, connection_states = ConnectionStates};
-%% {ssl_close, Msg} ->
-%% self() ! Msg,
-%% State#state{tls_buffer=Buffer}
-%% end;
-%% next_record(#state{socket = Socket, tls_buffer = undefined} = State) ->
-%% inet:setopts(Socket, [{active,once}]),
-%% State#state{tls_buffer=continue};
-%% next_record(State) ->
-%% State#state{tls_buffer=continue}.
-
next_record(#state{tls_cipher_texts = [], socket = Socket} = State) ->
inet:setopts(Socket, [{active,once}]),
State;
@@ -1887,7 +1810,8 @@ next_state_connection(#state{send_queue = Queue0,
ok
end,
renegotiate(State#state{connection_states = ConnectionStates,
- send_queue = queue:in_r({From, RestData}, Queue)})
+ send_queue = queue:in_r({From, RestData}, Queue),
+ renegotiation = {true, internal}})
end;
{empty, Queue0} ->
next_state_is_connection(State)
@@ -1952,7 +1876,7 @@ initial_state(Role, Host, Port, Socket, {SSLOptions, SocketOptions}, User,
user_data_buffer = <<>>,
log_alert = true,
session_cache_cb = SessionCacheCb,
- renegotiation = false,
+ renegotiation = {false, first},
recv_during_renegotiation = false,
send_queue = queue:new()
}.
@@ -2065,11 +1989,16 @@ mpint_binary(Binary) ->
<<?UINT32(Size), Binary/binary>>.
-ack_connection(#state{renegotiation = true}) ->
- ok;
-ack_connection(#state{renegotiation = false, from = From}) ->
- gen_fsm:reply(From, connected).
-
+ack_connection(#state{renegotiation = {true, Initiater}} = State)
+ when Initiater == internal;
+ Initiater == peer ->
+ State#state{renegotiation = undefined};
+ack_connection(#state{renegotiation = {true, From}} = State) ->
+ gen_fsm:reply(From, ok),
+ State#state{renegotiation = undefined};
+ack_connection(#state{renegotiation = {false, first}, from = From} = State) ->
+ gen_fsm:reply(From, connected),
+ State#state{renegotiation = undefined}.
renegotiate(#state{role = client} = State) ->
%% Handle same way as if server requested
@@ -2089,5 +2018,13 @@ renegotiate(#state{role = server,
Transport:send(Socket, BinMsg),
{next_state, hello, next_record(State#state{connection_states =
ConnectionStates,
- tls_handshake_hashes = Hs0,
- renegotiation = true})}.
+ tls_handshake_hashes = Hs0})}.
+notify_senders(SendQueue) ->
+ lists:foreach(fun({From, _}) ->
+ gen_fsm:reply(From, {error, closed})
+ end, queue:to_list(SendQueue)).
+
+notify_renegotiater({true, From}) when not is_atom(From) ->
+ gen_fsm:reply(From, {error, closed});
+notify_renegotiater(_) ->
+ ok.