diff options
-rw-r--r-- | lib/ssl/src/dtls_connection.erl | 206 | ||||
-rw-r--r-- | lib/ssl/src/ssl_connection.erl | 448 | ||||
-rw-r--r-- | lib/ssl/src/ssl_connection.hrl | 134 | ||||
-rw-r--r-- | lib/ssl/src/tls_connection.erl | 164 | ||||
-rw-r--r-- | lib/ssl/test/ssl_basic_SUITE.erl | 4 |
5 files changed, 526 insertions, 430 deletions
diff --git a/lib/ssl/src/dtls_connection.erl b/lib/ssl/src/dtls_connection.erl index 37719ad439..b145dd9298 100644 --- a/lib/ssl/src/dtls_connection.erl +++ b/lib/ssl/src/dtls_connection.erl @@ -141,14 +141,14 @@ next_record(#state{protocol_buffers = next_record(State#state{protocol_buffers = Buffers#protocol_buffers{dtls_cipher_texts = Rest}, connection_states = ConnectionStates}); -next_record(#state{role = server, - socket = {Listener, {Client, _}}} = State) -> +next_record(#state{static_env = #static_env{role = server, + socket = {Listener, {Client, _}}}} = State) -> dtls_packet_demux:active_once(Listener, Client, self()), {no_record, State}; -next_record(#state{role = client, - socket = {_Server, Socket} = DTLSSocket, - close_tag = CloseTag, - transport_cb = Transport} = State) -> +next_record(#state{static_env = #static_env{role = client, + socket = {_Server, Socket} = DTLSSocket, + close_tag = CloseTag, + transport_cb = Transport}} = State) -> case dtls_socket:setopts(Transport, Socket, [{active,once}]) of ok -> {no_record, State}; @@ -279,11 +279,13 @@ handle_common_event(internal, #ssl_tls{type = _Unknown}, StateName, State) -> %% Handshake handling %%==================================================================== -renegotiate(#state{role = client} = State, Actions) -> +renegotiate(#state{static_env = #static_env{role = client}} = State, Actions) -> %% Handle same way as if server requested %% the renegotiation - next_event(connection, no_record, State, [{next_event, internal, #hello_request{}} | Actions]); -renegotiate(#state{role = server} = State0, Actions) -> + {next_state, connection, State, + [{next_event, internal, #hello_request{}} | Actions]}; + +renegotiate(#state{static_env = #static_env{role = server}} = State0, Actions) -> HelloRequest = ssl_handshake:hello_request(), State1 = prepare_flight(State0), {State, MoreActions} = send_handshake(HelloRequest, State1), @@ -353,8 +355,8 @@ encode_alert(#alert{} = Alert, Version, ConnectionStates) -> dtls_record:encode_alert_record(Alert, Version, ConnectionStates). send_alert(Alert, #state{negotiated_version = Version, - socket = Socket, - transport_cb = Transport, + static_env = #static_env{socket = Socket, + transport_cb = Transport}, connection_states = ConnectionStates0} = State0) -> {BinMsg, ConnectionStates} = encode_alert(Alert, Version, ConnectionStates0), @@ -406,13 +408,15 @@ getopts(Transport, Socket, Tag) -> init(enter, _, State) -> {keep_state, State}; init({call, From}, {start, Timeout}, - #state{host = Host, port = Port, role = client, + #state{static_env = #static_env{host = Host, + port = Port, + role = client, + session_cache = Cache, + session_cache_cb = CacheCb}, ssl_options = SslOpts, session = #session{own_certificate = Cert} = Session0, connection_states = ConnectionStates0, - renegotiation = {Renegotiation, _}, - session_cache = Cache, - session_cache_cb = CacheCb + renegotiation = {Renegotiation, _} } = State0) -> Timer = ssl_connection:start_or_recv_cancel_timer(Timeout, From), Hello = dtls_handshake:client_hello(Host, Port, ConnectionStates0, SslOpts, @@ -422,15 +426,17 @@ init({call, From}, {start, Timeout}, HelloVersion = dtls_record:hello_version(Version, SslOpts#ssl_options.versions), State1 = prepare_flight(State0#state{negotiated_version = Version}), {State2, Actions} = send_handshake(Hello, State1#state{negotiated_version = HelloVersion}), - State = State2#state{negotiated_version = Version, %% Requested version - session = - Session0#session{session_id = Hello#client_hello.session_id}, - start_or_recv_from = From, - timer = Timer, - flight_state = {retransmit, ?INITIAL_RETRANSMIT_TIMEOUT} - }, - next_event(hello, no_record, State, Actions); -init({call, _} = Type, Event, #state{role = server, data_tag = udp} = State) -> + State3 = State2#state{negotiated_version = Version, %% Requested version + session = + Session0#session{session_id = Hello#client_hello.session_id}, + start_or_recv_from = From, + timer = Timer, + flight_state = {retransmit, ?INITIAL_RETRANSMIT_TIMEOUT} + }, + {Record, State} = next_record(State3), + next_event(hello, Record, State, Actions); +init({call, _} = Type, Event, #state{static_env = #static_env{role = server, + data_tag = udp}} = State) -> Result = gen_handshake(?FUNCTION_NAME, Type, Event, State#state{flight_state = {retransmit, ?INITIAL_RETRANSMIT_TIMEOUT}, protocol_specific = #{current_cookie_secret => dtls_v1:cookie_secret(), @@ -439,7 +445,7 @@ init({call, _} = Type, Event, #state{role = server, data_tag = udp} = State) -> max_ignored_alerts => 10}}), erlang:send_after(dtls_v1:cookie_timeout(), self(), new_cookie_secret), Result; -init({call, _} = Type, Event, #state{role = server} = State) -> +init({call, _} = Type, Event, #state{static_env = #static_env{role = server}} = State) -> %% I.E. DTLS over sctp gen_handshake(?FUNCTION_NAME, Type, Event, State#state{flight_state = reliable}); init(Type, Event, State) -> @@ -467,16 +473,16 @@ error(_, _, _) -> #state{}) -> gen_statem:state_function_result(). %%-------------------------------------------------------------------- -hello(enter, _, #state{role = server} = State) -> +hello(enter, _, #state{static_env = #static_env{role = server}} = State) -> {keep_state, State}; -hello(enter, _, #state{role = client} = State0) -> +hello(enter, _, #state{static_env = #static_env{role = client}} = State0) -> {State, Actions} = handle_flight_timer(State0), {keep_state, State, Actions}; hello(internal, #client_hello{cookie = <<>>, client_version = Version} = Hello, - #state{role = server, - transport_cb = Transport, - socket = Socket, + #state{static_env = #static_env{role = server, + transport_cb = Transport, + socket = Socket}, protocol_specific = #{current_cookie_secret := Secret}} = State0) -> {ok, {IP, Port}} = dtls_socket:peername(Transport, Socket), Cookie = dtls_handshake:cookie(Secret, IP, Port, Hello), @@ -488,18 +494,19 @@ hello(internal, #client_hello{cookie = <<>>, %% negotiated. VerifyRequest = dtls_handshake:hello_verify_request(Cookie, ?HELLO_VERIFY_REQUEST_VERSION), State1 = prepare_flight(State0#state{negotiated_version = Version}), - {State, Actions} = send_handshake(VerifyRequest, State1), - next_event(?FUNCTION_NAME, no_record, - State#state{tls_handshake_history = ssl_handshake:init_handshake_history()}, Actions); -hello(internal, #hello_verify_request{cookie = Cookie}, #state{role = client, - host = Host, port = Port, + {State2, Actions} = send_handshake(VerifyRequest, State1), + {Record, State} = next_record(State2), + next_event(?FUNCTION_NAME, Record, State#state{tls_handshake_history = ssl_handshake:init_handshake_history()}, Actions); +hello(internal, #hello_verify_request{cookie = Cookie}, #state{static_env = #static_env{role = client, + host = Host, + port = Port, + session_cache = Cache, + session_cache_cb = CacheCb}, ssl_options = SslOpts, session = #session{own_certificate = OwnCert} = Session0, connection_states = ConnectionStates0, - renegotiation = {Renegotiation, _}, - session_cache = Cache, - session_cache_cb = CacheCb + renegotiation = {Renegotiation, _} } = State0) -> Hello = dtls_handshake:client_hello(Host, Port, Cookie, ConnectionStates0, @@ -526,12 +533,13 @@ hello(internal, #server_hello{extensions = Extensions} = Hello, {next_state, user_hello, State#state{start_or_recv_from = undefined, hello = Hello}, [{reply, From, {ok, ssl_connection:map_extensions(Extensions)}}]}; -hello(internal, #client_hello{cookie = Cookie} = Hello, - #state{role = server, - transport_cb = Transport, - socket = Socket, - protocol_specific = #{current_cookie_secret := Secret, - previous_cookie_secret := PSecret}} = State0) -> + +hello(internal, #client_hello{cookie = Cookie} = Hello, #state{static_env = #static_env{role = server, + transport_cb = Transport, + socket = Socket}, + protocol_specific = #{current_cookie_secret := Secret, + previous_cookie_secret := PSecret} + } = State0) -> {ok, {IP, Port}} = dtls_socket:peername(Transport, Socket), case dtls_handshake:cookie(Secret, IP, Port, Hello) of Cookie -> @@ -546,11 +554,12 @@ hello(internal, #client_hello{cookie = Cookie} = Hello, end end; hello(internal, #server_hello{} = Hello, - #state{connection_states = ConnectionStates0, - negotiated_version = ReqVersion, - role = client, - renegotiation = {Renegotiation, _}, - ssl_options = SslOptions} = State) -> + #state{ + static_env = #static_env{role = client}, + connection_states = ConnectionStates0, + negotiated_version = ReqVersion, + renegotiation = {Renegotiation, _}, + ssl_options = SslOptions} = State) -> case dtls_handshake:hello(Hello, SslOptions, ConnectionStates0, Renegotiation) of #alert{} = Alert -> handle_own_alert(Alert, ReqVersion, ?FUNCTION_NAME, State); @@ -658,9 +667,13 @@ connection(enter, _, State) -> {keep_state, State}; connection(info, Event, State) -> gen_info(Event, ?FUNCTION_NAME, State); -connection(internal, #hello_request{}, #state{host = Host, port = Port, +connection(internal, #hello_request{}, #state{static_env = #static_env{host = Host, + port = Port, + session_cache = Cache, + session_cache_cb = CacheCb + }, session = #session{own_certificate = Cert} = Session0, - session_cache = Cache, session_cache_cb = CacheCb, + ssl_options = SslOpts, connection_states = ConnectionStates0, renegotiation = {Renegotiation, _}} = State0) -> @@ -670,12 +683,15 @@ connection(internal, #hello_request{}, #state{host = Host, port = Port, Version = Hello#client_hello.client_version, HelloVersion = dtls_record:hello_version(Version, SslOpts#ssl_options.versions), State1 = prepare_flight(State0), - {State, Actions} = send_handshake(Hello, State1#state{negotiated_version = HelloVersion}), - next_event(hello, no_record, State#state{flight_state = {retransmit, ?INITIAL_RETRANSMIT_TIMEOUT}, - session = Session0#session{session_id - = Hello#client_hello.session_id}}, - Actions); -connection(internal, #client_hello{} = Hello, #state{role = server, allow_renegotiate = true} = State) -> + {State2, Actions} = send_handshake(Hello, State1#state{negotiated_version = HelloVersion}), + {Record, State} = + next_record( + State2#state{flight_state = {retransmit, ?INITIAL_RETRANSMIT_TIMEOUT}, + session = Session0#session{session_id + = Hello#client_hello.session_id}}), + next_event(hello, Record, State, Actions); +connection(internal, #client_hello{} = Hello, #state{static_env = #static_env{role = server}, + allow_renegotiate = true} = State) -> %% 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 @@ -684,7 +700,8 @@ connection(internal, #client_hello{} = Hello, #state{role = server, allow_renego erlang:send_after(?WAIT_TO_ALLOW_RENEGOTIATION, self(), allow_renegotiate), {next_state, hello, State#state{allow_renegotiate = false, renegotiation = {true, peer}}, [{next_event, internal, Hello}]}; -connection(internal, #client_hello{}, #state{role = server, allow_renegotiate = false} = State0) -> +connection(internal, #client_hello{}, #state{static_env = #static_env{role = server}, + allow_renegotiate = false} = State0) -> Alert = ?ALERT_REC(?WARNING, ?NO_RENEGOTIATION), State1 = send_alert(Alert, State0), {Record, State} = ssl_connection:prepare_connection(State1, ?MODULE), @@ -739,29 +756,32 @@ initial_state(Role, Host, Port, Socket, {SSLOptions, SocketOptions, _}, User, end, Monitor = erlang:monitor(process, User), - - #state{socket_options = SocketOptions, + InitStatEnv = #static_env{ + role = Role, + transport_cb = CbModule, + protocol_cb = ?MODULE, + data_tag = DataTag, + close_tag = CloseTag, + error_tag = ErrorTag, + host = Host, + port = Port, + socket = Socket, + session_cache_cb = SessionCacheCb + }, + + #state{static_env = InitStatEnv, + socket_options = SocketOptions, %% We do not want to save the password in the state so that %% could be written in the clear into error logs. ssl_options = SSLOptions#ssl_options{password = undefined}, session = #session{is_resumable = new}, - transport_cb = CbModule, - data_tag = DataTag, - close_tag = CloseTag, - error_tag = ErrorTag, - role = Role, - host = Host, - port = Port, - socket = Socket, connection_states = ConnectionStates, protocol_buffers = #protocol_buffers{}, user_application = {Monitor, User}, user_data_buffer = <<>>, - session_cache_cb = SessionCacheCb, renegotiation = {false, first}, allow_renegotiate = SSLOptions#ssl_options.client_renegotiation, start_or_recv_from = undefined, - protocol_cb = ?MODULE, flight_buffer = new_flight(), flight_state = {retransmit, ?INITIAL_RETRANSMIT_TIMEOUT} }. @@ -802,17 +822,19 @@ decode_cipher_text(#state{protocol_buffers = #protocol_buffers{dtls_cipher_texts {Alert, State} end. -dtls_version(hello, Version, #state{role = server} = State) -> +dtls_version(hello, Version, #state{static_env = #static_env{role = server}} = State) -> State#state{negotiated_version = Version}; %%Inital version dtls_version(_,_, State) -> State. handle_client_hello(#client_hello{client_version = ClientVersion} = Hello, #state{connection_states = ConnectionStates0, - port = Port, session = #session{own_certificate = Cert} = Session0, + static_env = #static_env{port = Port, + session_cache = Cache, + session_cache_cb = CacheCb}, + session = #session{own_certificate = Cert} = Session0, renegotiation = {Renegotiation, _}, - session_cache = Cache, - session_cache_cb = CacheCb, + negotiated_protocol = CurrentProtocol, key_algorithm = KeyExAlg, ssl_options = SslOpts} = State0) -> @@ -842,7 +864,7 @@ handle_client_hello(#client_hello{client_version = ClientVersion} = Hello, %% raw data from socket, unpack records handle_info({Protocol, _, _, _, Data}, StateName, - #state{data_tag = Protocol} = State0) -> + #state{static_env = #static_env{data_tag = Protocol}} = State0) -> case next_dtls_record(Data, StateName, State0) of {Record, State} -> next_event(StateName, Record, State); @@ -851,10 +873,10 @@ handle_info({Protocol, _, _, _, Data}, StateName, ssl_connection:stop({shutdown, own_alert}, State0) end; handle_info({CloseTag, Socket}, StateName, - #state{socket = Socket, + #state{static_env = #static_env{socket = Socket, + close_tag = CloseTag}, socket_options = #socket_options{active = Active}, protocol_buffers = #protocol_buffers{dtls_cipher_texts = CTs}, - close_tag = CloseTag, negotiated_version = Version} = State) -> %% Note that as of DTLS 1.2 (TLS 1.1), %% failure to properly close a connection no longer requires that a @@ -907,8 +929,8 @@ handle_alerts([Alert | Alerts], {next_state, StateName, State}) -> handle_alerts([Alert | Alerts], {next_state, StateName, State, _Actions}) -> handle_alerts(Alerts, ssl_connection:handle_alert(Alert, StateName, State)). -handle_own_alert(Alert, Version, StateName, #state{data_tag = udp, - role = Role, +handle_own_alert(Alert, Version, StateName, #state{static_env = #static_env{data_tag = udp, + role = Role}, ssl_options = Options} = State0) -> case ignore_alert(Alert, State0) of {true, State} -> @@ -998,10 +1020,10 @@ next_flight(Flight) -> change_cipher_spec => undefined, handshakes_after_change_cipher_spec => []}. -handle_flight_timer(#state{data_tag = udp, +handle_flight_timer(#state{static_env = #static_env{data_tag = udp}, flight_state = {retransmit, Timeout}} = State) -> start_retransmision_timer(Timeout, State); -handle_flight_timer(#state{data_tag = udp, +handle_flight_timer(#state{static_env = #static_env{data_tag = udp}, flight_state = connection} = State) -> {State, []}; handle_flight_timer(State) -> @@ -1017,8 +1039,8 @@ new_timeout(N) when N =< 30 -> new_timeout(_) -> 60. -send_handshake_flight(#state{socket = Socket, - transport_cb = Transport, +send_handshake_flight(#state{static_env = #static_env{socket = Socket, + transport_cb = Transport}, flight_buffer = #{handshakes := Flight, change_cipher_spec := undefined}, negotiated_version = Version, @@ -1029,8 +1051,8 @@ send_handshake_flight(#state{socket = Socket, send(Transport, Socket, Encoded), {State0#state{connection_states = ConnectionStates}, []}; -send_handshake_flight(#state{socket = Socket, - transport_cb = Transport, +send_handshake_flight(#state{static_env = #static_env{socket = Socket, + transport_cb = Transport}, flight_buffer = #{handshakes := [_|_] = Flight0, change_cipher_spec := ChangeCipher, handshakes_after_change_cipher_spec := []}, @@ -1043,8 +1065,8 @@ send_handshake_flight(#state{socket = Socket, send(Transport, Socket, [HsBefore, EncChangeCipher]), {State0#state{connection_states = ConnectionStates}, []}; -send_handshake_flight(#state{socket = Socket, - transport_cb = Transport, +send_handshake_flight(#state{static_env = #static_env{socket = Socket, + transport_cb = Transport}, flight_buffer = #{handshakes := [_|_] = Flight0, change_cipher_spec := ChangeCipher, handshakes_after_change_cipher_spec := Flight1}, @@ -1059,8 +1081,8 @@ send_handshake_flight(#state{socket = Socket, send(Transport, Socket, [HsBefore, EncChangeCipher, HsAfter]), {State0#state{connection_states = ConnectionStates}, []}; -send_handshake_flight(#state{socket = Socket, - transport_cb = Transport, +send_handshake_flight(#state{static_env = #static_env{socket = Socket, + transport_cb = Transport}, flight_buffer = #{handshakes := [], change_cipher_spec := ChangeCipher, handshakes_after_change_cipher_spec := Flight1}, @@ -1117,10 +1139,10 @@ log_ignore_alert(false, _, _,_) -> ok. send_application_data(Data, From, _StateName, - #state{socket = Socket, + #state{static_env = #static_env{socket = Socket, + protocol_cb = Connection, + transport_cb = Transport}, negotiated_version = Version, - protocol_cb = Connection, - transport_cb = Transport, connection_states = ConnectionStates0, ssl_options = #ssl_options{renegotiate_at = RenegotiateAt}} = State0) -> diff --git a/lib/ssl/src/ssl_connection.erl b/lib/ssl/src/ssl_connection.erl index 4b406b4c1e..bab13211df 100644 --- a/lib/ssl/src/ssl_connection.erl +++ b/lib/ssl/src/ssl_connection.erl @@ -335,8 +335,8 @@ prf(ConnectionPid, Secret, Label, Seed, WantedLength) -> %% Alert and close handling %%==================================================================== handle_own_alert(Alert, _, StateName, - #state{role = Role, - protocol_cb = Connection, + #state{static_env = #static_env{role = Role, + protocol_cb = Connection}, ssl_options = SslOpts} = State) -> try %% Try to tell the other side send_alert(Alert, StateName, State) @@ -351,31 +351,39 @@ handle_own_alert(Alert, _, StateName, end, stop({shutdown, own_alert}, State). -handle_normal_shutdown(Alert, _, #state{socket = Socket, - transport_cb = Transport, - protocol_cb = Connection, +handle_normal_shutdown(Alert, _, #state{static_env = #static_env{role = Role, + socket = Socket, + transport_cb = Transport, + protocol_cb = Connection, + tracker = Tracker}, start_or_recv_from = StartFrom, - tracker = Tracker, - role = Role, renegotiation = {false, first}} = State) -> + renegotiation = {false, first}} = State) -> Pids = Connection:pids(State), alert_user(Pids, Transport, Tracker,Socket, StartFrom, Alert, Role, Connection); -handle_normal_shutdown(Alert, StateName, #state{socket = Socket, - socket_options = Opts, - transport_cb = Transport, - protocol_cb = Connection, - user_application = {_Mon, Pid}, - tracker = Tracker, - start_or_recv_from = RecvFrom, role = Role} = State) -> +handle_normal_shutdown(Alert, StateName, #state{static_env = #static_env{role = Role, + socket = Socket, + transport_cb = Transport, + protocol_cb = Connection, + tracker = Tracker}, + socket_options = Opts, + user_application = {_Mon, Pid}, + start_or_recv_from = RecvFrom} = State) -> Pids = Connection:pids(State), alert_user(Pids, Transport, Tracker, Socket, StateName, Opts, Pid, RecvFrom, Alert, Role, Connection). handle_alert(#alert{level = ?FATAL} = Alert, StateName, - #state{socket = Socket, transport_cb = Transport, - protocol_cb = Connection, - ssl_options = SslOpts, start_or_recv_from = From, host = Host, - port = Port, session = Session, user_application = {_Mon, Pid}, - role = Role, socket_options = Opts, tracker = Tracker} = State) -> + #state{static_env = #static_env{role = Role, + socket = Socket, + host = Host, + port = Port, + tracker = Tracker, + transport_cb = Transport, + protocol_cb = Connection}, + ssl_options = SslOpts, + start_or_recv_from = From, + session = Session, user_application = {_Mon, Pid}, + socket_options = Opts} = State) -> invalidate_session(Role, Host, Port, Session), log_alert(SslOpts#ssl_options.log_alert, Role, Connection:protocol_name(), StateName, Alert#alert{role = opposite_role(Role)}), @@ -389,7 +397,9 @@ handle_alert(#alert{level = ?WARNING, description = ?CLOSE_NOTIFY} = Alert, stop({shutdown, peer_close}, State); handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert, StateName, - #state{role = Role, ssl_options = SslOpts, protocol_cb = Connection, + #state{static_env = #static_env{role = Role, + protocol_cb = Connection}, + ssl_options = SslOpts, renegotiation = {true, internal}} = State) -> log_alert(SslOpts#ssl_options.log_alert, Role, Connection:protocol_name(), StateName, Alert#alert{role = opposite_role(Role)}), @@ -397,9 +407,11 @@ handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert, stop({shutdown, peer_close}, State); handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert, connection = StateName, - #state{role = Role, - ssl_options = SslOpts, renegotiation = {true, From}, - protocol_cb = Connection} = State0) -> + #state{static_env = #static_env{role = Role, + protocol_cb = Connection}, + ssl_options = SslOpts, + renegotiation = {true, From} + } = State0) -> log_alert(SslOpts#ssl_options.log_alert, Role, Connection:protocol_name(), StateName, Alert#alert{role = opposite_role(Role)}), gen_statem:reply(From, {error, renegotiation_rejected}), @@ -407,9 +419,11 @@ handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert, Connection:next_event(connection, no_record, State#state{renegotiation = undefined}); handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert, StateName, - #state{role = Role, - ssl_options = SslOpts, renegotiation = {true, From}, - protocol_cb = Connection} = State0) -> + #state{static_env = #static_env{role = Role, + protocol_cb = Connection}, + ssl_options = SslOpts, + renegotiation = {true, From} + } = State0) -> log_alert(SslOpts#ssl_options.log_alert, Role, Connection:protocol_name(), StateName, Alert#alert{role = opposite_role(Role)}), gen_statem:reply(From, {error, renegotiation_rejected}), @@ -419,7 +433,9 @@ handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert, %% Gracefully log and ignore all other warning alerts handle_alert(#alert{level = ?WARNING} = Alert, StateName, - #state{ssl_options = SslOpts, protocol_cb = Connection, role = Role} = State) -> + #state{static_env = #static_env{role = Role, + protocol_cb = Connection}, + ssl_options = SslOpts} = State) -> log_alert(SslOpts#ssl_options.log_alert, Role, Connection:protocol_name(), StateName, Alert#alert{role = opposite_role(Role)}), Connection:next_event(StateName, no_record, State). @@ -427,7 +443,6 @@ handle_alert(#alert{level = ?WARNING} = Alert, StateName, %%==================================================================== %% Data handling %%==================================================================== - passive_receive(State0 = #state{user_data_buffer = Buffer}, StateName, Connection) -> case Buffer of <<>> -> @@ -442,16 +457,16 @@ passive_receive(State0 = #state{user_data_buffer = Buffer}, StateName, Connectio end end. -read_application_data(Data, #state{user_application = {_Mon, Pid}, - socket = Socket, - protocol_cb = Connection, - transport_cb = Transport, +read_application_data(Data, #state{static_env = #static_env{socket = Socket, + protocol_cb = Connection, + transport_cb = Transport, + tracker = Tracker}, + user_application = {_Mon, Pid}, socket_options = SOpts, bytes_to_read = BytesToRead, start_or_recv_from = RecvFrom, timer = Timer, - user_data_buffer = Buffer0, - tracker = Tracker} = State0) -> + user_data_buffer = Buffer0} = State0) -> Buffer1 = if Buffer0 =:= <<>> -> Data; Data =:= <<>> -> Buffer0; @@ -500,7 +515,7 @@ read_application_data(Data, #state{user_application = {_Mon, Pid}, end. dist_app_data(ClientData, #state{erl_dist_data = #{dist_handle := undefined, - dist_buffer := DistBuff} = DistData} = State) -> + dist_buffer := DistBuff} = DistData} = State) -> {no_record, State#state{erl_dist_data = DistData#{dist_buffer => [ClientData, DistBuff]}}}; dist_app_data(ClientData, #state{erl_dist_data = #{dist_handle := DHandle, dist_buffer := DistBuff} = ErlDistData, @@ -573,7 +588,7 @@ handle_session(#server_hello{cipher_suite = CipherSuite, ssl_config(Opts, Role, State) -> ssl_config(Opts, Role, State, new). -ssl_config(Opts, Role, State0, Type) -> +ssl_config(Opts, Role, #state{static_env = InitStatEnv0} =State0, Type) -> {ok, #{cert_db_ref := Ref, cert_db_handle := CertDbHandle, fileref_db_handle := FileRefHandle, @@ -585,13 +600,16 @@ ssl_config(Opts, Role, State0, Type) -> ssl_config:init(Opts, Role), TimeStamp = erlang:monotonic_time(), Session = State0#state.session, + State = State0#state{session = Session#session{own_certificate = OwnCert, time_stamp = TimeStamp}, - file_ref_db = FileRefHandle, - cert_db_ref = Ref, - cert_db = CertDbHandle, - crl_db = CRLDbHandle, - session_cache = CacheHandle, + static_env = InitStatEnv0#static_env{ + file_ref_db = FileRefHandle, + cert_db_ref = Ref, + cert_db = CertDbHandle, + crl_db = CRLDbHandle, + session_cache = CacheHandle + }, private_key = Key, diffie_hellman_params = DHParams, ssl_options = Opts}, @@ -618,7 +636,8 @@ init({call, From}, {start, Timeout}, State0, Connection) -> Timer = start_or_recv_cancel_timer(Timeout, From), Connection:next_event(hello, no_record, State0#state{start_or_recv_from = From, timer = Timer}); init({call, From}, {start, {Opts, EmOpts}, Timeout}, - #state{role = Role, ssl_options = OrigSSLOptions, + #state{static_env = #static_env{role = Role}, + ssl_options = OrigSSLOptions, socket_options = SockOpts} = State0, Connection) -> try SslOpts = ssl:handle_options(Opts, OrigSSLOptions), @@ -666,10 +685,11 @@ user_hello({call, From}, cancel, #state{negotiated_version = Version} = State, _ gen_statem:reply(From, ok), handle_own_alert(?ALERT_REC(?FATAL, ?USER_CANCELED, user_canceled), Version, ?FUNCTION_NAME, State); -user_hello({call, From}, {handshake_continue, NewOptions, Timeout}, #state{hello = Hello, - role = Role, - start_or_recv_from = RecvFrom, - ssl_options = Options0} = State0, _Connection) -> +user_hello({call, From}, {handshake_continue, NewOptions, Timeout}, + #state{hello = Hello, + static_env = #static_env{role = Role}, + start_or_recv_from = RecvFrom, + ssl_options = Options0} = State0, _Connection) -> Timer = start_or_recv_cancel_timer(Timeout, RecvFrom), Options = ssl:handle_options(NewOptions, Options0#ssl_options{handshake = full}), State = ssl_config(Options, Role, State0, continue), @@ -688,7 +708,7 @@ user_hello(_, _, _, _) -> abbreviated({call, From}, Msg, State, Connection) -> handle_call(Msg, From, ?FUNCTION_NAME, State, Connection); abbreviated(internal, #finished{verify_data = Data} = Finished, - #state{role = server, + #state{static_env = #static_env{role = server}, negotiated_version = Version, expecting_finished = true, tls_handshake_history = Handshake, @@ -708,7 +728,8 @@ abbreviated(internal, #finished{verify_data = Data} = Finished, handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0) end; abbreviated(internal, #finished{verify_data = Data} = Finished, - #state{role = client, tls_handshake_history = Handshake0, + #state{static_env = #static_env{role = client}, + tls_handshake_history = Handshake0, session = #session{master_secret = MasterSecret}, negotiated_version = Version, connection_states = ConnectionStates0} = State0, Connection) -> @@ -729,7 +750,8 @@ abbreviated(internal, #finished{verify_data = Data} = Finished, %% only allowed to send next_protocol message after change cipher spec %% & before finished message and it is not allowed during renegotiation abbreviated(internal, #next_protocol{selected_protocol = SelectedProtocol}, - #state{role = server, expecting_next_protocol_negotiation = true} = State, + #state{static_env = #static_env{role = server}, + expecting_next_protocol_negotiation = true} = State, Connection) -> Connection:next_event(?FUNCTION_NAME, no_record, State#state{negotiated_protocol = SelectedProtocol, @@ -759,32 +781,34 @@ certify({call, From}, Msg, State, Connection) -> certify(info, Msg, State, _) -> handle_info(Msg, ?FUNCTION_NAME, State); certify(internal, #certificate{asn1_certificates = []}, - #state{role = server, negotiated_version = Version, + #state{static_env = #static_env{role = server}, + negotiated_version = Version, ssl_options = #ssl_options{verify = verify_peer, fail_if_no_peer_cert = true}} = State, _) -> Alert = ?ALERT_REC(?FATAL,?HANDSHAKE_FAILURE), handle_own_alert(Alert, Version, ?FUNCTION_NAME, State); certify(internal, #certificate{asn1_certificates = []}, - #state{role = server, + #state{static_env = #static_env{role = server}, ssl_options = #ssl_options{verify = verify_peer, fail_if_no_peer_cert = false}} = State0, Connection) -> Connection:next_event(?FUNCTION_NAME, no_record, State0#state{client_certificate_requested = false}); certify(internal, #certificate{}, - #state{role = server, + #state{static_env = #static_env{role = server}, negotiated_version = Version, ssl_options = #ssl_options{verify = verify_none}} = State, _) -> Alert = ?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE, unrequested_certificate), handle_own_alert(Alert, Version, ?FUNCTION_NAME, State); certify(internal, #certificate{} = Cert, - #state{negotiated_version = Version, - role = Role, - host = Host, - cert_db = CertDbHandle, - cert_db_ref = CertDbRef, - crl_db = CRLDbInfo, + #state{static_env = #static_env{ + role = Role, + host = Host, + cert_db = CertDbHandle, + cert_db_ref = CertDbRef, + crl_db = CRLDbInfo}, + negotiated_version = Version, ssl_options = Opts} = State, Connection) -> case ssl_handshake:certify(Cert, CertDbHandle, CertDbRef, Opts, CRLDbInfo, Role, Host) of @@ -795,7 +819,8 @@ certify(internal, #certificate{} = Cert, handle_own_alert(Alert, Version, ?FUNCTION_NAME, State) end; certify(internal, #server_key_exchange{exchange_keys = Keys}, - #state{role = client, negotiated_version = Version, + #state{static_env = #static_env{role = client}, + negotiated_version = Version, key_algorithm = Alg, public_key_info = PubKeyInfo, session = Session, @@ -829,7 +854,8 @@ certify(internal, #server_key_exchange{exchange_keys = Keys}, end end; certify(internal, #certificate_request{}, - #state{role = client, negotiated_version = Version, + #state{static_env = #static_env{role = client}, + negotiated_version = Version, key_algorithm = Alg} = State, _) when Alg == dh_anon; Alg == ecdh_anon; Alg == psk; Alg == dhe_psk; Alg == ecdhe_psk; Alg == rsa_psk; @@ -837,17 +863,17 @@ certify(internal, #certificate_request{}, handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE), Version, ?FUNCTION_NAME, State); certify(internal, #certificate_request{}, - #state{session = #session{own_certificate = undefined}, - role = client} = State, Connection) -> + #state{static_env = #static_env{role = client}, + session = #session{own_certificate = undefined}} = State, Connection) -> %% The client does not have a certificate and will send an empty reply, the server may fail %% or accept the connection by its own preference. No signature algorihms needed as there is %% no certificate to verify. Connection:next_event(?FUNCTION_NAME, no_record, State#state{client_certificate_requested = true}); certify(internal, #certificate_request{} = CertRequest, - #state{session = #session{own_certificate = Cert}, - role = client, - ssl_options = #ssl_options{signature_algs = SupportedHashSigns}, - negotiated_version = Version} = State, Connection) -> + #state{static_env = #static_env{role = client}, + session = #session{own_certificate = Cert}, + ssl_options = #ssl_options{signature_algs = SupportedHashSigns}, + negotiated_version = Version} = State, Connection) -> case ssl_handshake:select_hashsign(CertRequest, Cert, SupportedHashSigns, ssl:tls_version(Version)) of #alert {} = Alert -> handle_own_alert(Alert, Version, ?FUNCTION_NAME, State); @@ -858,12 +884,12 @@ certify(internal, #certificate_request{} = CertRequest, end; %% PSK and RSA_PSK might bypass the Server-Key-Exchange certify(internal, #server_hello_done{}, - #state{session = #session{master_secret = undefined}, + #state{static_env = #static_env{role = client}, + session = #session{master_secret = undefined}, negotiated_version = Version, psk_identity = PSKIdentity, ssl_options = #ssl_options{user_lookup_fun = PSKLookup}, premaster_secret = undefined, - role = client, key_algorithm = Alg} = State0, Connection) when Alg == psk -> case ssl_handshake:premaster_secret({Alg, PSKIdentity}, PSKLookup) of @@ -875,12 +901,12 @@ certify(internal, #server_hello_done{}, client_certify_and_key_exchange(State, Connection) end; certify(internal, #server_hello_done{}, - #state{session = #session{master_secret = undefined}, + #state{static_env = #static_env{role = client}, + session = #session{master_secret = undefined}, ssl_options = #ssl_options{user_lookup_fun = PSKLookup}, negotiated_version = {Major, Minor} = Version, psk_identity = PSKIdentity, premaster_secret = undefined, - role = client, key_algorithm = Alg} = State0, Connection) when Alg == rsa_psk -> Rand = ssl_cipher:random_bytes(?NUM_OF_PREMASTERSECRET_BYTES-2), @@ -896,11 +922,11 @@ certify(internal, #server_hello_done{}, end; %% Master secret was determined with help of server-key exchange msg certify(internal, #server_hello_done{}, - #state{session = #session{master_secret = MasterSecret} = Session, + #state{static_env = #static_env{role = client}, + session = #session{master_secret = MasterSecret} = Session, connection_states = ConnectionStates0, negotiated_version = Version, - premaster_secret = undefined, - role = client} = State0, Connection) -> + premaster_secret = undefined} = State0, Connection) -> case ssl_handshake:master_secret(ssl:tls_version(Version), Session, ConnectionStates0, client) of {MasterSecret, ConnectionStates} -> @@ -911,11 +937,11 @@ certify(internal, #server_hello_done{}, end; %% Master secret is calculated from premaster_secret certify(internal, #server_hello_done{}, - #state{session = Session0, + #state{static_env = #static_env{role = client}, + session = Session0, connection_states = ConnectionStates0, negotiated_version = Version, - premaster_secret = PremasterSecret, - role = client} = State0, Connection) -> + premaster_secret = PremasterSecret} = State0, Connection) -> case ssl_handshake:master_secret(ssl:tls_version(Version), PremasterSecret, ConnectionStates0, client) of {MasterSecret, ConnectionStates} -> @@ -927,7 +953,7 @@ certify(internal, #server_hello_done{}, handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0) end; certify(internal = Type, #client_key_exchange{} = Msg, - #state{role = server, + #state{static_env = #static_env{role = server}, client_certificate_requested = true, ssl_options = #ssl_options{fail_if_no_peer_cert = true}} = State, Connection) -> @@ -957,7 +983,7 @@ cipher(info, Msg, State, _) -> handle_info(Msg, ?FUNCTION_NAME, State); cipher(internal, #certificate_verify{signature = Signature, hashsign_algorithm = CertHashSign}, - #state{role = server, + #state{static_env = #static_env{role = server}, key_algorithm = KexAlg, public_key_info = PublicKeyInfo, negotiated_version = Version, @@ -978,16 +1004,17 @@ cipher(internal, #certificate_verify{signature = Signature, end; %% client must send a next protocol message if we are expecting it cipher(internal, #finished{}, - #state{role = server, expecting_next_protocol_negotiation = true, + #state{static_env = #static_env{role = server}, + expecting_next_protocol_negotiation = true, negotiated_protocol = undefined, negotiated_version = Version} = State0, _Connection) -> handle_own_alert(?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE), Version, ?FUNCTION_NAME, State0); cipher(internal, #finished{verify_data = Data} = Finished, - #state{negotiated_version = Version, - host = Host, - port = Port, - role = Role, - expecting_finished = true, + #state{static_env = #static_env{role = Role, + host = Host, + port = Port}, + negotiated_version = Version, + expecting_finished = true, session = #session{master_secret = MasterSecret} = Session0, ssl_options = SslOpts, @@ -1007,12 +1034,13 @@ cipher(internal, #finished{verify_data = Data} = Finished, %% only allowed to send next_protocol message after change cipher spec %% & before finished message and it is not allowed during renegotiation cipher(internal, #next_protocol{selected_protocol = SelectedProtocol}, - #state{role = server, expecting_next_protocol_negotiation = true, - expecting_finished = true} = State, Connection) -> - Connection:next_event(?FUNCTION_NAME, no_record, - State#state{expecting_next_protocol_negotiation = false, - negotiated_protocol = SelectedProtocol - }); + #state{static_env = #static_env{role = server}, + expecting_next_protocol_negotiation = true, + expecting_finished = true} = State0, Connection) -> + {Record, State} = + Connection:next_record(State0#state{negotiated_protocol = SelectedProtocol}), + Connection:next_event(?FUNCTION_NAME, Record, + State#state{expecting_next_protocol_negotiation = false}); cipher(internal, #change_cipher_spec{type = <<1>>}, #state{connection_states = ConnectionStates0} = State, Connection) -> ConnectionStates = @@ -1029,13 +1057,15 @@ cipher(Type, Msg, State, Connection) -> gen_statem:state_function_result(). %%-------------------------------------------------------------------- connection({call, RecvFrom}, {recv, N, Timeout}, - #state{protocol_cb = Connection, socket_options = - #socket_options{active = false}} = State0, Connection) -> + #state{static_env = #static_env{protocol_cb = Connection}, + socket_options = + #socket_options{active = false}} = State0, Connection) -> Timer = start_or_recv_cancel_timer(Timeout, RecvFrom), passive_receive(State0#state{bytes_to_read = N, start_or_recv_from = RecvFrom, timer = Timer}, ?FUNCTION_NAME, Connection); -connection({call, From}, renegotiate, #state{protocol_cb = Connection} = State, + +connection({call, From}, renegotiate, #state{static_env = #static_env{protocol_cb = Connection}} = State, Connection) -> Connection:renegotiate(State#state{renegotiation = {true, From}}, []); connection({call, From}, peer_certificate, @@ -1056,7 +1086,7 @@ connection({call, From}, negotiated_protocol, [{reply, From, {ok, SelectedProtocol}}]); connection({call, From}, Msg, State, Connection) -> handle_call(Msg, From, ?FUNCTION_NAME, State, Connection); -connection(cast, {internal_renegotiate, WriteState}, #state{protocol_cb = Connection, +connection(cast, {internal_renegotiate, WriteState}, #state{static_env = #static_env{protocol_cb = Connection}, connection_states = ConnectionStates} = State, Connection) -> Connection:renegotiate(State#state{renegotiation = {true, internal}, @@ -1086,7 +1116,8 @@ connection(Type, Msg, State, Connection) -> gen_statem:state_function_result(). %%-------------------------------------------------------------------- downgrade(internal, #alert{description = ?CLOSE_NOTIFY}, - #state{transport_cb = Transport, socket = Socket, + #state{static_env = #static_env{transport_cb = Transport, + socket = Socket}, downgrade = {Pid, From}} = State, _) -> tls_socket:setopts(Transport, Socket, [{active, false}, {packet, 0}, {mode, binary}]), Transport:controlling_process(Socket, Pid), @@ -1095,9 +1126,9 @@ downgrade(internal, #alert{description = ?CLOSE_NOTIFY}, downgrade(timeout, downgrade, #state{downgrade = {_, From}} = State, _) -> gen_statem:reply(From, {error, timeout}), stop(normal, State); -downgrade( - info, {CloseTag, Socket}, - #state{socket = Socket, close_tag = CloseTag, downgrade = {_, From}} = +downgrade(info, {CloseTag, Socket}, + #state{static_env = #static_env{socket = Socket, + close_tag = CloseTag}, downgrade = {_, From}} = State, _) -> gen_statem:reply(From, {error, CloseTag}), stop(normal, State); @@ -1109,10 +1140,11 @@ downgrade(Type, Event, State, Connection) -> %% common or unexpected events for the state. %%-------------------------------------------------------------------- handle_common_event(internal, {handshake, {#hello_request{} = Handshake, _}}, connection = StateName, - #state{role = client} = State, _) -> + #state{static_env = #static_env{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}, _) +handle_common_event(internal, {handshake, {#hello_request{}, _}}, StateName, + #state{static_env = #static_env{role = client}}, _) when StateName =/= connection -> {keep_state_and_data}; handle_common_event(internal, {handshake, {Handshake, Raw}}, StateName, @@ -1174,11 +1206,10 @@ handle_call({close, _} = Close, From, StateName, State, _Connection) -> {shutdown, normal}, {reply, From, Result}, State#state{terminated = true}); handle_call({shutdown, read_write = How}, From, StateName, - #state{transport_cb = Transport, - socket = Socket} = State, _) -> - + #state{static_env = #static_env{transport_cb = Transport, + socket = Socket}} = State, _) -> try send_alert(?ALERT_REC(?WARNING, ?CLOSE_NOTIFY), - StateName, State) of + StateName, State) of _ -> case Transport:shutdown(Socket, How) of ok -> @@ -1191,8 +1222,8 @@ handle_call({shutdown, read_write = How}, From, StateName, Return end; handle_call({shutdown, How0}, From, StateName, - #state{transport_cb = Transport, - socket = Socket} = State, _) -> + #state{static_env = #static_env{transport_cb = Transport, + socket = Socket}} = State, _) -> case Transport:shutdown(Socket, How0) of ok -> {next_state, StateName, State, [{reply, From, ok}]}; @@ -1217,15 +1248,16 @@ handle_call({new_user, User}, From, StateName, {next_state, StateName, State#state{user_application = {NewMon,User}}, [{reply, From, ok}]}; handle_call({get_opts, OptTags}, From, _, - #state{socket = Socket, - transport_cb = Transport, + #state{static_env = #static_env{socket = Socket, + transport_cb = Transport}, socket_options = SockOpts}, Connection) -> OptsReply = get_socket_opts(Connection, Transport, Socket, OptTags, SockOpts, []), {keep_state_and_data, [{reply, From, OptsReply}]}; handle_call({set_opts, Opts0}, From, StateName, - #state{socket_options = Opts1, - socket = Socket, - transport_cb = Transport} = State0, Connection) -> + #state{static_env = #static_env{socket = Socket, + transport_cb = Transport}, + socket_options = Opts1 + } = State0, Connection) -> {Reply, Opts} = set_socket_opts(Connection, Transport, Socket, Opts0, Opts1, []), State = State0#state{socket_options = Opts}, handle_active_option(Opts#socket_options.active, StateName, From, Reply, State); @@ -1266,18 +1298,21 @@ handle_call(_,_,_,_,_) -> {keep_state_and_data, [postpone]}. handle_info({ErrorTag, Socket, econnaborted}, StateName, - #state{socket = Socket, transport_cb = Transport, - protocol_cb = Connection, - start_or_recv_from = StartFrom, role = Role, - error_tag = ErrorTag, - tracker = Tracker} = State) when StateName =/= connection -> + #state{static_env = #static_env{role = Role, + socket = Socket, + transport_cb = Transport, + error_tag = ErrorTag, + tracker = Tracker, + protocol_cb = Connection}, + start_or_recv_from = StartFrom + } = State) when StateName =/= connection -> Pids = Connection:pids(State), alert_user(Pids, Transport, Tracker,Socket, StartFrom, ?ALERT_REC(?FATAL, ?CLOSE_NOTIFY), Role, Connection), stop(normal, State); -handle_info({ErrorTag, Socket, Reason}, StateName, #state{socket = Socket, - error_tag = ErrorTag} = State) -> +handle_info({ErrorTag, Socket, Reason}, StateName, #state{static_env = #static_env{socket = Socket, + error_tag = ErrorTag}} = State) -> Report = io_lib:format("SSL: Socket error: ~p ~n", [Reason]), error_logger:error_report(Report), handle_normal_shutdown(?ALERT_REC(?FATAL, ?CLOSE_NOTIFY), StateName, State), @@ -1298,10 +1333,10 @@ handle_info({'EXIT', Pid, _Reason}, StateName, %%% So that terminate will be run when supervisor issues shutdown handle_info({'EXIT', _Sup, shutdown}, _StateName, State) -> stop(shutdown, State); -handle_info({'EXIT', Socket, normal}, _StateName, #state{socket = Socket} = State) -> +handle_info({'EXIT', Socket, normal}, _StateName, #state{static_env = #static_env{socket = Socket}} = State) -> %% Handle as transport close" stop({shutdown, transport_closed}, State); -handle_info({'EXIT', Socket, Reason}, _StateName, #state{socket = Socket} = State) -> +handle_info({'EXIT', Socket, Reason}, _StateName, #state{static_env = #static_env{socket = Socket}} = State) -> stop({shutdown, Reason}, State); handle_info(allow_renegotiate, StateName, State) -> @@ -1321,7 +1356,7 @@ handle_info({cancel_start_or_recv, RecvFrom}, StateName, handle_info({cancel_start_or_recv, _RecvFrom}, StateName, State) -> {next_state, StateName, State#state{timer = undefined}}; -handle_info(Msg, StateName, #state{socket = Socket, error_tag = Tag} = State) -> +handle_info(Msg, StateName, #state{static_env = #static_env{socket = Socket, error_tag = Tag}} = State) -> Report = io_lib:format("SSL: Got unexpected info: ~p ~n", [{Msg, Tag, Socket}]), error_logger:info_report(Report), {next_state, StateName, State}. @@ -1338,14 +1373,15 @@ terminate(_, _, #state{terminated = true}) -> %% before run by gen_statem which will end up here ok; terminate({shutdown, transport_closed} = Reason, - _StateName, #state{protocol_cb = Connection, - socket = Socket, transport_cb = Transport} = State) -> + _StateName, #state{static_env = #static_env{protocol_cb = Connection, + socket = Socket, + transport_cb = Transport}} = State) -> handle_trusted_certs_db(State), Connection:close(Reason, Socket, Transport, undefined, undefined); terminate({shutdown, own_alert}, _StateName, #state{ - protocol_cb = Connection, - socket = Socket, - transport_cb = Transport} = State) -> + static_env = #static_env{protocol_cb = Connection, + socket = Socket, + transport_cb = Transport}} = State) -> handle_trusted_certs_db(State), case application:get_env(ssl, alert_timeout) of {ok, Timeout} when is_integer(Timeout) -> @@ -1353,23 +1389,27 @@ terminate({shutdown, own_alert}, _StateName, #state{ _ -> Connection:close({timeout, ?DEFAULT_TIMEOUT}, Socket, Transport, undefined, undefined) end; -terminate(downgrade = Reason, connection, #state{protocol_cb = Connection, - transport_cb = Transport, socket = Socket - } = State) -> +terminate(downgrade = Reason, connection, #state{static_env = #static_env{protocol_cb = Connection, + transport_cb = Transport, + socket = Socket} + } = State) -> handle_trusted_certs_db(State), Connection:close(Reason, Socket, Transport, undefined, undefined); -terminate(Reason, connection, #state{protocol_cb = Connection, +terminate(Reason, connection, #state{static_env = #static_env{ + protocol_cb = Connection, + transport_cb = Transport, + socket = Socket}, connection_states = ConnectionStates, - ssl_options = #ssl_options{padding_check = Check}, - transport_cb = Transport, socket = Socket + ssl_options = #ssl_options{padding_check = Check} } = State) -> handle_trusted_certs_db(State), Alert = terminate_alert(Reason), %% Send the termination ALERT if possible - catch (Connection:send_alert_in_connection(Alert, State)), + catch (ok = Connection:send_alert_in_connection(Alert, State)), Connection:close({timeout, ?DEFAULT_TIMEOUT}, Socket, Transport, ConnectionStates, Check); -terminate(Reason, _StateName, #state{transport_cb = Transport, protocol_cb = Connection, - socket = Socket +terminate(Reason, _StateName, #state{static_env = #static_env{transport_cb = Transport, + protocol_cb = Connection, + socket = Socket} } = State) -> handle_trusted_certs_db(State), Connection:close(Reason, Socket, Transport, undefined, undefined). @@ -1403,15 +1443,15 @@ format_status(terminate, [_, StateName, State]) -> %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- -send_alert(Alert, connection, #state{protocol_cb = Connection} = State) -> +send_alert(Alert, connection, #state{static_env = #static_env{protocol_cb = Connection}} = State) -> Connection:send_alert_in_connection(Alert, State); -send_alert(Alert, _, #state{protocol_cb = Connection} = State) -> +send_alert(Alert, _, #state{static_env = #static_env{protocol_cb = Connection}} = State) -> Connection:send_alert(Alert, State). -connection_info(#state{sni_hostname = SNIHostname, - session = #session{session_id = SessionId, +connection_info(#state{static_env = #static_env{protocol_cb = Connection}, + sni_hostname = SNIHostname, + session = #session{session_id = SessionId, cipher_suite = CipherSuite, ecc = ECCCurve}, - protocol_cb = Connection, negotiated_version = {_,_} = Version, ssl_options = Opts}) -> RecordCB = record_cb(Connection), @@ -1532,9 +1572,10 @@ handle_peer_cert_key(client, _, handle_peer_cert_key(_, _, _, _, State) -> State. -certify_client(#state{client_certificate_requested = true, role = client, - cert_db = CertDbHandle, - cert_db_ref = CertDbRef, +certify_client(#state{static_env = #static_env{role = client, + cert_db = CertDbHandle, + cert_db_ref = CertDbRef}, + client_certificate_requested = true, session = #session{own_certificate = OwnCert}} = State, Connection) -> Certificate = ssl_handshake:certificate(OwnCert, CertDbHandle, CertDbRef, client), @@ -1542,7 +1583,8 @@ certify_client(#state{client_certificate_requested = true, role = client, certify_client(#state{client_certificate_requested = false} = State, _) -> State. -verify_client_cert(#state{client_certificate_requested = true, role = client, +verify_client_cert(#state{static_env = #static_env{role = client}, + client_certificate_requested = true, negotiated_version = Version, private_key = PrivateKey, session = #session{master_secret = MasterSecret, @@ -1662,8 +1704,8 @@ certify_server(#state{key_algorithm = Algo} = State, _) when Algo == dh_anon; Algo == ecdhe_psk; Algo == srp_anon -> State; -certify_server(#state{cert_db = CertDbHandle, - cert_db_ref = CertDbRef, +certify_server(#state{static_env = #static_env{cert_db = CertDbHandle, + cert_db_ref = CertDbRef}, session = #session{own_certificate = OwnCert}} = State, Connection) -> case ssl_handshake:certificate(OwnCert, CertDbHandle, CertDbRef, server) of Cert = #certificate{} -> @@ -1672,9 +1714,9 @@ certify_server(#state{cert_db = CertDbHandle, throw(Alert) end. -key_exchange(#state{role = server, key_algorithm = rsa} = State,_) -> +key_exchange(#state{static_env = #static_env{role = server}, key_algorithm = rsa} = State,_) -> State; -key_exchange(#state{role = server, key_algorithm = Algo, +key_exchange(#state{static_env = #static_env{role = server}, key_algorithm = Algo, hashsign_algorithm = HashSignAlgo, diffie_hellman_params = #'DHParameter'{} = Params, private_key = PrivateKey, @@ -1695,12 +1737,14 @@ key_exchange(#state{role = server, key_algorithm = Algo, PrivateKey}), State = Connection:queue_handshake(Msg, State0), State#state{diffie_hellman_keys = DHKeys}; -key_exchange(#state{role = server, private_key = #'ECPrivateKey'{parameters = ECCurve} = Key, key_algorithm = Algo, +key_exchange(#state{static_env = #static_env{role = server}, + private_key = #'ECPrivateKey'{parameters = ECCurve} = Key, + key_algorithm = Algo, session = Session} = State, _) when Algo == ecdh_ecdsa; Algo == ecdh_rsa -> State#state{diffie_hellman_keys = Key, session = Session#session{ecc = ECCurve}}; -key_exchange(#state{role = server, key_algorithm = Algo, +key_exchange(#state{static_env = #static_env{role = server}, key_algorithm = Algo, hashsign_algorithm = HashSignAlgo, private_key = PrivateKey, session = #session{ecc = ECCCurve}, @@ -1722,10 +1766,10 @@ key_exchange(#state{role = server, key_algorithm = Algo, PrivateKey}), State = Connection:queue_handshake(Msg, State0), State#state{diffie_hellman_keys = ECDHKeys}; -key_exchange(#state{role = server, key_algorithm = psk, +key_exchange(#state{static_env = #static_env{role = server}, key_algorithm = psk, ssl_options = #ssl_options{psk_identity = undefined}} = State, _) -> State; -key_exchange(#state{role = server, key_algorithm = psk, +key_exchange(#state{static_env = #static_env{role = server}, key_algorithm = psk, ssl_options = #ssl_options{psk_identity = PskIdentityHint}, hashsign_algorithm = HashSignAlgo, private_key = PrivateKey, @@ -1742,7 +1786,7 @@ key_exchange(#state{role = server, key_algorithm = psk, ServerRandom, PrivateKey}), Connection:queue_handshake(Msg, State0); -key_exchange(#state{role = server, key_algorithm = dhe_psk, +key_exchange(#state{static_env = #static_env{role = server}, key_algorithm = dhe_psk, ssl_options = #ssl_options{psk_identity = PskIdentityHint}, hashsign_algorithm = HashSignAlgo, diffie_hellman_params = #'DHParameter'{} = Params, @@ -1763,7 +1807,7 @@ key_exchange(#state{role = server, key_algorithm = dhe_psk, PrivateKey}), State = Connection:queue_handshake(Msg, State0), State#state{diffie_hellman_keys = DHKeys}; -key_exchange(#state{role = server, key_algorithm = ecdhe_psk, +key_exchange(#state{static_env = #static_env{role = server}, key_algorithm = ecdhe_psk, ssl_options = #ssl_options{psk_identity = PskIdentityHint}, hashsign_algorithm = HashSignAlgo, private_key = PrivateKey, @@ -1784,10 +1828,10 @@ key_exchange(#state{role = server, key_algorithm = ecdhe_psk, PrivateKey}), State = Connection:queue_handshake(Msg, State0), State#state{diffie_hellman_keys = ECDHKeys}; -key_exchange(#state{role = server, key_algorithm = rsa_psk, +key_exchange(#state{static_env = #static_env{role = server}, key_algorithm = rsa_psk, ssl_options = #ssl_options{psk_identity = undefined}} = State, _) -> State; -key_exchange(#state{role = server, key_algorithm = rsa_psk, +key_exchange(#state{static_env = #static_env{role = server}, key_algorithm = rsa_psk, ssl_options = #ssl_options{psk_identity = PskIdentityHint}, hashsign_algorithm = HashSignAlgo, private_key = PrivateKey, @@ -1804,7 +1848,7 @@ key_exchange(#state{role = server, key_algorithm = rsa_psk, ServerRandom, PrivateKey}), Connection:queue_handshake(Msg, State0); -key_exchange(#state{role = server, key_algorithm = Algo, +key_exchange(#state{static_env = #static_env{role = server}, key_algorithm = Algo, ssl_options = #ssl_options{user_lookup_fun = LookupFun}, hashsign_algorithm = HashSignAlgo, session = #session{srp_username = Username}, @@ -1834,14 +1878,14 @@ key_exchange(#state{role = server, key_algorithm = Algo, State = Connection:queue_handshake(Msg, State0), State#state{srp_params = SrpParams, srp_keys = Keys}; -key_exchange(#state{role = client, +key_exchange(#state{static_env = #static_env{role = client}, key_algorithm = rsa, public_key_info = PublicKeyInfo, negotiated_version = Version, premaster_secret = PremasterSecret} = State0, Connection) -> Msg = rsa_key_exchange(ssl:tls_version(Version), PremasterSecret, PublicKeyInfo), Connection:queue_handshake(Msg, State0); -key_exchange(#state{role = client, +key_exchange(#state{static_env = #static_env{role = client}, key_algorithm = Algorithm, negotiated_version = Version, diffie_hellman_keys = {DhPubKey, _} @@ -1852,7 +1896,7 @@ key_exchange(#state{role = client, Msg = ssl_handshake:key_exchange(client, ssl:tls_version(Version), {dh, DhPubKey}), Connection:queue_handshake(Msg, State0); -key_exchange(#state{role = client, +key_exchange(#state{static_env = #static_env{role = client}, key_algorithm = Algorithm, negotiated_version = Version, session = Session, @@ -1862,14 +1906,14 @@ key_exchange(#state{role = client, Algorithm == ecdh_anon -> Msg = ssl_handshake:key_exchange(client, ssl:tls_version(Version), {ecdh, Key}), Connection:queue_handshake(Msg, State0#state{session = Session#session{ecc = ECCurve}}); -key_exchange(#state{role = client, +key_exchange(#state{static_env = #static_env{role = client}, ssl_options = SslOpts, key_algorithm = psk, negotiated_version = Version} = State0, Connection) -> Msg = ssl_handshake:key_exchange(client, ssl:tls_version(Version), {psk, SslOpts#ssl_options.psk_identity}), Connection:queue_handshake(Msg, State0); -key_exchange(#state{role = client, +key_exchange(#state{static_env = #static_env{role = client}, ssl_options = SslOpts, key_algorithm = dhe_psk, negotiated_version = Version, @@ -1879,7 +1923,7 @@ key_exchange(#state{role = client, SslOpts#ssl_options.psk_identity, DhPubKey}), Connection:queue_handshake(Msg, State0); -key_exchange(#state{role = client, +key_exchange(#state{static_env = #static_env{role = client}, ssl_options = SslOpts, key_algorithm = ecdhe_psk, negotiated_version = Version, @@ -1889,7 +1933,7 @@ key_exchange(#state{role = client, SslOpts#ssl_options.psk_identity, ECDHKeys}), Connection:queue_handshake(Msg, State0); -key_exchange(#state{role = client, +key_exchange(#state{static_env = #static_env{role = client}, ssl_options = SslOpts, key_algorithm = rsa_psk, public_key_info = PublicKeyInfo, @@ -1899,7 +1943,7 @@ key_exchange(#state{role = client, Msg = rsa_psk_key_exchange(ssl:tls_version(Version), SslOpts#ssl_options.psk_identity, PremasterSecret, PublicKeyInfo), Connection:queue_handshake(Msg, State0); -key_exchange(#state{role = client, +key_exchange(#state{static_env = #static_env{role = client}, key_algorithm = Algorithm, negotiated_version = Version, srp_keys = {ClientPubKey, _}} @@ -1949,11 +1993,11 @@ request_client_cert(#state{key_algorithm = Alg} = State, _) Alg == srp_dss; Alg == srp_rsa; Alg == srp_anon -> State; -request_client_cert(#state{ssl_options = #ssl_options{verify = verify_peer, - signature_algs = SupportedHashSigns}, - connection_states = ConnectionStates0, - cert_db = CertDbHandle, - cert_db_ref = CertDbRef, +request_client_cert(#state{static_env = #static_env{cert_db = CertDbHandle, + cert_db_ref = CertDbRef}, + ssl_options = #ssl_options{verify = verify_peer, + signature_algs = SupportedHashSigns}, + connection_states = ConnectionStates0, negotiated_version = Version} = State0, Connection) -> #{security_parameters := #security_parameters{cipher_suite = CipherSuite}} = @@ -1998,7 +2042,7 @@ finalize_handshake(State0, StateName, Connection) -> State = next_protocol(State2, Connection), finished(State, StateName, Connection). -next_protocol(#state{role = server} = State, _) -> +next_protocol(#state{static_env = #static_env{role = server}} = State, _) -> State; next_protocol(#state{negotiated_protocol = undefined} = State, _) -> State; @@ -2011,7 +2055,8 @@ next_protocol(#state{negotiated_protocol = NextProtocol} = State0, Connection) - cipher_protocol(State, Connection) -> Connection:queue_change_cipher(#change_cipher_spec{}, State). -finished(#state{role = Role, negotiated_version = Version, +finished(#state{static_env = #static_env{role = Role}, + negotiated_version = Version, session = Session, connection_states = ConnectionStates0, tls_handshake_history = Handshake0} = State0, StateName, Connection) -> @@ -2090,8 +2135,9 @@ calculate_secret(#server_srp_params{srp_n = Prime, srp_g = Generator} = ServerKe master_secret(#alert{} = Alert, _) -> Alert; -master_secret(PremasterSecret, #state{session = Session, - negotiated_version = Version, role = Role, +master_secret(PremasterSecret, #state{static_env = #static_env{role = Role}, + session = Session, + negotiated_version = Version, connection_states = ConnectionStates0} = State) -> case ssl_handshake:master_secret(ssl:tls_version(Version), PremasterSecret, ConnectionStates0, Role) of @@ -2325,18 +2371,18 @@ 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 = <<>>}}) when CertDb =/= undefined -> +handle_trusted_certs_db(#state{static_env = #static_env{cert_db_ref = Ref, + cert_db = CertDb}, + 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); -handle_trusted_certs_db(#state{file_ref_db = undefined}) -> +handle_trusted_certs_db(#state{static_env = #static_env{file_ref_db = undefined}}) -> %% Something went wrong early (typically cacertfile does not %% exist) so there is nothing to handle ok; -handle_trusted_certs_db(#state{cert_db_ref = Ref, - file_ref_db = RefDb, +handle_trusted_certs_db(#state{static_env = #static_env{cert_db_ref = Ref, + file_ref_db = RefDb}, ssl_options = #ssl_options{cacertfile = File}}) -> case ssl_pkix_db:ref_count(Ref, RefDb, -1) of 0 -> @@ -2399,19 +2445,22 @@ host_id(_, Host, _) -> Host. handle_new_session(NewId, CipherSuite, Compression, - #state{session = Session0, - protocol_cb = Connection} = State0) -> + #state{static_env = #static_env{protocol_cb = Connection}, + session = Session0 + } = State0) -> Session = Session0#session{session_id = NewId, cipher_suite = CipherSuite, compression_method = Compression}, Connection:next_event(certify, no_record, State0#state{session = Session}). -handle_resumed_session(SessId, #state{connection_states = ConnectionStates0, - negotiated_version = Version, - host = Host, port = Port, - protocol_cb = Connection, - session_cache = Cache, - session_cache_cb = CacheCb} = State) -> +handle_resumed_session(SessId, #state{static_env = #static_env{host = Host, + port = Port, + protocol_cb = Connection, + session_cache = Cache, + session_cache_cb = CacheCb}, + connection_states = ConnectionStates0, + negotiated_version = Version + } = State) -> Session = CacheCb:lookup(Cache, {{Host, Port}, SessId}), case ssl_handshake:master_secret(ssl:tls_version(Version), Session, ConnectionStates0, client) of @@ -2468,8 +2517,8 @@ ssl_options_list([Key | Keys], [Value | Values], 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) -> +handle_active_option(_, connection = StateName0, To, Reply, #state{static_env = #static_env{protocol_cb = Connection}, + user_data_buffer = <<>>} = State0) -> case Connection:next_event(StateName0, no_record, State0) of {next_state, StateName, State} -> hibernate_after(StateName, State, [{reply, To, Reply}]); @@ -2483,7 +2532,8 @@ handle_active_option(_, StateName, To, Reply, #state{user_data_buffer = <<>>} = {next_state, StateName, State, [{reply, To, Reply}]}; %% user_data_buffer =/= <<>> -handle_active_option(_, StateName0, To, Reply, #state{protocol_cb = Connection} = State0) -> +handle_active_option(_, StateName0, To, Reply, + #state{static_env = #static_env{protocol_cb = Connection}} = State0) -> case read_application_data(<<>>, State0) of {stop, _, _} = Stop -> Stop; @@ -2664,7 +2714,7 @@ invalidate_session(server, _, Port, Session) -> handle_sni_extension(undefined, State) -> State; -handle_sni_extension(#sni{hostname = Hostname}, State0) -> +handle_sni_extension(#sni{hostname = Hostname}, #state{static_env = #static_env{role = Role} = InitStatEnv0} = State0) -> NewOptions = update_ssl_options_from_sni(State0#state.ssl_options, Hostname), case NewOptions of undefined -> @@ -2678,14 +2728,16 @@ handle_sni_extension(#sni{hostname = Hostname}, State0) -> private_key := Key, dh_params := DHParams, own_certificate := OwnCert}} = - ssl_config:init(NewOptions, State0#state.role), + ssl_config:init(NewOptions, Role), State0#state{ session = State0#state.session#session{own_certificate = OwnCert}, - file_ref_db = FileRefHandle, - cert_db_ref = Ref, - cert_db = CertDbHandle, - crl_db = CRLDbHandle, - session_cache = CacheHandle, + static_env = InitStatEnv0#static_env{ + file_ref_db = FileRefHandle, + cert_db_ref = Ref, + cert_db = CertDbHandle, + crl_db = CRLDbHandle, + session_cache = CacheHandle + }, private_key = Key, diffie_hellman_params = DHParams, ssl_options = NewOptions, diff --git a/lib/ssl/src/ssl_connection.hrl b/lib/ssl/src/ssl_connection.hrl index 66e3182313..8ddc3199e1 100644 --- a/lib/ssl/src/ssl_connection.hrl +++ b/lib/ssl/src/ssl_connection.hrl @@ -33,71 +33,79 @@ -include("ssl_cipher.hrl"). -include_lib("public_key/include/public_key.hrl"). +-record(static_env, { + role :: client | server, + transport_cb :: atom(), % callback module + protocol_cb :: tls_connection | dtls_connection, + data_tag :: atom(), % ex tcp. + close_tag :: atom(), % ex tcp_closed + error_tag :: atom(), % ex tcp_error + host :: string() | inet:ip_address(), + port :: integer(), + socket :: port() | tuple(), %% TODO: dtls socket + cert_db :: reference() | 'undefined', + session_cache :: db_handle(), + session_cache_cb :: atom(), + crl_db :: term(), + file_ref_db :: db_handle(), + cert_db_ref :: certdb_ref() | 'undefined', + tracker :: pid() | 'undefined' %% Tracker process for listen socket + }). -record(state, { - role :: client | server, - user_application :: {Monitor::reference(), User::pid()}, - transport_cb :: atom(), % callback module - protocol_cb :: tls_connection | dtls_connection, - data_tag :: atom(), % ex tcp. - close_tag :: atom(), % ex tcp_closed - error_tag :: atom(), % ex tcp_error - host :: string() | inet:ip_address(), - port :: integer(), - socket :: port() | tuple(), %% TODO: dtls socket - sender :: pid() | undefined, - ssl_options :: #ssl_options{}, - socket_options :: #socket_options{}, - connection_states :: ssl_record:connection_states() | secret_printout(), - protocol_buffers :: term() | secret_printout() , %% #protocol_buffers{} from tls_record.hrl or dtls_recor.hrl - unprocessed_handshake_events = 0 :: integer(), - tls_handshake_history :: ssl_handshake:ssl_handshake_history() | secret_printout() - | 'undefined', - cert_db :: reference() | 'undefined', - session :: #session{} | secret_printout(), - session_cache :: db_handle(), - session_cache_cb :: atom(), - crl_db :: term(), - negotiated_version :: ssl_record:ssl_version() | 'undefined', - client_hello_version :: ssl_record:ssl_version() | 'undefined', - client_certificate_requested = false :: boolean(), - key_algorithm :: ssl_cipher_format:key_algo(), - hashsign_algorithm = {undefined, undefined}, - cert_hashsign_algorithm = {undefined, undefined}, - public_key_info :: ssl_handshake:public_key_info() | 'undefined', - private_key :: public_key:private_key() | secret_printout() | 'undefined', - diffie_hellman_params:: #'DHParameter'{} | undefined | secret_printout(), - diffie_hellman_keys :: {PublicKey :: binary(), PrivateKey :: binary()} | #'ECPrivateKey'{} | undefined | secret_printout(), - psk_identity :: binary() | 'undefined', % server psk identity hint - srp_params :: #srp_user{} | secret_printout() | 'undefined', - srp_keys ::{PublicKey :: binary(), PrivateKey :: binary()} | secret_printout() | 'undefined', - premaster_secret :: binary() | secret_printout() | 'undefined', - file_ref_db :: db_handle(), - cert_db_ref :: certdb_ref() | 'undefined', - bytes_to_read :: undefined | integer(), %% bytes to read in passive mode - user_data_buffer :: undefined | binary() | secret_printout(), - erl_dist_data = #{} :: map(), + static_env :: #static_env{}, + %% Change seldome + user_application :: {Monitor::reference(), User::pid()}, + ssl_options :: #ssl_options{}, + socket_options :: #socket_options{}, + session :: #session{} | secret_printout(), + allow_renegotiate = true ::boolean(), + terminated = false ::boolean(), + negotiated_version :: ssl_record:ssl_version() | 'undefined', + bytes_to_read :: undefined | integer(), %% bytes to read in passive mode + downgrade, + + %% Changed often + connection_states :: ssl_record:connection_states() | secret_printout(), + protocol_buffers :: term() | secret_printout() , %% #protocol_buffers{} from tls_record.hrl or dtls_recor.hr + user_data_buffer :: undefined | binary() | secret_printout(), + + %% Used only in HS + unprocessed_handshake_events = 0 :: integer(), + tls_handshake_history :: ssl_handshake:ssl_handshake_history() | secret_printout() + | 'undefined', + client_hello_version :: ssl_record:ssl_version() | 'undefined', + client_certificate_requested = false :: boolean(), + key_algorithm :: ssl_cipher_format:key_algo(), + hashsign_algorithm = {undefined, undefined}, + cert_hashsign_algorithm = {undefined, undefined}, + public_key_info :: ssl_handshake:public_key_info() | 'undefined', + private_key :: public_key:private_key() | secret_printout() | 'undefined', + diffie_hellman_params:: #'DHParameter'{} | undefined | secret_printout(), + diffie_hellman_keys :: {PublicKey :: binary(), PrivateKey :: binary()} | #'ECPrivateKey'{} | undefined | secret_printout(), + psk_identity :: binary() | 'undefined', % server psk identity hint + srp_params :: #srp_user{} | secret_printout() | 'undefined', + srp_keys ::{PublicKey :: binary(), PrivateKey :: binary()} | secret_printout() | 'undefined', + premaster_secret :: binary() | secret_printout() | 'undefined', renegotiation :: undefined | {boolean(), From::term() | internal | peer}, - start_or_recv_from :: term(), - timer :: undefined | reference(), % start_or_recive_timer - %%send_queue :: queue:queue(), - hello, %%:: #client_hello{} | #server_hello{}, - terminated = false ::boolean(), - allow_renegotiate = true ::boolean(), - expecting_next_protocol_negotiation = false ::boolean(), - expecting_finished = false ::boolean(), - next_protocol = undefined :: undefined | binary(), - negotiated_protocol, - tracker :: pid() | 'undefined', %% Tracker process for listen socket - sni_hostname = undefined, - downgrade, - flight_buffer = [] :: list() | map(), %% Buffer of TLS/DTLS records, used during the TLS handshake - %% to when possible pack more than one TLS record into the - %% underlaying packet format. Introduced by DTLS - RFC 4347. - %% The mecahnism is also usefull in TLS although we do not - %% need to worry about packet loss in TLS. In DTLS we need to track DTLS handshake seqnr - flight_state = reliable, %% reliable | {retransmit, integer()}| {waiting, ref(), integer()} - last two is used in DTLS over udp. - protocol_specific = #{} :: map() - }). + start_or_recv_from :: term(), + timer :: undefined | reference(), % start_or_recive_timer + hello, %%:: #client_hello{} | #server_hello{}, + expecting_next_protocol_negotiation = false ::boolean(), + expecting_finished = false ::boolean(), + next_protocol = undefined :: undefined | binary(), + negotiated_protocol, + sni_hostname = undefined, + flight_buffer = [] :: list() | map(), %% Buffer of TLS/DTLS records, used during the TLS handshake + %% to when possible pack more than one TLS record into the + %% underlaying packet format. Introduced by DTLS - RFC 4347. + %% The mecahnism is also usefull in TLS although we do not + %% need to worry about packet loss in TLS. In DTLS we need to track DTLS handshake seqnr + flight_state = reliable, %% reliable | {retransmit, integer()}| {waiting, ref(), integer()} - last two is used in DTLS over udp. + erl_dist_data = #{} :: map(), + protocol_specific = #{} :: map() + }). + + -define(DEFAULT_DIFFIE_HELLMAN_PARAMS, #'DHParameter'{prime = ?DEFAULT_DIFFIE_HELLMAN_PRIME, base = ?DEFAULT_DIFFIE_HELLMAN_GENERATOR}). diff --git a/lib/ssl/src/tls_connection.erl b/lib/ssl/src/tls_connection.erl index 0f986b5e21..098e47d609 100644 --- a/lib/ssl/src/tls_connection.erl +++ b/lib/ssl/src/tls_connection.erl @@ -162,10 +162,11 @@ next_record(#state{protocol_buffers = end; next_record(#state{protocol_buffers = #protocol_buffers{tls_packets = [], tls_cipher_texts = []}, protocol_specific = #{active_n_toggle := true, active_n := N} = ProtocolSpec, - socket = Socket, - close_tag = CloseTag, - transport_cb = Transport} = State) -> - case tls_socket:setopts(Transport, Socket, [{active, N}]) of + static_env = #static_env{socket = Socket, + close_tag = CloseTag, + transport_cb = Transport} + } = State) -> + case tls_socket:setopts(Transport, Socket, [{active, N}]) of ok -> {no_record, State#state{protocol_specific = ProtocolSpec#{active_n_toggle => false}}}; _ -> @@ -259,15 +260,15 @@ handle_common_event(internal, #ssl_tls{type = _Unknown}, StateName, State) -> renegotiation(Pid, WriteState) -> gen_statem:call(Pid, {user_renegotiate, WriteState}). -renegotiate(#state{role = client} = State, Actions) -> +renegotiate(#state{static_env = #static_env{role = client}} = State, Actions) -> %% Handle same way as if server requested %% the renegotiation Hs0 = ssl_handshake:init_handshake_history(), {next_state, connection, State#state{tls_handshake_history = Hs0}, [{next_event, internal, #hello_request{}} | Actions]}; -renegotiate(#state{role = server, - socket = Socket, - transport_cb = Transport, +renegotiate(#state{static_env = #static_env{role = server, + socket = Socket, + transport_cb = Transport}, negotiated_version = Version, connection_states = ConnectionStates0} = State0, Actions) -> HelloRequest = ssl_handshake:hello_request(), @@ -294,8 +295,8 @@ queue_handshake(Handshake, #state{negotiated_version = Version, tls_handshake_history = Hist, flight_buffer = Flight0 ++ [BinHandshake]}. -send_handshake_flight(#state{socket = Socket, - transport_cb = Transport, +send_handshake_flight(#state{static_env = #static_env{socket = Socket, + transport_cb = Transport}, flight_buffer = Flight} = State0) -> send(Transport, Socket, Flight), {State0#state{flight_buffer = []}, []}. @@ -345,8 +346,8 @@ encode_alert(#alert{} = Alert, Version, ConnectionStates) -> tls_record:encode_alert_record(Alert, Version, ConnectionStates). send_alert(Alert, #state{negotiated_version = Version, - socket = Socket, - transport_cb = Transport, + static_env = #static_env{socket = Socket, + transport_cb = Transport}, connection_states = ConnectionStates0} = StateData0) -> {BinMsg, ConnectionStates} = encode_alert(Alert, Version, ConnectionStates0), @@ -429,14 +430,17 @@ getopts(Transport, Socket, Tag) -> %%-------------------------------------------------------------------- init({call, From}, {start, Timeout}, - #state{host = Host, port = Port, role = client, + #state{static_env = #static_env{role = client, + host = Host, + port = Port, + transport_cb = Transport, + socket = Socket, + session_cache = Cache, + session_cache_cb = CacheCb}, ssl_options = SslOpts, session = #session{own_certificate = Cert} = Session0, - transport_cb = Transport, socket = Socket, connection_states = ConnectionStates0, - renegotiation = {Renegotiation, _}, - session_cache = Cache, - session_cache_cb = CacheCb + renegotiation = {Renegotiation, _} } = State0) -> Timer = ssl_connection:start_or_recv_cancel_timer(Timeout, From), Hello = tls_handshake:client_hello(Host, Port, ConnectionStates0, SslOpts, @@ -493,10 +497,12 @@ hello(internal, #server_hello{extensions = Extensions} = Hello, [{reply, From, {ok, ssl_connection:map_extensions(Extensions)}}]}; hello(internal, #client_hello{client_version = ClientVersion} = Hello, #state{connection_states = ConnectionStates0, - port = Port, session = #session{own_certificate = Cert} = Session0, + static_env = #static_env{ + port = Port, + session_cache = Cache, + session_cache_cb = CacheCb}, + 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) -> @@ -523,7 +529,7 @@ hello(internal, #client_hello{client_version = ClientVersion} = Hello, hello(internal, #server_hello{} = Hello, #state{connection_states = ConnectionStates0, negotiated_version = ReqVersion, - role = client, + static_env = #static_env{role = client}, renegotiation = {Renegotiation, _}, ssl_options = SslOptions} = State) -> case tls_handshake:hello(Hello, SslOptions, ConnectionStates0, Renegotiation) of @@ -581,11 +587,13 @@ connection({call, From}, {user_renegotiate, WriteState}, {next_state, ?FUNCTION_NAME, State#state{connection_states = ConnectionStates#{current_write => WriteState}}, [{next_event,{call, From}, renegotiate}]}; connection(internal, #hello_request{}, - #state{role = client, + #state{static_env = #static_env{role = client, + host = Host, + port = Port, + session_cache = Cache, + session_cache_cb = CacheCb}, renegotiation = {Renegotiation, _}, - host = Host, port = Port, session = #session{own_certificate = Cert} = Session0, - session_cache = Cache, session_cache_cb = CacheCb, ssl_options = SslOpts, connection_states = ConnectionStates} = State0) -> Hello = tls_handshake:client_hello(Host, Port, ConnectionStates, SslOpts, @@ -594,8 +602,9 @@ connection(internal, #hello_request{}, next_event(hello, no_record, State#state{session = Session0#session{session_id = Hello#client_hello.session_id}}, Actions); connection(internal, #client_hello{} = Hello, - #state{role = server, allow_renegotiate = true, connection_states = CS, - %%protocol_cb = Connection, + #state{static_env = #static_env{role = server}, + allow_renegotiate = true, + connection_states = CS, protocol_specific = #{sender := Sender} } = State) -> %% Mitigate Computational DoS attack @@ -611,8 +620,9 @@ connection(internal, #client_hello{} = Hello, }, [{next_event, internal, Hello}]); connection(internal, #client_hello{}, - #state{role = server, allow_renegotiate = false, - protocol_cb = Connection} = State0) -> + #state{static_env = #static_env{role = server, + protocol_cb = Connection}, + allow_renegotiate = false} = State0) -> Alert = ?ALERT_REC(?WARNING, ?NO_RENEGOTIATION), send_alert_in_connection(Alert, State0), State = Connection:reinit_handshake_data(State0), @@ -633,10 +643,10 @@ downgrade(Type, Event, State) -> callback_mode() -> state_functions. - -terminate( - {shutdown, sender_died, Reason}, _StateName, - #state{socket = Socket, transport_cb = Transport} = State) -> +terminate({shutdown, sender_died, Reason}, _StateName, + #state{static_env = #static_env{socket = Socket, + transport_cb = Transport}} + = State) -> ssl_connection:handle_trusted_certs_db(State), close(Reason, Socket, Transport, undefined, undefined); terminate(Reason, StateName, State) -> @@ -665,44 +675,45 @@ initial_state(Role, Sender, Host, Port, Socket, {SSLOptions, SocketOptions, Trac _ -> ssl_session_cache end, - InternalActiveN = case application:get_env(ssl, internal_active_n) of {ok, N} when is_integer(N) andalso (not IsErlDist) -> N; _ -> ?INTERNAL_ACTIVE_N end, - UserMonitor = erlang:monitor(process, User), - - #state{socket_options = SocketOptions, - ssl_options = SSLOptions, - session = #session{is_resumable = new}, - transport_cb = CbModule, - data_tag = DataTag, - close_tag = CloseTag, - error_tag = ErrorTag, - role = Role, - host = Host, - port = Port, - socket = Socket, - erl_dist_data = ErlDistData, - connection_states = ConnectionStates, - protocol_buffers = #protocol_buffers{}, - user_application = {UserMonitor, User}, - user_data_buffer = <<>>, - session_cache_cb = SessionCacheCb, - renegotiation = {false, first}, - allow_renegotiate = SSLOptions#ssl_options.client_renegotiation, - start_or_recv_from = undefined, - protocol_cb = ?MODULE, - tracker = Tracker, - flight_buffer = [], - protocol_specific = #{sender => Sender, - active_n => InternalActiveN, - active_n_toggle => true - } - }. + InitStatEnv = #static_env{ + role = Role, + transport_cb = CbModule, + protocol_cb = ?MODULE, + data_tag = DataTag, + close_tag = CloseTag, + error_tag = ErrorTag, + host = Host, + port = Port, + socket = Socket, + session_cache_cb = SessionCacheCb, + tracker = Tracker + }, + #state{ + static_env = InitStatEnv, + socket_options = SocketOptions, + ssl_options = SSLOptions, + session = #session{is_resumable = new}, + erl_dist_data = ErlDistData, + connection_states = ConnectionStates, + protocol_buffers = #protocol_buffers{}, + user_application = {UserMonitor, User}, + user_data_buffer = <<>>, + renegotiation = {false, first}, + allow_renegotiate = SSLOptions#ssl_options.client_renegotiation, + start_or_recv_from = undefined, + flight_buffer = [], + protocol_specific = #{sender => Sender, + active_n => InternalActiveN, + active_n_toggle => true + } + }. erl_dist_data(true) -> #{dist_handle => undefined, @@ -710,12 +721,14 @@ erl_dist_data(true) -> erl_dist_data(false) -> #{}. -initialize_tls_sender(#state{role = Role, - socket = Socket, +initialize_tls_sender(#state{static_env = #static_env{ + role = Role, + transport_cb = Transport, + protocol_cb = Connection, + socket = Socket, + tracker = Tracker + }, socket_options = SockOpts, - tracker = Tracker, - protocol_cb = Connection, - transport_cb = Transport, negotiated_version = Version, ssl_options = #ssl_options{renegotiate_at = RenegotiateAt}, connection_states = #{current_write := ConnectionWriteState}, @@ -762,8 +775,7 @@ tls_handshake_events(Packets) -> %% raw data from socket, upack records handle_info({Protocol, _, Data}, StateName, - #state{data_tag = Protocol - } = State0) -> + #state{static_env = #static_env{data_tag = Protocol}} = State0) -> case next_tls_record(Data, StateName, State0) of {Record, State} -> next_event(StateName, Record, State); @@ -771,12 +783,14 @@ handle_info({Protocol, _, Data}, StateName, ssl_connection:handle_normal_shutdown(Alert, StateName, State0), ssl_connection:stop({shutdown, own_alert}, State0) end; -handle_info({tcp_passive, Socket}, StateName, #state{socket = Socket, - protocol_specific = PS - } = State) -> - next_event(StateName, no_record, State#state{protocol_specific = PS#{active_n_toggle => true}}); +handle_info({tcp_passive, Socket}, StateName, + #state{static_env = #static_env{socket = Socket}, + protocol_specific = PS + } = State) -> + next_event(StateName, no_record, + State#state{protocol_specific = PS#{active_n_toggle => true}}); handle_info({CloseTag, Socket}, StateName, - #state{socket = Socket, close_tag = CloseTag, + #state{static_env = #static_env{socket = Socket, close_tag = CloseTag}, socket_options = #socket_options{active = Active}, protocol_buffers = #protocol_buffers{tls_cipher_texts = CTs}, user_data_buffer = Buffer, diff --git a/lib/ssl/test/ssl_basic_SUITE.erl b/lib/ssl/test/ssl_basic_SUITE.erl index 9633800da5..a00fa86b61 100644 --- a/lib/ssl/test/ssl_basic_SUITE.erl +++ b/lib/ssl/test/ssl_basic_SUITE.erl @@ -3980,8 +3980,8 @@ tls_tcp_error_propagation_in_active_mode(Config) when is_list(Config) -> {status, _, _, StatusInfo} = sys:get_status(Pid), [_, _,_, _, Prop] = StatusInfo, State = ssl_test_lib:state(Prop), - Socket = element(11, State), - + StaticEnv = element(2, State), + Socket = element(10, StaticEnv), %% Fake tcp error Pid ! {tcp_error, Socket, etimedout}, |