diff options
Diffstat (limited to 'lib/ssl/src')
39 files changed, 1972 insertions, 1058 deletions
diff --git a/lib/ssl/src/Makefile b/lib/ssl/src/Makefile index 1e55bb497d..789bed5c3f 100644 --- a/lib/ssl/src/Makefile +++ b/lib/ssl/src/Makefile @@ -173,7 +173,7 @@ ERL_COMPILE_FLAGS += -I$(ERL_TOP)/lib/kernel/src \ # Targets # ---------------------------------------------------- -opt debug: $(TARGET_FILES) $(APP_TARGET) $(APPUP_TARGET) $(DEP_FILE) +$(TYPES): $(TARGET_FILES) $(APP_TARGET) $(APPUP_TARGET) $(DEP_FILE) deps: $(DEP_FILE) diff --git a/lib/ssl/src/dtls_connection.erl b/lib/ssl/src/dtls_connection.erl index 65076994f9..08229d8bb5 100644 --- a/lib/ssl/src/dtls_connection.erl +++ b/lib/ssl/src/dtls_connection.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2013-2022. All Rights Reserved. +%% Copyright Ericsson AB 2013-2023. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -46,7 +46,8 @@ %% ClientKeyExchange \ %% CertificateVerify* Flight 5 %% [ChangeCipherSpec] / -%% Finished --------> / +%% NextProtocol* / +%% Finished --------> / %% %% [ChangeCipherSpec] \ Flight 6 %% <-------- Finished / @@ -64,7 +65,8 @@ %% <-------- Finished / part 2 %% %% [ChangeCipherSpec] \ Abbrev Flight 3 -%% Finished --------> / +%% NextProtocol* / +%% Finished --------> / %% %% %% Message Flights for Abbbriviated Handshake @@ -142,6 +144,7 @@ user_hello/3, wait_ocsp_stapling/3, certify/3, + wait_cert_verify/3, cipher/3, abbreviated/3, connection/3]). @@ -160,16 +163,15 @@ %%==================================================================== init([Role, Host, Port, Socket, Options, User, CbInfo]) -> process_flag(trap_exit, true), - State0 = #state{protocol_specific = Map} = - initial_state(Role, Host, Port, Socket, Options, User, CbInfo), + State0 = initial_state(Role, Host, Port, Socket, Options, User, CbInfo), try State = ssl_gen_statem:ssl_config(State0#state.ssl_options, Role, State0), gen_statem:enter_loop(?MODULE, [], initial_hello, State) catch throw:Error -> - EState = State0#state{protocol_specific = - Map#{error => Error}}, + #state{protocol_specific = Map} = State0, + EState = State0#state{protocol_specific = Map#{error => Error}}, gen_statem:enter_loop(?MODULE, [], config_error, EState) end. %%==================================================================== @@ -208,13 +210,14 @@ initial_hello({call, From}, {start, Timeout}, session_cache_cb = CacheCb}, protocol_specific = PS, handshake_env = #handshake_env{renegotiation = {Renegotiation, _}}, - connection_env = #connection_env{cert_key_pairs = CertKeyPairs} = CEnv, + connection_env = #connection_env{cert_key_alts = CertKeyAlts} = CEnv, ssl_options = #{versions := Versions} = SslOpts, session = Session0, connection_states = ConnectionStates0 } = State0) -> Packages = maps:get(active_n, PS), dtls_socket:setopts(Transport, Socket, [{active,Packages}]), + CertKeyPairs = ssl_certificate:available_cert_key_pairs(CertKeyAlts), Session = ssl_session:client_select_session({Host, Port, SslOpts}, Cache, CacheCb, Session0, CertKeyPairs), Hello = dtls_handshake:client_hello(Host, Port, ConnectionStates0, SslOpts, Session#session.session_id, Renegotiation), @@ -324,14 +327,12 @@ hello(internal, #hello_verify_request{cookie = Cookie}, }, dtls_gen_connection:next_event(?FUNCTION_NAME, no_record, State, Actions); hello(internal, #client_hello{extensions = Extensions} = Hello, - #state{ssl_options = #{handshake := hello}, - handshake_env = HsEnv, + #state{handshake_env = #handshake_env{continue_status = pause}, start_or_recv_from = From} = State0) -> try tls_dtls_connection:handle_sni_extension(State0, Hello) of #state{} = State -> - {next_state, user_hello, State#state{start_or_recv_from = undefined, - handshake_env = HsEnv#handshake_env{hello = Hello}}, - [{reply, From, {ok, Extensions}}]} + {next_state, user_hello, State#state{start_or_recv_from = undefined}, + [{postpone, true}, {reply, From, {ok, Extensions}}]} catch throw:#alert{} = Alert -> alert_or_reset_connection(Alert, ?FUNCTION_NAME, State0) end; @@ -354,16 +355,11 @@ hello(internal, #client_hello{cookie = Cookie} = Hello, #state{static_env = #sta hello(internal, Hello#client_hello{cookie = <<>>}, State) end end; -hello(internal, #server_hello{extensions = Extensions} = Hello, - #state{ssl_options = #{ - handshake := hello}, - handshake_env = HsEnv, +hello(internal, #server_hello{extensions = Extensions}, + #state{handshake_env = #handshake_env{continue_status = pause}, start_or_recv_from = From} = State) -> - {next_state, user_hello, State#state{start_or_recv_from = undefined, - handshake_env = HsEnv#handshake_env{ - hello = Hello}}, - [{reply, From, {ok, Extensions}}]}; - + {next_state, user_hello, State#state{start_or_recv_from = undefined}, + [{postpone, true},{reply, From, {ok, Extensions}}]}; hello(internal, #server_hello{} = Hello, #state{ static_env = #static_env{role = client}, @@ -469,6 +465,24 @@ certify(state_timeout, Event, State) -> certify(Type, Event, State) -> gen_handshake(?FUNCTION_NAME, Type, Event, State). + +%%-------------------------------------------------------------------- +-spec wait_cert_verify(gen_statem:event_type(), term(), #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +wait_cert_verify(enter, _Event, State0) -> + {State, Actions} = handle_flight_timer(State0), + {keep_state, State, Actions}; +wait_cert_verify(info, Event, State) -> + gen_info(Event, ?FUNCTION_NAME, State); +wait_cert_verify(state_timeout, Event, State) -> + handle_state_timeout(Event, ?FUNCTION_NAME, State); +wait_cert_verify(Type, Event, State) -> + try tls_dtls_connection:gen_handshake(?FUNCTION_NAME, Type, Event, State) + catch throw:#alert{} = Alert -> + ssl_gen_statem:handle_own_alert(Alert, ?FUNCTION_NAME, State) + end. + %%-------------------------------------------------------------------- -spec cipher(gen_statem:event_type(), term(), #state{}) -> gen_statem:state_function_result(). @@ -498,13 +512,20 @@ cipher(Type, Event, State) -> #hello_request{} | #client_hello{}| term(), #state{}) -> gen_statem:state_function_result(). %%-------------------------------------------------------------------- -connection(enter, _, #state{connection_states = Cs0} = State0) -> - State = case maps:is_key(previous_cs, Cs0) of - false -> - State0; - true -> - Cs = maps:remove(previous_cs, Cs0), - State0#state{connection_states = Cs} +connection(enter, _, #state{connection_states = Cs0, + static_env = Env} = State0) -> + State = case Env of + #static_env{socket = {Listener, {Client, _}}} -> + dtls_packet_demux:connection_setup(Listener, Client), + case maps:is_key(previous_cs, Cs0) of + false -> + State0; + true -> + Cs = maps:remove(previous_cs, Cs0), + State0#state{connection_states = Cs} + end; + _ -> %% client + State0 end, {keep_state, State}; connection(info, Event, State) -> @@ -516,13 +537,14 @@ connection(internal, #hello_request{}, #state{static_env = #static_env{host = Ho session_cache_cb = CacheCb }, handshake_env = #handshake_env{renegotiation = {Renegotiation, _}}, - connection_env = #connection_env{cert_key_pairs = CertKeyPairs} = CEnv, + connection_env = #connection_env{cert_key_alts = CertKeyAlts} = CEnv, session = Session0, ssl_options = #{versions := Versions} = SslOpts, connection_states = ConnectionStates0, protocol_specific = PS } = State0) -> #{current_cookie_secret := Cookie} = PS, + CertKeyPairs = ssl_certificate:available_cert_key_pairs(CertKeyAlts), Session = ssl_session:client_select_session({Host, Port, SslOpts}, Cache, CacheCb, Session0, CertKeyPairs), Hello = dtls_handshake:client_hello(Host, Port, Cookie, ConnectionStates0, SslOpts, Session#session.session_id, Renegotiation, undefined), @@ -557,14 +579,20 @@ connection(internal, #client_hello{}, #state{static_env = #static_env{role = ser dtls_gen_connection:next_event(?FUNCTION_NAME, Record, State); connection(internal, new_connection, #state{ssl_options=SSLOptions, handshake_env=HsEnv, + static_env = #static_env{socket = {Listener, {Client, _}}}, connection_states = OldCs} = State) -> case maps:get(previous_cs, OldCs, undefined) of undefined -> - BeastMitigation = maps:get(beast_mitigation, SSLOptions, disabled), - ConnectionStates0 = dtls_record:init_connection_states(server, BeastMitigation), - ConnectionStates = ConnectionStates0#{previous_cs => OldCs}, - {next_state, hello, State#state{handshake_env = HsEnv#handshake_env{renegotiation = {false, first}}, - connection_states = ConnectionStates}}; + case dtls_packet_demux:new_connection(Listener, Client) of + true -> + {keep_state, State}; + false -> + BeastMitigation = maps:get(beast_mitigation, SSLOptions, disabled), + ConnectionStates0 = dtls_record:init_connection_states(server, BeastMitigation), + ConnectionStates = ConnectionStates0#{previous_cs => OldCs}, + {next_state, hello, State#state{handshake_env = HsEnv#handshake_env{renegotiation = {false, first}}, + connection_states = ConnectionStates}} + end; _ -> %% Someone spamming new_connection, just drop them {keep_state, State} @@ -628,6 +656,7 @@ format_status(Type, Data) -> initial_state(Role, Host, Port, Socket, {#{client_renegotiation := ClientRenegotiation} = SSLOptions, SocketOptions, Trackers}, User, {CbModule, DataTag, CloseTag, ErrorTag, PassiveTag}) -> + put(log_level, maps:get(log_level, SSLOptions)), BeastMitigation = maps:get(beast_mitigation, SSLOptions, disabled), ConnectionStates = dtls_record:init_connection_states(Role, BeastMitigation), #{session_cb := SessionCacheCb} = ssl_config:pre_1_3_session_opts(Role), @@ -681,14 +710,14 @@ handle_client_hello(#client_hello{client_version = ClientVersion} = Hello, State handshake_env = #handshake_env{kex_algorithm = KeyExAlg, renegotiation = {Renegotiation, _}, negotiated_protocol = CurrentProtocol} = HsEnv, - connection_env = #connection_env{cert_key_pairs = CertKeyPairs} = CEnv, + connection_env = #connection_env{cert_key_alts = CertKeyAlts} = CEnv, session = Session0, ssl_options = SslOpts} = tls_dtls_connection:handle_sni_extension(State0, Hello), SessionTracker = proplists:get_value(session_id_tracker, Trackers), {Version, {Type, Session}, ConnectionStates, Protocol0, ServerHelloExt, HashSign} = dtls_handshake:hello(Hello, SslOpts, {SessionTracker, Session0, - ConnectionStates0, CertKeyPairs, KeyExAlg}, Renegotiation), + ConnectionStates0, CertKeyAlts, KeyExAlg}, Renegotiation), Protocol = case Protocol0 of undefined -> CurrentProtocol; _ -> Protocol0 @@ -738,20 +767,23 @@ gen_handshake(StateName, Type, Event, State) -> catch throw:#alert{}=Alert -> alert_or_reset_connection(Alert, StateName, State); - error:_ -> + error:Reason:ST -> + ?SSL_LOG(info, handshake_error, [{error, Reason}, {stacktrace, ST}]), Alert = ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, malformed_handshake_data), alert_or_reset_connection(Alert, StateName, State) end. gen_info(Event, connection = StateName, State) -> try dtls_gen_connection:handle_info(Event, StateName, State) - catch error:_ -> + catch error:Reason:ST -> + ?SSL_LOG(info, internal_error, [{error, Reason}, {stacktrace, ST}]), Alert = ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, malformed_data), alert_or_reset_connection(Alert, StateName, State) end; gen_info(Event, StateName, State) -> try dtls_gen_connection:handle_info(Event, StateName, State) - catch error:_ -> + catch error:Reason:ST -> + ?SSL_LOG(info, handshake_error, [{error, Reason}, {stacktrace, ST}]), Alert = ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE,malformed_handshake_data), alert_or_reset_connection(Alert, StateName, State) end. diff --git a/lib/ssl/src/dtls_gen_connection.erl b/lib/ssl/src/dtls_gen_connection.erl index 4964d3d21f..c075fa5879 100644 --- a/lib/ssl/src/dtls_gen_connection.erl +++ b/lib/ssl/src/dtls_gen_connection.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2020-2022. All Rights Reserved. +%% Copyright Ericsson AB 2020-2023. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -572,6 +572,12 @@ handle_info(new_cookie_secret, StateName, {next_state, StateName, State#state{protocol_specific = CookieInfo#{current_cookie_secret => dtls_v1:cookie_secret(), previous_cookie_secret => Secret}}}; +handle_info({socket_reused, Client}, StateName, + #state{static_env = #static_env{socket = {_, {Client, _}}}} = State) -> + Alert = ?ALERT_REC(?FATAL, ?CLOSE_NOTIFY, transport_closed), + ssl_gen_statem:handle_normal_shutdown(Alert#alert{role = server}, StateName, State), + {stop, {shutdown, transport_closed}, State}; + handle_info(Msg, StateName, State) -> ssl_gen_statem:handle_info(Msg, StateName, State). diff --git a/lib/ssl/src/dtls_packet_demux.erl b/lib/ssl/src/dtls_packet_demux.erl index 19f1d2359c..c4cdb2eb01 100644 --- a/lib/ssl/src/dtls_packet_demux.erl +++ b/lib/ssl/src/dtls_packet_demux.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2016-2022. All Rights Reserved. +%% Copyright Ericsson AB 2016-2023. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -33,6 +33,8 @@ sockname/1, close/1, new_owner/1, + new_connection/2, + connection_setup/2, get_all_opts/1, set_all_opts/2, get_sock_opts/2, @@ -55,7 +57,6 @@ dtls_options, emulated_options, dtls_msq_queues = kv_new(), - clients = set_new(), dtls_processes = kv_new(), accepters = queue:new(), first, @@ -85,6 +86,12 @@ close(PacketSocket) -> new_owner(PacketSocket) -> call(PacketSocket, new_owner). +new_connection(PacketSocket, Client) -> + call(PacketSocket, {new_connection, Client, self()}). + +connection_setup(PacketSocket, Client) -> + gen_server:cast(PacketSocket, {connection_setup, Client}). + get_sock_opts(PacketSocket, SplitSockOpts) -> call(PacketSocket, {get_sock_opts, SplitSockOpts}). get_all_opts(PacketSocket) -> @@ -146,6 +153,18 @@ handle_call(close, _, #state{dtls_processes = Processes, end; handle_call(new_owner, _, State) -> {reply, ok, State#state{close = false, first = true}}; +handle_call({new_connection, Old, _Pid}, _, + #state{accepters = Accepters, dtls_msq_queues = MsgQs0} = State) -> + case queue:is_empty(Accepters) of + false -> + OldQueue = kv_get(Old, MsgQs0), + MsgQs1 = kv_delete(Old, MsgQs0), + MsgQs = kv_insert({old,Old}, OldQueue, MsgQs1), + {reply, true, State#state{dtls_msq_queues = MsgQs}}; + true -> + {reply, false, State} + end; + handle_call({get_sock_opts, {SocketOptNames, EmOptNames}}, _, #state{listener = Socket, emulated_options = EmOpts} = State) -> case get_socket_opts(Socket, SocketOptNames) of @@ -170,7 +189,16 @@ handle_call({getstat, Options}, _, #state{listener = Socket, transport = {Tran handle_cast({active_once, Client, Pid}, State0) -> State = handle_active_once(Client, Pid, State0), - {noreply, State}. + {noreply, State}; +handle_cast({connection_setup, Client}, #state{dtls_msq_queues = MsgQueues} = State) -> + case kv_lookup({old, Client}, MsgQueues) of + none -> + {noreply, State}; + {value, {Pid, _}} -> + Pid ! {socket_reused, Client}, + %% Will be deleted when handling DOWN message + {noreply, State} + end. handle_info({Transport, Socket, IP, InPortNo, _} = Msg, #state{listener = Socket, transport = {_,Transport,_,_,_}} = State0) -> State = handle_datagram({IP, InPortNo}, Msg, State0), @@ -190,24 +218,40 @@ handle_info({udp_error, Socket, econnreset = Error}, #state{listener = Socket, t ?LOG_NOTICE(Report), {noreply, State}; handle_info({ErrorTag, Socket, Error}, #state{listener = Socket, transport = {_,_,_, ErrorTag,_}} = State) -> - Report = io_lib:format("SSL Packet muliplxer shutdown: Socket error: ~p ~n", [Error]), + Report = io_lib:format("SSL Packet muliplexer shutdown: Socket error: ~p ~n", [Error]), ?LOG_NOTICE(Report), {noreply, State#state{close=true}}; -handle_info({'DOWN', _, process, Pid, _}, #state{clients = Clients, - dtls_processes = Processes0, - dtls_msq_queues = MsgQueues0, - close = ListenClosed} = State) -> +handle_info({'DOWN', _, process, Pid, _}, + #state{dtls_processes = Processes0, + dtls_msq_queues = MsgQueues0, + close = ListenClosed} = State0) -> Client = kv_get(Pid, Processes0), Processes = kv_delete(Pid, Processes0), - MsgQueues = kv_delete(Client, MsgQueues0), + State = case kv_lookup(Client, MsgQueues0) of + none -> + MsgQueues1 = kv_delete({old, Client}, MsgQueues0), + State0#state{dtls_processes = Processes, dtls_msq_queues = MsgQueues1}; + {value, {Pid, _}} -> + MsgQueues1 = kv_delete(Client, MsgQueues0), + %% Restore old process if exists + case kv_lookup({old, Client}, MsgQueues1) of + none -> + State0#state{dtls_processes = Processes, dtls_msq_queues = MsgQueues1}; + {value, Old} -> + MsgQueues2 = kv_delete({old, Client}, MsgQueues1), + MsgQueues = kv_insert(Client, Old, MsgQueues2), + State0#state{dtls_processes = Processes, dtls_msq_queues = MsgQueues} + end; + {value, _} -> %% Old process died (just delete its queue) + MsgQueues1 = kv_delete({old, Client}, MsgQueues0), + State0#state{dtls_processes = Processes, dtls_msq_queues = MsgQueues1} + end, case ListenClosed andalso kv_empty(Processes) of true -> {stop, normal, State}; false -> - {noreply, State#state{clients = set_delete(Client, Clients), - dtls_processes = Processes, - dtls_msq_queues = MsgQueues}} + {noreply, State} end. terminate(_Reason, _State) -> @@ -219,55 +263,57 @@ code_change(_OldVsn, State, _Extra) -> %%%=================================================================== %%% Internal functions %%%=================================================================== -handle_datagram(Client, Msg, #state{clients = Clients, - accepters = AcceptorsQueue0} = State) -> - case set_is_member(Client, Clients) of - false -> +handle_datagram(Client, Msg, #state{dtls_msq_queues = MsgQueues, accepters = AcceptorsQueue0} = State) -> + case kv_lookup(Client, MsgQueues) of + none -> case queue:out(AcceptorsQueue0) of - {{value, {UserPid, From}}, AcceptorsQueue} -> - setup_new_connection(UserPid, From, Client, Msg, + {{value, {UserPid, From}}, AcceptorsQueue} -> + setup_new_connection(UserPid, From, Client, Msg, State#state{accepters = AcceptorsQueue}); {empty, _} -> %% Drop packet client will resend State end; - true -> - dispatch(Client, Msg, State) + {value, Queue} -> + dispatch(Queue, Client, Msg, State) end. -dispatch(Client, Msg, #state{dtls_msq_queues = MsgQueues} = State) -> - case kv_lookup(Client, MsgQueues) of - {value, Queue0} -> - case queue:out(Queue0) of - {{value, Pid}, Queue} when is_pid(Pid) -> - Pid ! Msg, - State#state{dtls_msq_queues = - kv_update(Client, Queue, MsgQueues)}; - {{value, _UDP}, _Queue} -> - State#state{dtls_msq_queues = - kv_update(Client, queue:in(Msg, Queue0), MsgQueues)}; - {empty, Queue} -> - State#state{dtls_msq_queues = - kv_update(Client, queue:in(Msg, Queue), MsgQueues)} - end +dispatch({Pid, Queue0}, Client, Msg, #state{dtls_msq_queues = MsgQueues} = State) -> + case queue:out(Queue0) of + {{value, Pid}, Queue} when is_pid(Pid) -> + Pid ! Msg, + State#state{dtls_msq_queues = + kv_update(Client, {Pid, Queue}, MsgQueues)}; + {{value, _UDP}, _Queue} -> + State#state{dtls_msq_queues = + kv_update(Client, {Pid, queue:in(Msg, Queue0)}, MsgQueues)}; + {empty, Queue} -> + State#state{dtls_msq_queues = + kv_update(Client, {Pid, queue:in(Msg, Queue)}, MsgQueues)} end. + next_datagram(Socket, N) -> inet:setopts(Socket, [{active, N}]). handle_active_once(Client, Pid, #state{dtls_msq_queues = MsgQueues} = State0) -> - Queue0 = kv_get(Client, MsgQueues), + {Key, Queue0} = case kv_lookup(Client, MsgQueues) of + {value, {Pid, Q0}} -> {Client, Q0}; + _ -> + OldKey = {old, Client}, + {Pid, Q0} = kv_get(OldKey, MsgQueues), + {OldKey, Q0} + end, case queue:out(Queue0) of - {{value, Pid}, _} when is_pid(Pid) -> - State0; - {{value, Msg}, Queue} -> - Pid ! Msg, - State0#state{dtls_msq_queues = kv_update(Client, Queue, MsgQueues)}; - {empty, Queue0} -> - State0#state{dtls_msq_queues = kv_update(Client, queue:in(Pid, Queue0), MsgQueues)} + {{value, Pid}, _} when is_pid(Pid) -> + State0; + {{value, Msg}, Queue} -> + Pid ! Msg, + State0#state{dtls_msq_queues = kv_update(Key, {Pid, Queue}, MsgQueues)}; + {empty, Queue0} -> + State0#state{dtls_msq_queues = kv_update(Key, {Pid, queue:in(Pid, Queue0)}, MsgQueues)} end. setup_new_connection(User, From, Client, Msg, #state{dtls_processes = Processes, - clients = Clients, dtls_msq_queues = MsgQueues, dtls_options = DTLSOpts, port = Port, @@ -281,8 +327,7 @@ setup_new_connection(User, From, Client, Msg, #state{dtls_processes = Processes, erlang:monitor(process, Pid), gen_server:reply(From, {ok, Pid, {Client, Socket}}), Pid ! Msg, - State#state{clients = set_insert(Client, Clients), - dtls_msq_queues = kv_insert(Client, queue:new(), MsgQueues), + State#state{dtls_msq_queues = kv_insert(Client, {Pid, queue:new()}, MsgQueues), dtls_processes = kv_insert(Pid, Client, Processes)}; {error, Reason} -> gen_server:reply(From, {error, Reason}), @@ -295,7 +340,7 @@ kv_lookup(Key, Store) -> gb_trees:lookup(Key, Store). kv_insert(Key, Value, Store) -> gb_trees:insert(Key, Value, Store). -kv_get(Key, Store) -> +kv_get(Key, Store) -> gb_trees:get(Key, Store). kv_delete(Key, Store) -> gb_trees:delete(Key, Store). @@ -304,15 +349,6 @@ kv_new() -> kv_empty(Store) -> gb_trees:is_empty(Store). -set_new() -> - gb_sets:empty(). -set_insert(Item, Set) -> - gb_sets:insert(Item, Set). -set_delete(Item, Set) -> - gb_sets:delete(Item, Set). -set_is_member(Item, Set) -> - gb_sets:is_member(Item, Set). - call(Server, Msg) -> try gen_server:call(Server, Msg, infinity) diff --git a/lib/ssl/src/dtls_record.erl b/lib/ssl/src/dtls_record.erl index 6efc2dc8be..dadb16d250 100644 --- a/lib/ssl/src/dtls_record.erl +++ b/lib/ssl/src/dtls_record.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2013-2021. All Rights Reserved. +%% Copyright Ericsson AB 2013-2023. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -175,9 +175,9 @@ current_connection_state_epoch(#{current_write := #{epoch := Epoch}}, %% and returns it as a list of tls_compressed binaries also returns leftover %% data %%-------------------------------------------------------------------- -get_dtls_records(Data, Vinfo, Buffer, SslOpts) -> +get_dtls_records(Data, Vinfo, Buffer, #{log_level := LogLevel}) -> BinData = list_to_binary([Buffer, Data]), - get_dtls_records_aux(Vinfo, BinData, [], SslOpts). + get_dtls_records_aux(Vinfo, BinData, [], LogLevel). %%==================================================================== %% Encoding DTLS records @@ -423,40 +423,48 @@ initial_connection_state(ConnectionEnd, BeastMitigation) -> max_fragment_length => undefined }. -get_dtls_records_aux({DataTag, StateName, _, Versions} = Vinfo, <<?BYTE(Type),?BYTE(MajVer),?BYTE(MinVer), - ?UINT16(Epoch), ?UINT48(SequenceNumber), - ?UINT16(Length), Data:Length/binary, Rest/binary>> = RawDTLSRecord, - Acc, #{log_level := LogLevel} = SslOpts) +get_dtls_records_aux({DataTag, StateName, _, Versions} = Vinfo, + <<?BYTE(Type),?BYTE(MajVer),?BYTE(MinVer), + ?UINT16(Epoch), ?UINT48(SequenceNumber), + ?UINT16(Length), Data:Length/binary, Rest/binary>> = RawDTLSRecord, + Acc0, LogLevel) when ((StateName == hello) orelse ((StateName == certify) andalso (DataTag == udp)) - orelse ((StateName == abbreviated) andalso (DataTag == udp))) andalso ((Type == ?HANDSHAKE) - orelse - (Type == ?ALERT)) -> + orelse ((StateName == abbreviated) andalso (DataTag == udp))) + andalso ((Type == ?HANDSHAKE) orelse (Type == ?ALERT)) -> ssl_logger:debug(LogLevel, inbound, 'record', [RawDTLSRecord]), + Acc = [#ssl_tls{type = Type, version = {MajVer, MinVer}, + epoch = Epoch, sequence_number = SequenceNumber, + fragment = Data} | Acc0], case is_acceptable_version({MajVer, MinVer}, Versions) of true -> - get_dtls_records_aux(Vinfo, Rest, [#ssl_tls{type = Type, - version = {MajVer, MinVer}, - epoch = Epoch, sequence_number = SequenceNumber, - fragment = Data} | Acc], SslOpts); + get_dtls_records_aux(Vinfo, Rest, Acc, LogLevel); false -> - ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC) - end; -get_dtls_records_aux({_, _, Version, _} = Vinfo, <<?BYTE(Type),?BYTE(MajVer),?BYTE(MinVer), - ?UINT16(Epoch), ?UINT48(SequenceNumber), + ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC) + end; +get_dtls_records_aux({_, _, Version, Versions} = Vinfo, + <<?BYTE(Type),?BYTE(MajVer),?BYTE(MinVer), + ?UINT16(Epoch), ?UINT48(SequenceNumber), ?UINT16(Length), Data:Length/binary, Rest/binary>> = RawDTLSRecord, - Acc, #{log_level := LogLevel} = SslOpts) when (Type == ?APPLICATION_DATA) orelse - (Type == ?HANDSHAKE) orelse - (Type == ?ALERT) orelse - (Type == ?CHANGE_CIPHER_SPEC) -> + Acc0, LogLevel) + when (Type == ?APPLICATION_DATA) orelse + (Type == ?HANDSHAKE) orelse + (Type == ?ALERT) orelse + (Type == ?CHANGE_CIPHER_SPEC) -> ssl_logger:debug(LogLevel, inbound, 'record', [RawDTLSRecord]), - case {MajVer, MinVer} of - Version -> - get_dtls_records_aux(Vinfo, Rest, [#ssl_tls{type = Type, - version = {MajVer, MinVer}, - epoch = Epoch, sequence_number = SequenceNumber, - fragment = Data} | Acc], SslOpts); - _ -> + Acc = [#ssl_tls{type = Type, version = {MajVer,MinVer}, + epoch = Epoch, sequence_number = SequenceNumber, + fragment = Data} | Acc0], + if {MajVer, MinVer} =:= Version -> + get_dtls_records_aux(Vinfo, Rest, Acc, LogLevel); + Type == ?HANDSHAKE -> + case is_acceptable_version({MajVer, MinVer}, Versions) of + true -> + get_dtls_records_aux(Vinfo, Rest, Acc, LogLevel); + false -> + ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC) + end; + true -> ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC) end; get_dtls_records_aux(_, <<?BYTE(_), ?BYTE(_MajVer), ?BYTE(_MinVer), diff --git a/lib/ssl/src/inet_tls_dist.erl b/lib/ssl/src/inet_tls_dist.erl index c234bf81be..9118fb59f6 100644 --- a/lib/ssl/src/inet_tls_dist.erl +++ b/lib/ssl/src/inet_tls_dist.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2011-2022. All Rights Reserved. +%% Copyright Ericsson AB 2011-2023. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -232,7 +232,7 @@ gen_accept(Driver, Listen) -> %% smaller than MaxPending accept_loop(DLK, undefined, MaxPending, Pending) when map_size(Pending) < MaxPending -> accept_loop(DLK, spawn_accept(DLK), MaxPending, Pending); -accept_loop(DLK, HandshakePid, MaxPending, Pending) -> +accept_loop({_, _, NetKernelPid} = DLK, HandshakePid, MaxPending, Pending) -> receive {continue, HandshakePid} when is_pid(HandshakePid) -> accept_loop(DLK, undefined, MaxPending, Pending#{HandshakePid => true}); @@ -244,6 +244,9 @@ accept_loop(DLK, HandshakePid, MaxPending, Pending) -> %% HandshakePid crashed before turning into Pending, which means %% error happened in accept. Need to restart the listener. exit(Reason); + {'EXIT', NetKernelPid, Reason} -> + %% Since we're trapping exits, need to manually propagate this signal + exit(Reason); Unexpected -> ?LOG_WARNING("TLS distribution: unexpected message: ~p~n" ,[Unexpected]), accept_loop(DLK, HandshakePid, MaxPending, Pending) @@ -440,7 +443,7 @@ gen_accept_connection( Driver, AcceptPid, DistCtrl, MyNode, Allowed, SetupTime, Kernel) end, - [link, {priority, max}])). + dist_util:net_ticker_spawn_options())). do_accept( _Driver, AcceptPid, DistCtrl, MyNode, Allowed, SetupTime, Kernel) -> @@ -539,7 +542,7 @@ gen_setup(Driver, Node, Type, MyNode, LongOrShortNames, SetupTime) -> Kernel = self(), monitor_pid( spawn_opt(setup_fun(Driver, Kernel, Node, Type, MyNode, LongOrShortNames, SetupTime), - [link, {priority, max}])). + dist_util:net_ticker_spawn_options())). -spec setup_fun(_,_,_,_,_,_,_) -> fun(() -> no_return()). setup_fun(Driver, Kernel, Node, Type, MyNode, LongOrShortNames, SetupTime) -> diff --git a/lib/ssl/src/ssl.app.src b/lib/ssl/src/ssl.app.src index a43a84f26d..b5cb6b5d91 100644 --- a/lib/ssl/src/ssl.app.src +++ b/lib/ssl/src/ssl.app.src @@ -2,7 +2,7 @@ [{description, "Erlang/OTP SSL application"}, {vsn, "%VSN%"}, {modules, [ - %% TLS/SSL + %% TLS/SSL tls_connection, tls_connection_1_3, tls_handshake, @@ -34,7 +34,7 @@ dtls_server_sup, dtls_server_session_cache_sup, %% API - ssl, %% Main API + ssl, %% Main API ssl_session_cache_api, %% Both TLS/SSL and DTLS tls_dtls_connection, @@ -46,7 +46,7 @@ ssl_cipher_format, ssl_srp_primes, ssl_alert, - ssl_listen_tracker_sup, %% may be used by DTLS over SCTP + ssl_listen_tracker_sup, %% may be used by DTLS over SCTP tls_bloom_filter, tls_client_ticket_store, %% Erlang Distribution over SSL/TLS @@ -55,8 +55,8 @@ ssl_dist_sup, ssl_dist_connection_sup, ssl_dist_admin_sup, - tls_dist_sup, - tls_dist_server_sup, + tls_dist_sup, + tls_dist_server_sup, %% SSL/TLS session and cert handling ssl_session, ssl_client_session_cache_db, @@ -70,7 +70,7 @@ ssl_certificate, %% CRL handling ssl_crl, - ssl_crl_cache, + ssl_crl_cache, ssl_crl_cache_api, ssl_crl_hash_dir, %% Logging @@ -85,6 +85,6 @@ {applications, [crypto, public_key, kernel, stdlib]}, {env, []}, {mod, {ssl_app, []}}, - {runtime_dependencies, ["stdlib-3.12","public_key-1.11.3","kernel-8.0", + {runtime_dependencies, ["stdlib-4.1","public_key-1.11.3","kernel-8.4", "erts-10.0","crypto-5.0", "inets-5.10.7", "runtime_tools-1.15.1"]}]}. diff --git a/lib/ssl/src/ssl.erl b/lib/ssl/src/ssl.erl index 2b88568927..ad5028655d 100644 --- a/lib/ssl/src/ssl.erl +++ b/lib/ssl/src/ssl.erl @@ -26,6 +26,7 @@ -module(ssl). -include_lib("public_key/include/public_key.hrl"). +-include_lib("kernel/include/logger.hrl"). -include("ssl_internal.hrl"). -include("ssl_api.hrl"). @@ -310,7 +311,8 @@ {certfile, cert_pem()} | {key, key()} | {keyfile, key_pem()} | - {password, key_password()} | + {password, key_pem_password()} | + {certs_keys, certs_keys()} | {ciphers, cipher_suites()} | {eccs, [named_curve()]} | {signature_algs, signature_algs()} | @@ -334,7 +336,8 @@ {ssl_imp, ssl_imp()} | {session_tickets, session_tickets()} | {key_update_at, key_update_at()} | - {middlebox_comp_mode, middlebox_comp_mode()}. + {receiver_spawn_opts, spawn_opts()} | + {sender_spawn_opts, spawn_opts()}. -type protocol() :: tls | dtls. -type handshake_completion() :: hello | full. @@ -347,8 +350,14 @@ key_id := crypto:key_id(), password => crypto:password()}. % exported -type key_pem() :: file:filename(). --type key_password() :: string() | fun(() -> string()). --type cipher_suites() :: ciphers(). +-type key_pem_password() :: iodata() | fun(() -> iodata()). +-type certs_keys() :: [cert_key_conf()]. +-type cert_key_conf() :: #{cert => cert(), + key => key(), + certfile => cert_pem(), + keyfile => key_pem(), + password => key_pem_password()}. +-type cipher_suites() :: ciphers(). -type ciphers() :: [erl_cipher_suite()] | string(). % (according to old API) exported -type cipher_filters() :: list({key_exchange | cipher | mac | prf, @@ -391,6 +400,7 @@ -type middlebox_comp_mode() :: boolean(). -type client_early_data() :: binary(). -type server_early_data() :: disabled | enabled. +-type spawn_opts() :: [erlang:spawn_opt_option()]. %% ------------------------------------------------------------------------------------------------------- @@ -407,7 +417,8 @@ {max_fragment_length, max_fragment_length()} | {customize_hostname_check, customize_hostname_check()} | {fallback, fallback()} | - {certificate_authorities, certificate_authorities()} | + {middlebox_comp_mode, middlebox_comp_mode()} | + {certificate_authorities, client_certificate_authorities()} | {session_tickets, client_session_tickets()} | {use_ticket, use_ticket()} | {early_data, client_early_data()}. @@ -418,8 +429,8 @@ -type client_verify_type() :: verify_type(). -type client_reuse_session() :: session_id() | {session_id(), SessionData::binary()}. -type client_reuse_sessions() :: boolean() | save. --type certificate_authorities() :: boolean(). --type client_cacerts() :: [public_key:der_encoded()]. +-type client_certificate_authorities() :: boolean(). +-type client_cacerts() :: [public_key:der_encoded()] | [public_key:combined_cert()]. -type client_cafile() :: file:filename(). -type app_level_protocol() :: binary(). -type client_alpn() :: [app_level_protocol()]. @@ -447,6 +458,7 @@ {dhfile, dh_file()} | {verify, server_verify_type()} | {fail_if_no_peer_cert, fail_if_no_peer_cert()} | + {certificate_authorities, server_certificate_authorities()} | {reuse_sessions, server_reuse_sessions()} | {reuse_session, server_reuse_session()} | {alpn_preferred_protocols, server_alpn()} | @@ -462,7 +474,7 @@ {cookie, cookie()} | {early_data, server_early_data()}. --type server_cacerts() :: [public_key:der_encoded()]. +-type server_cacerts() :: [public_key:der_encoded()] | [public_key:combined_cert()]. -type server_cafile() :: file:filename(). -type server_alpn() :: [app_level_protocol()]. -type server_next_protocol() :: [app_level_protocol()]. @@ -479,6 +491,7 @@ -type honor_ecc_order() :: boolean(). -type client_renegotiation() :: boolean(). -type cookie() :: boolean(). +-type server_certificate_authorities() :: boolean(). %% ------------------------------------------------------------------------------------------------------- -type prf_random() :: client_random | server_random. % exported -type protocol_extensions() :: #{renegotiation_info => binary(), @@ -1395,39 +1408,15 @@ clear_pem_cache() -> ssl_pem_cache:clear(). %%--------------------------------------------------------------- --spec format_error({error, Reason}) -> string() when +-spec format_error(Reason | {error, Reason}) -> string() when Reason :: any(). %% %% Description: Creates error string. %%-------------------------------------------------------------------- format_error({error, Reason}) -> - format_error(Reason); -format_error(Reason) when is_list(Reason) -> - Reason; -format_error(closed) -> - "TLS connection is closed"; -format_error({tls_alert, {_, Description}}) -> - Description; -format_error({options,{FileType, File, Reason}}) when FileType == cacertfile; - FileType == certfile; - FileType == keyfile; - FileType == dhfile -> - Error = file_error_format(Reason), - file_desc(FileType) ++ File ++ ": " ++ Error; -format_error({options, {socket_options, Option, Error}}) -> - lists:flatten(io_lib:format("Invalid transport socket option ~p: ~s", [Option, format_error(Error)])); -format_error({options, {socket_options, Option}}) -> - lists:flatten(io_lib:format("Invalid socket option: ~p", [Option])); -format_error({options, Options}) -> - lists:flatten(io_lib:format("Invalid TLS option: ~p", [Options])); - -format_error(Error) -> - case inet:format_error(Error) of - "unknown POSIX" ++ _ -> - unexpected_format(Error); - Other -> - Other - end. + do_format_error(Reason); +format_error(Reason) -> + do_format_error(Reason). tls_version({3, _} = Version) -> Version; @@ -1673,11 +1662,11 @@ handle_option(fallback = Option, Value0, OptionsMap, #{role := Role}) -> assert_role(client_only, Role, Option, Value0), Value = validate_option(Option, Value0), OptionsMap#{Option => Value}; -handle_option(certificate_authorities = Option, unbound, OptionsMap, #{role := Role}) -> - Value = default_option_role(client, false, Role), - OptionsMap#{Option => Value}; -handle_option(certificate_authorities = Option, Value0, #{versions := Versions} = OptionsMap, #{role := Role}) -> - assert_role(client_only, Role, Option, Value0), +handle_option(certificate_authorities = Option, unbound, OptionsMap, #{role := server}) -> + OptionsMap#{Option => true}; +handle_option(certificate_authorities = Option, unbound, OptionsMap, #{role := client}) -> + OptionsMap#{Option => false}; +handle_option(certificate_authorities = Option, Value0, #{versions := Versions} = OptionsMap, _Env) -> assert_option_dependency(Option, versions, Versions, ['tlsv1.3']), Value = validate_option(Option, Value0), OptionsMap#{Option => Value}; @@ -1713,6 +1702,13 @@ handle_option(key_update_at = Option, Value0, #{versions := Versions} = OptionsM assert_option_dependency(Option, versions, Versions, ['tlsv1.3']), Value = validate_option(Option, Value0), OptionsMap#{Option => Value}; +handle_option(log_level = Option, unbound, OptionsMap, _Env) -> + DefaultLevel = case logger:get_module_level(?MODULE) of + [] -> notice; + [{ssl,Level}] -> Level + end, + Value = validate_option(Option, DefaultLevel), + OptionsMap#{Option => Value}; handle_option(next_protocols_advertised = Option, unbound, OptionsMap, #{rules := Rules}) -> Value = validate_option(Option, default_value(Option, Rules)), @@ -1746,6 +1742,11 @@ handle_option(password = Option, unbound, OptionsMap, #{rules := Rules}) -> handle_option(password = Option, Value0, OptionsMap, _Env) -> Value = validate_option(Option, Value0), OptionsMap#{password => Value}; +handle_option(certs_keys, unbound, OptionsMap, _Env) -> + OptionsMap; +handle_option(certs_keys = Option, Value0, OptionsMap, _Env) -> + Value = validate_option(Option, Value0), + OptionsMap#{certs_keys => Value}; handle_option(psk_identity = Option, unbound, OptionsMap, #{rules := Rules}) -> Value = validate_option(Option, default_value(Option, Rules)), OptionsMap#{Option => Value}; @@ -2086,10 +2087,10 @@ validate_option(cacertfile, undefined, _) -> <<>>; validate_option(cacertfile, Value, _) when is_binary(Value) -> - Value; + unambiguous_path(Value); validate_option(cacertfile, Value, _) when is_list(Value), Value =/= ""-> - binary_filename(Value); + binary_filename(unambiguous_path(Value)); validate_option(cacerts, Value, _) when Value == undefined; is_list(Value) -> @@ -2293,11 +2294,13 @@ validate_option(partial_chain, Value, _) when is_function(Value) -> Value; validate_option(password, Value, _) - when is_list(Value) -> + when is_list(Value); is_binary(Value) -> Value; validate_option(password, Value, _) when is_function(Value, 0) -> Value; +validate_option(certs_keys, Value, _) when is_list(Value) -> + Value; validate_option(protocol, Value = tls, _) -> Value; validate_option(protocol, Value = dtls, _) -> @@ -2307,6 +2310,9 @@ validate_option(psk_identity, undefined, _) -> validate_option(psk_identity, Identity, _) when is_list(Identity), Identity =/= "", length(Identity) =< 65535 -> binary_filename(Identity); +validate_option(receiver_spawn_opts, Value, _) + when is_list(Value) -> + Value; validate_option(renegotiate_at, Value, _) when is_integer(Value) -> erlang:min(Value, ?DEFAULT_RENEGOTIATE_AT); validate_option(reuse_session, undefined, _) -> @@ -2329,6 +2335,9 @@ validate_option(reuse_sessions, save = Value, _) -> validate_option(secure_renegotiate, Value, _) when is_boolean(Value) -> Value; +validate_option(sender_spawn_opts, Value, _) + when is_list(Value) -> + Value; validate_option(server_name_indication, Value, _) when is_list(Value) -> %% RFC 6066, Section 3: Currently, the only server names supported are @@ -2465,7 +2474,7 @@ handle_hashsigns_option(_, _Version) -> undefined. handle_signature_algorithms_option(Value, Version) when is_list(Value) - andalso Version >= {3, 4} -> + andalso Version >= {3, 3} -> case tls_v1:signature_schemes(Version, Value) of [] -> throw({error, {options, @@ -2555,7 +2564,7 @@ dtls_validate_versions([Version | Rest], Versions) when Version == 'dtlsv1'; dtls_validate_versions([Ver| _], Versions) -> throw({error, {options, {Ver, {versions, Versions}}}}). -%% The option cacerts overrides cacertsfile +%% The option cacerts overrides cacertfile ca_cert_default(_,_, [_|_]) -> undefined; ca_cert_default(verify_none, _, _) -> @@ -2679,6 +2688,47 @@ handle_supported_groups_option(Value, Version) when is_list(Value) -> end. +-spec do_format_error( string() + | closed + | {tls_alert, {_, Description :: string()}} + | {options, Options :: term()} + | {options, {socket_options, Option :: term()}} + | {options, {socket_options, Option :: term(), Error}} + | {options, {FileType, File :: string(), Error}} + | InetError + | OtherReason) -> string() + when + FileType :: cacertfile | certfile | keyfile | dhfile, + OtherReason :: term(), + InetError :: inet:posix() | system_limit. + +do_format_error(Reason) when is_list(Reason) -> + Reason; +do_format_error(closed) -> + "TLS connection is closed"; +do_format_error({tls_alert, {_, Description}}) -> + Description; +do_format_error({options,{FileType, File, Reason}}) when FileType == cacertfile; + FileType == certfile; + FileType == keyfile; + FileType == dhfile -> + Error = file_error_format(Reason), + file_desc(FileType) ++ File ++ ": " ++ Error; +do_format_error ({options, {socket_options, Option, Error}}) -> + lists:flatten(io_lib:format("Invalid transport socket option ~p: ~s", [Option, do_format_error(Error)])); +do_format_error({options, {socket_options, Option}}) -> + lists:flatten(io_lib:format("Invalid socket option: ~p", [Option])); +do_format_error({options, Options}) -> + lists:flatten(io_lib:format("Invalid TLS option: ~p", [Options])); + +do_format_error(Error) -> + case inet:format_error(Error) of + "unknown POSIX" ++ _ -> + unexpected_format(Error); + Other -> + Other + end. + unexpected_format(Error) -> lists:flatten(io_lib:format("Unexpected error: ~p", [Error])). @@ -2849,8 +2899,25 @@ add_filter(Filter, Filters) -> maybe_client_warn_no_verify(#{verify := verify_none, warn_verify_none := true, log_level := LogLevel}, client) -> - ssl_logger:log(warning, LogLevel, #{description => "Authenticity is not established by certificate path validation", - reason => "Option {verify, verify_peer} and cacertfile/cacerts is missing"}, #{}); + ssl_logger:log(warning, LogLevel, + #{description => "Server authenticity is not verified since certificate path validation is not enabled", + reason => "The option {verify, verify_peer} and one of the options 'cacertfile' or " + "'cacerts' are required to enable this."}, ?LOCATION); maybe_client_warn_no_verify(_,_) -> %% Warning not needed. Note client certificate validation is optional in TLS ok. + +unambiguous_path(Value) -> + AbsName = filename:absname(Value), + case file:read_link(AbsName) of + {ok, PathWithNoLink} -> + case filename:pathtype(PathWithNoLink) of + relative -> + Dirname = filename:dirname(AbsName), + filename:join([Dirname, PathWithNoLink]); + _ -> + PathWithNoLink + end; + _ -> + AbsName + end. diff --git a/lib/ssl/src/ssl_certificate.erl b/lib/ssl/src/ssl_certificate.erl index eef6aa875a..2e2b43f564 100644 --- a/lib/ssl/src/ssl_certificate.erl +++ b/lib/ssl/src/ssl_certificate.erl @@ -80,7 +80,9 @@ public_key_type/1, foldl_db/3, find_cross_sign_root_paths/4, - handle_cert_auths/4 + handle_cert_auths/4, + available_cert_key_pairs/1, + available_cert_key_pairs/2 ]). %%==================================================================== @@ -307,25 +309,60 @@ find_cross_sign_root_paths([_ | Rest] = Path, CertDbHandle, CertDbRef, Invalidat end. handle_cert_auths(Chain, [], _, _) -> - %% If we have no authorities extension to check we just accept - %% first choice + %% If we have no authorities extension (or corresponding + %% 'certificate_authorities' in the certificate request message in + %% TLS-1.2 is empty) to check we just accept first choice. {ok, Chain}; handle_cert_auths([Cert], CertAuths, CertDbHandle, CertDbRef) -> - {ok, {_, [Cert | _] = EChain}, {_, [_ | DCerts]}} = certificate_chain(Cert, CertDbHandle, CertDbRef, [], both), - case cert_auth_member(cert_subjects(DCerts), CertAuths) of - true -> - {ok, EChain}; - false -> - {error, EChain, not_in_auth_domain} + case certificate_chain(Cert, CertDbHandle, CertDbRef, [], both) of + {ok, {_, [Cert | _] = EChain}, {_, [_ | DCerts]}} -> + case cert_auth_member(cert_issuers(DCerts), CertAuths) of + true -> + {ok, EChain}; + false -> + {error, EChain, not_in_auth_domain} + end; + _ -> + {ok, [Cert]} end; handle_cert_auths([_ | Certs] = EChain, CertAuths, _, _) -> - case cert_auth_member(cert_subjects(Certs), CertAuths) of + case cert_auth_member(cert_issuers(Certs), CertAuths) of true -> {ok, EChain}; false -> {error, EChain, not_in_auth_domain} end. +available_cert_key_pairs(CertKeyGroups) -> + %% To be able to find possible TLS session pre TLS-1.3 + %% that may be reused. At this point the version is + %% not negotiated. + RevAlgos = [dsa, rsa, rsa_pss_pss, ecdsa], + cert_key_group_to_list(RevAlgos, CertKeyGroups, []). + +%% Create the prioritized list of cert key pairs that +%% are availble for use in the negotiated version +available_cert_key_pairs(CertKeyGroups, {3, 4}) -> + RevAlgos = [rsa, rsa_pss_pss, ecdsa, eddsa], + cert_key_group_to_list(RevAlgos, CertKeyGroups, []); +available_cert_key_pairs(CertKeyGroups, {3, 3}) -> + RevAlgos = [dsa, rsa, rsa_pss_pss, ecdsa], + cert_key_group_to_list(RevAlgos, CertKeyGroups, []); +available_cert_key_pairs(CertKeyGroups, {3, N}) when N < 3-> + RevAlgos = [dsa, rsa, ecdsa], + cert_key_group_to_list(RevAlgos, CertKeyGroups, []). + +cert_key_group_to_list([], _, Acc) -> + final_group_list(Acc); +cert_key_group_to_list([Algo| Rest], CertKeyGroups, Acc) -> + CertKeyPairs = maps:get(Algo, CertKeyGroups, []), + cert_key_group_to_list(Rest, CertKeyGroups, CertKeyPairs ++ Acc). + +final_group_list([]) -> + [#{certs => [[]], private_key => #{}}]; +final_group_list(List) -> + List. + %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- @@ -411,7 +448,7 @@ find_alternative_root([Cert | _], CertDbHandle, CertDbRef, InvalidatedList) -> end. find_issuer(#cert{der=DerCert, otp=OtpCert}, CertDbHandle, CertsDbRef, ListDb, InvalidatedList) -> - IsIssuerFun = + IsIssuerFun = fun({_Key, #cert{otp=ErlCertCandidate}}, Acc) -> case public_key:pkix_is_issuer(OtpCert, ErlCertCandidate) of true -> @@ -431,12 +468,15 @@ find_issuer(#cert{der=DerCert, otp=OtpCert}, CertDbHandle, CertsDbRef, ListDb, I end, Result = case is_reference(CertsDbRef) of - true -> - do_find_issuer(IsIssuerFun, CertDbHandle, ListDb); - false -> + true when ListDb == [] -> + CertEntryList = ssl_pkix_db:select_certentries_by_ref(CertsDbRef, CertDbHandle), + do_find_issuer(IsIssuerFun, CertDbHandle, CertEntryList); + false when ListDb == [] -> {extracted, CertsData} = CertsDbRef, - DB = [Entry || {decoded, Entry} <- CertsData], - do_find_issuer(IsIssuerFun, CertDbHandle, DB) + CertEntryList = [Entry || {decoded, Entry} <- CertsData], + do_find_issuer(IsIssuerFun, CertDbHandle, CertEntryList); + _ -> + do_find_issuer(IsIssuerFun, CertDbHandle, ListDb) end, case Result of issuer_not_found -> @@ -445,6 +485,7 @@ find_issuer(#cert{der=DerCert, otp=OtpCert}, CertDbHandle, CertsDbRef, ListDb, I Result end. + do_find_issuer(IssuerFun, CertDbHandle, CertDb) -> try foldl_db(IssuerFun, CertDbHandle, CertDb) @@ -667,18 +708,18 @@ maybe_shorten_path(Path, PartialChainHandler, Default) -> DerCerts = [Der || #cert{der=Der} <- Path], try PartialChainHandler(DerCerts) of {trusted_ca, Root} -> - new_trusteded_path(Root, Path, Default); + new_trusted_path(Root, Path, Default); unknown_ca -> Default catch _:_ -> Default end. -new_trusteded_path(DerCert, [#cert{der=DerCert}=Cert | Chain], _) -> - {Cert, Chain}; -new_trusteded_path(DerCert, [_ | Rest], Default) -> - new_trusteded_path(DerCert, Rest, Default); -new_trusteded_path(_, [], Default) -> +new_trusted_path(DerCert, [#cert{der=DerCert}=Cert | Path], _) -> + {Cert, Path}; +new_trusted_path(DerCert, [_ | Rest], Default) -> + new_trusted_path(DerCert, Rest, Default); +new_trusted_path(_, [], Default) -> %% User did not pick a cert present %% in the cert chain so ignore Default. @@ -758,21 +799,29 @@ subject(Cert) -> {_Serial,Subject} = public_key:pkix_subject_id(Cert), Subject. -cert_subjects([], Acc) -> - Acc; -cert_subjects([Cert | Rest], Acc) -> - cert_subjects(Rest, [subject(Cert) | Acc]). - -cert_subjects(OTPCerts) -> - cert_subjects(OTPCerts, []). +issuer(Cert) -> + case public_key:pkix_is_self_signed(Cert) of + true -> + subject(Cert); + false -> + case is_binary(Cert) of + true -> + #'OTPCertificate'{tbsCertificate = TBSCert} = public_key:pkix_decode_cert(Cert, otp), + public_key:pkix_normalize_name(TBSCert#'OTPTBSCertificate'.issuer); + false -> + #'OTPCertificate'{tbsCertificate = TBSCert} = Cert, + public_key:pkix_normalize_name(TBSCert#'OTPTBSCertificate'.issuer) + end + end. -decode_cert_auths(<<>>, Acc) -> +cert_issuers([], Acc) -> Acc; -decode_cert_auths(<<?UINT16(Len), Auth:Len/binary, Rest/binary>>, Acc) -> - NormAut = public_key:pkix_normalize_name(Auth), - decode_cert_auths(Rest, [NormAut | Acc]). +cert_issuers([Cert | Rest], Acc) -> + cert_issuers(Rest, [issuer(Cert) | Acc]). + +cert_issuers(OTPCerts) -> + cert_issuers(OTPCerts, []). -cert_auth_member(ChainSubjects, EncCertAuths) -> - CertAuths = decode_cert_auths(EncCertAuths, []), +cert_auth_member(ChainSubjects, CertAuths) -> CommonAuthorities = sets:intersection(sets:from_list(ChainSubjects), sets:from_list(CertAuths)), not sets:is_empty(CommonAuthorities). diff --git a/lib/ssl/src/ssl_cipher.erl b/lib/ssl/src/ssl_cipher.erl index f567bd6c53..87dd624306 100644 --- a/lib/ssl/src/ssl_cipher.erl +++ b/lib/ssl/src/ssl_cipher.erl @@ -1,7 +1,7 @@ % %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2022. All Rights Reserved. +%% Copyright Ericsson AB 2007-2023. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -183,10 +183,10 @@ cipher(?AES_CBC, CipherState, Mac, Fragment, Version) -> end, block_size(aes_128_cbc), CipherState, Mac, Fragment, Version). aead_encrypt(Type, Key, Nonce, Fragment, AdditionalData, TagLen) -> - crypto:crypto_one_time_aead(aead_type(Type,size(Key)), Key, Nonce, Fragment, AdditionalData, TagLen, true). + crypto:crypto_one_time_aead(aead_type(Type,byte_size(Key)), Key, Nonce, Fragment, AdditionalData, TagLen, true). aead_decrypt(Type, Key, Nonce, CipherText, CipherTag, AdditionalData) -> - crypto:crypto_one_time_aead(aead_type(Type,size(Key)), Key, Nonce, CipherText, AdditionalData, CipherTag, false). + crypto:crypto_one_time_aead(aead_type(Type,byte_size(Key)), Key, Nonce, CipherText, AdditionalData, CipherTag, false). aead_type(?AES_GCM, 16) -> aes_128_gcm; @@ -260,12 +260,13 @@ decipher(?RC4, HashSz, CipherState = #cipher_state{state = State}, Fragment, _, #generic_stream_cipher{content = Content, mac = Mac} = GSC, {Content, Mac, CipherState} catch - _:_ -> + _:Reason:ST -> %% This is a DECRYPTION_FAILED but %% "differentiating between bad_record_mac and decryption_failed %% alerts may permit certain attacks against CBC mode as used in %% TLS [CBCATT]. It is preferable to uniformly use the %% bad_record_mac alert to hide the specific type of the error." + ?SSL_LOG(debug, decrypt_error, [{reason,Reason}, {stacktrace, ST}]), ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC, decryption_failed) end; @@ -305,12 +306,13 @@ block_decipher(Fun, #cipher_state{key=Key, iv=IV} = CipherState0, {<<16#F0, Content/binary>>, Mac, CipherState1} end catch - _:_ -> + _:Reason:ST -> %% This is a DECRYPTION_FAILED but %% "differentiating between bad_record_mac and decryption_failed %% alerts may permit certain attacks against CBC mode as used in %% TLS [CBCATT]. It is preferable to uniformly use the %% bad_record_mac alert to hide the specific type of the error." + ?SSL_LOG(debug, decrypt_error, [{reason,Reason}, {stacktrace, ST}]), ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC, decryption_failed) end. @@ -674,8 +676,8 @@ scheme_to_components(eddsa_ed448) -> {none, eddsa, ed448}; scheme_to_components(rsa_pss_pss_sha256) -> {sha256, rsa_pss_pss, undefined}; scheme_to_components(rsa_pss_pss_sha384) -> {sha384, rsa_pss_pss, undefined}; scheme_to_components(rsa_pss_pss_sha512) -> {sha512, rsa_pss_pss, undefined}; -scheme_to_components(rsa_pkcs1_sha1) -> {sha1, rsa_pkcs1, undefined}; -scheme_to_components(ecdsa_sha1) -> {sha1, ecdsa, undefined}; +scheme_to_components(rsa_pkcs1_sha1) -> {sha, rsa_pkcs1, undefined}; +scheme_to_components(ecdsa_sha1) -> {sha, ecdsa, undefined}; %% Handling legacy signature algorithms scheme_to_components({Hash,Sign}) -> {Hash, Sign, undefined}. diff --git a/lib/ssl/src/ssl_config.erl b/lib/ssl/src/ssl_config.erl index 91bd03decf..46578c05a5 100644 --- a/lib/ssl/src/ssl_config.erl +++ b/lib/ssl/src/ssl_config.erl @@ -41,20 +41,162 @@ %% Internal application API %%==================================================================== init(#{erl_dist := ErlDist, - key := Key, - keyfile := KeyFile, - password := Password, %% Can be fun() or string() dh := DH, dhfile := DHFile} = SslOpts, Role) -> init_manager_name(ErlDist), + #{pem_cache := PemCache} = Config = init_cacerts(SslOpts, Role), + DHParams = init_diffie_hellman(PemCache, DH, DHFile, Role), + + CertKeyAlts = init_certs_keys(SslOpts, Role, PemCache), + + {ok, Config#{cert_key_alts => CertKeyAlts, dh_params => DHParams}}. - {ok, #{pem_cache := PemCache} = Config, Certs} - = init_certificates(SslOpts, Role), +init_certs_keys(#{certs_keys := CertsKeys}, Role, PemCache) -> + Pairs = lists:map(fun(CertKey) -> cert_key_pair(CertKey, Role, PemCache) end, CertsKeys), + CertKeyGroups = group_pairs(Pairs), + prioritize_groups(CertKeyGroups); +init_certs_keys(SslOpts, Role, PemCache) -> + KeyPair = init_cert_key_pair(SslOpts, Role, PemCache), + group_pairs([KeyPair]). + +init_cert_key_pair(#{key := Key, + keyfile := KeyFile, + password := Password} = Opts, Role, PemCache) -> + {ok, Certs} = init_certificates(Opts, PemCache, Role), PrivateKey = init_private_key(PemCache, Key, KeyFile, Password, Role), - DHParams = init_diffie_hellman(PemCache, DH, DHFile, Role), - {ok, Config#{cert_key_pairs => [#{private_key => PrivateKey, certs => Certs}], dh_params => DHParams}}. + #{private_key => PrivateKey, certs => Certs}. + +cert_key_pair(CertKey, Role, PemCache) -> + CertKeyPairConf = cert_conf(key_conf(CertKey)), + init_cert_key_pair(CertKeyPairConf, Role, PemCache). + + +group_pairs([#{certs := [[]]}]) -> + #{eddsa => [], + ecdsa => [], + rsa_pss_pss => [], + rsa => [], + dsa => [] + }; +group_pairs(Pairs) -> + group_pairs(Pairs, #{eddsa => [], + ecdsa => [], + rsa_pss_pss => [], + rsa => [], + dsa => [] + }). +group_pairs([], Group) -> + Group; +group_pairs([#{private_key := #'ECPrivateKey'{parameters = {namedCurve, ?'id-Ed25519'}}} = Pair | Rest], #{eddsa := EDDSA} = Group) -> + group_pairs(Rest, Group#{eddsa => [Pair | EDDSA]}); +group_pairs([#{private_key := #'ECPrivateKey'{parameters = {namedCurve, ?'id-Ed448'}}} = Pair | Rest], #{eddsa := EDDSA} = Group) -> + group_pairs(Rest, Group#{eddsa => [Pair | EDDSA]}); +group_pairs([#{private_key := #'ECPrivateKey'{}} = Pair | Rest], #{ecdsa := ECDSA} = Group) -> + group_pairs(Rest, Group#{ecdsa => [Pair | ECDSA]}); +group_pairs([#{private_key := {#'RSAPrivateKey'{}, #'RSASSA-PSS-params'{}}} = Pair | Rest], #{rsa_pss_pss := RSAPSS} = Group) -> + group_pairs(Rest, Group#{rsa_pss_pss => [Pair | RSAPSS]}); +group_pairs([#{private_key := #'RSAPrivateKey'{}} = Pair | Rest], #{rsa := RSA} = Group) -> + group_pairs(Rest, Group#{rsa => [Pair | RSA]}); +group_pairs([#{private_key := #'DSAPrivateKey'{}} = Pair | Rest], #{dsa := DSA} = Group) -> + group_pairs(Rest, Group#{dsa => [Pair | DSA]}); +group_pairs([#{private_key := #{algorithm := dss, engine := _}} = Pair | Rest], Group) -> + Pairs = maps:get(dsa, Group), + group_pairs(Rest, Group#{dsa => [Pair | Pairs]}); +group_pairs([#{private_key := #{algorithm := Alg, engine := _}} = Pair | Rest], Group) -> + Pairs = maps:get(Alg, Group), + group_pairs(Rest, Group#{Alg => [Pair | Pairs]}). + +prioritize_groups(#{eddsa := EDDSA, + ecdsa := ECDSA, + rsa_pss_pss := RSAPSS, + rsa := RSA, + dsa := DSA} = CertKeyGroups) -> + CertKeyGroups#{eddsa => prio_eddsa(EDDSA), + ecdsa => prio_ecdsa(ECDSA), + rsa_pss_pss => prio_rsa_pss(RSAPSS), + rsa => prio_rsa(RSA), + dsa => prio_dsa(DSA)}. + +prio_eddsa(EDDSA) -> + %% Engine not supported yet + using_curve({namedCurve, ?'id-Ed25519'}, EDDSA, []) ++ using_curve({namedCurve, ?'id-Ed448'}, EDDSA, []). + +prio_ecdsa(ECDSA) -> + EnginePairs = [Pair || Pair = #{private_key := #{engine := _}} <- ECDSA], + Curves = tls_v1:ecc_curves(all), + EnginePairs ++ lists:foldr(fun(Curve, AccIn) -> + CurveOid = pubkey_cert_records:namedCurves(Curve), + Pairs = using_curve({namedCurve, CurveOid}, ECDSA -- EnginePairs, []), + Pairs ++ AccIn + end, [], Curves). +using_curve(_, [], Acc) -> + lists:reverse(Acc); +using_curve(Curve, [#{private_key := #'ECPrivateKey'{parameters = Curve}} = Pair | Rest], Acc) -> + using_curve(Curve, Rest, [Pair | Acc]); +using_curve(Curve, [_ | Rest], Acc) -> + using_curve(Curve, Rest, Acc). + +prio_rsa_pss(RSAPSS) -> + Order = fun(#{privat_key := {#'RSAPrivateKey'{modulus = N}, Params1}}, + #{private_key := {#'RSAPrivateKey'{modulus = N}, Params2}}) -> + prio_params_1(Params1, Params2); + (#{private_key := {#'RSAPrivateKey'{modulus = N}, _}}, + #{private_key := {#'RSAPrivateKey'{modulus = M}, _}}) when M > N -> + true; + (#{private_key := #{engine := _}}, _) -> + true; + (_,_) -> + false + end, + lists:sort(Order, RSAPSS). + +prio_params_1(#'RSASSA-PSS-params'{hashAlgorithm = #'HashAlgorithm'{algorithm = Oid1}}, + #'RSASSA-PSS-params'{hashAlgorithm = #'HashAlgorithm'{algorithm = Oid2}}) -> + public_key:pkix_hash_type(Oid1) > public_key:pkix_hash_type(Oid2). + +prio_rsa(RSA) -> + Order = fun(#{key := #'RSAPrivateKey'{modulus = N}}, + #{key := #'RSAPrivateKey'{modulus = M}}) when M > N -> + true; + (#{private_key := #{engine := _}}, _) -> + true; + (_,_) -> + false + end, + lists:sort(Order, RSA). + +prio_dsa(DSA) -> + Order = fun(#{key := #'DSAPrivateKey'{q = N}}, + #{key := #'DSAPrivateKey'{q = M}}) when M > N -> + true; + (#{private_key := #{engine := _}}, _) -> + true; + (_,_) -> + false + end, + lists:sort(Order, DSA). + +key_conf(#{key := _} = Conf) -> + Conf#{certfile => <<>>, + keyfile => <<>>, + password => undefined}; +key_conf(#{keyfile := _} = Conf) -> + case maps:get(password, Conf, undefined) of + undefined -> + Conf#{key => undefined, + password => undefined}; + _ -> + Conf#{key => undefined} + end. + +cert_conf(#{cert := Bin} = Conf) when is_binary(Bin)-> + Conf#{cert => [Bin]}; +cert_conf(#{cert := _} = Conf) -> + Conf#{certfile => <<>>}; +cert_conf(#{certfile := _} = Conf) -> + Conf#{cert => undefined}. pre_1_3_session_opts(Role) -> {Cb, InitArgs} = session_cb_opts(Role), @@ -119,12 +261,10 @@ init_manager_name(true) -> put(ssl_manager, ssl_manager:name(dist)), put(ssl_pem_cache, ssl_pem_cache:name(dist)). -init_certificates(#{cacerts := CaCerts, - cacertfile := CACertFile, - certfile := CertFile, - cert := OwnCerts, - crl_cache := CRLCache - }, Role) -> +init_cacerts(#{cacerts := CaCerts, + cacertfile := CACertFile, + crl_cache := CRLCache + }, Role) -> {ok, Config} = try Certs = case CaCerts of @@ -138,31 +278,36 @@ init_certificates(#{cacerts := CaCerts, _:Reason -> file_error(CACertFile, {cacertfile, Reason}) end, - init_certificates(OwnCerts, Config, CertFile, Role). + Config. -init_certificates(undefined, Config, <<>>, _) -> - {ok, Config, [[]]}; +init_certificates(#{certfile := CertFile, + cert := OwnCerts}, PemCache, Role) -> + init_certificates(OwnCerts, PemCache, CertFile, Role). -init_certificates(undefined, #{pem_cache := PemCache} = Config, CertFile, client) -> +init_certificates(undefined, _, <<>>, _) -> + {ok, [[]]}; +init_certificates(undefined, PemCache, CertFile, client) -> try %% OwnCert | [OwnCert | Chain] OwnCerts = ssl_certificate:file_to_certificats(CertFile, PemCache), - {ok, Config, OwnCerts} + {ok, OwnCerts} catch _Error:_Reason -> - {ok, Config, [[]]} + {ok, [[]]} end; - -init_certificates(undefined, #{pem_cache := PemCache} = Config, CertFile, server) -> +init_certificates(undefined, PemCache, CertFile, server) -> try %% OwnCert | [OwnCert | Chain] OwnCerts = ssl_certificate:file_to_certificats(CertFile, PemCache), - {ok, Config, OwnCerts} + {ok, OwnCerts} catch _:Reason -> file_error(CertFile, {certfile, Reason}) end; -init_certificates(OwnCerts, Config, _, _) -> - {ok, Config, OwnCerts}. +init_certificates(OwnCerts, _, _, _) when is_binary(OwnCerts)-> + {ok, [OwnCerts]}; +init_certificates(OwnCerts, _, _, _) -> + {ok, OwnCerts}. + init_private_key(_, #{algorithm := Alg} = Key, _, _Password, _Client) when Alg == ecdsa; Alg == rsa; Alg == dss -> diff --git a/lib/ssl/src/ssl_connection.hrl b/lib/ssl/src/ssl_connection.hrl index 86ee57d7fa..c8295f339f 100644 --- a/lib/ssl/src/ssl_connection.hrl +++ b/lib/ssl/src/ssl_connection.hrl @@ -55,7 +55,7 @@ -record(handshake_env, { - client_hello_version :: ssl_record:ssl_version() | 'undefined', + client_hello_version :: ssl_record:ssl_version() | 'undefined', %% Legacy client hello unprocessed_handshake_events = 0 :: integer(), tls_handshake_history :: ssl_handshake:ssl_handshake_history() | secret_printout() | 'undefined', @@ -67,7 +67,9 @@ early_data_accepted = false :: boolean(), %% TLS 1.3 allow_renegotiate = true ::boolean(), %% Ext handling - hello, %%:: #client_hello{} | #server_hello{} + %% continue_status reflects handling of the option handshake that is either full or + %% hello (will pause at hello message to allow user to act on hello extensions) + continue_status, %% full | pause | {pause, ClientVersionsExt} | continue sni_hostname = undefined, max_frag_enum :: undefined | {max_frag_enum, integer()}, expecting_next_protocol_negotiation = false ::boolean(), @@ -97,9 +99,12 @@ socket_tls_closed = false ::boolean(), negotiated_version :: ssl_record:ssl_version() | 'undefined', erl_dist_handle = undefined :: erlang:dist_handle() | 'undefined', - cert_key_pairs = undefined :: [#{private_key => public_key:private_key(), - certs => [public_key:der_encoded()]}] - | secret_printout() | 'undefined' + cert_key_alts = undefined :: #{eddsa => list(), + ecdsa => list(), + rsa_pss_pss => list(), + rsa => list(), + dsa => list() + } | secret_printout() | 'undefined' }). -record(state, { @@ -118,7 +123,7 @@ %% need to worry about packet loss in TLS. In DTLS we %% need to track DTLS handshake seqnr flight_buffer = [] :: list() | map(), - client_certificate_requested = false :: boolean(), + client_certificate_status = not_requested :: not_requested | requested | empty | needs_verifying | verified, protocol_specific = #{} :: map(), session :: #session{} | secret_printout(), key_share, @@ -150,8 +155,8 @@ %% session_cache_cb - not implemented %% crl_db - not implemented %% client_hello_version - Bleichenbacher mitigation in TLS 1.2 -%% client_certificate_requested - Built into TLS 1.3 state machine -%% key_algorithm - not used +%% client_certificate_status - only uses non_requested| requested +%% key_algorithm - only uses not_requested and requested %% diffie_hellman_params - used in TLS 1.2 ECDH key exchange %% diffie_hellman_keys - used in TLS 1.2 ECDH key exchange %% psk_identity - not used diff --git a/lib/ssl/src/ssl_crl_cache.erl b/lib/ssl/src/ssl_crl_cache.erl index 095e3e8b44..c12d829470 100644 --- a/lib/ssl/src/ssl_crl_cache.erl +++ b/lib/ssl/src/ssl_crl_cache.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2015-2020. All Rights Reserved. +%% Copyright Ericsson AB 2015-2022. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -64,7 +64,7 @@ fresh_crl(#'DistributionPoint'{distributionPoint = {fullName, Names}}, CRL) -> case get_crls(Names, undefined) of not_available -> CRL; - [NewCRL] -> + NewCRL -> NewCRL end. @@ -175,7 +175,7 @@ cache_lookup(URL, {{Cache, _}, _}) -> case ssl_pkix_db:lookup(string:trim(Path, leading, "/"), Cache) of undefined -> []; - CRLs -> + [CRLs] -> CRLs end. diff --git a/lib/ssl/src/ssl_gen_statem.erl b/lib/ssl/src/ssl_gen_statem.erl index caa6c6742d..36768ab6c7 100644 --- a/lib/ssl/src/ssl_gen_statem.erl +++ b/lib/ssl/src/ssl_gen_statem.erl @@ -25,8 +25,6 @@ -module(ssl_gen_statem). --include_lib("kernel/include/logger.hrl"). - -include("ssl_api.hrl"). -include("ssl_internal.hrl"). -include("ssl_connection.hrl"). @@ -102,24 +100,28 @@ %%% Initial Erlang process setup %%-------------------------------------------------------------------- %%-------------------------------------------------------------------- --spec start_link(client| server, pid(), ssl:host(), inet:port_number(), port(), list(), pid(), tuple()) -> +-spec start_link(client| server, pid(), ssl:host(), inet:port_number(), port(), tuple(), pid(), tuple()) -> {ok, pid()} | ignore | {error, reason()}. %% %% Description: Creates a process which calls Module:init/1 to %% choose appropriat gen_statem and initialize. %%-------------------------------------------------------------------- -start_link(Role, Sender, Host, Port, Socket, Options, User, CbInfo) -> - {ok, proc_lib:spawn_link(?MODULE, init, [[Role, Sender, Host, Port, Socket, Options, User, CbInfo]])}. +start_link(Role, Sender, Host, Port, Socket, {#{receiver_spawn_opts := ReceiverOpts}, _, _} = Options, User, CbInfo) -> + Opts = [link | proplists:delete(link, ReceiverOpts)], + Pid = proc_lib:spawn_opt(?MODULE, init, [[Role, Sender, Host, Port, Socket, Options, User, CbInfo]], Opts), + {ok, Pid}. %%-------------------------------------------------------------------- --spec start_link(atom(), ssl:host(), inet:port_number(), port(), list(), pid(), tuple()) -> +-spec start_link(atom(), ssl:host(), inet:port_number(), port(), tuple(), pid(), tuple()) -> {ok, pid()} | ignore | {error, reason()}. %% %% Description: Creates a gen_statem process which calls Module:init/1 to %% initialize. %%-------------------------------------------------------------------- -start_link(Role, Host, Port, Socket, Options, User, CbInfo) -> - {ok, proc_lib:spawn_link(?MODULE, init, [[Role, Host, Port, Socket, Options, User, CbInfo]])}. +start_link(Role, Host, Port, Socket, {#{receiver_spawn_opts := ReceiverOpts}, _, _} = Options, User, CbInfo) -> + Opts = [link | proplists:delete(link, ReceiverOpts)], + Pid = proc_lib:spawn_opt(?MODULE, init, [[Role, Host, Port, Socket, Options, User, CbInfo]], Opts), + {ok, Pid}. %%-------------------------------------------------------------------- @@ -149,6 +151,7 @@ init([_Role, _Host, _Port, _Socket, {TLSOpts, _, _}, _User, _CbInfo] = InitArgs -spec ssl_config(ssl_options(), client | server, #state{}) -> #state{}. %%-------------------------------------------------------------------- ssl_config(Opts, Role, #state{static_env = InitStatEnv0, + ssl_options = #{handshake := Handshake}, handshake_env = HsEnv, connection_env = CEnv} = State0) -> {ok, #{cert_db_ref := Ref, @@ -156,12 +159,21 @@ ssl_config(Opts, Role, #state{static_env = InitStatEnv0, fileref_db_handle := FileRefHandle, session_cache := CacheHandle, crl_db_info := CRLDbHandle, - cert_key_pairs := CertKeyPairs, + cert_key_alts := CertKeyAlts, dh_params := DHParams}} = ssl_config:init(Opts, Role), TimeStamp = erlang:monotonic_time(), Session = State0#state.session, + ContinueStatus = case Handshake of + hello -> + %% Will pause handshake after hello message to + %% enable user to react to hello extensions + pause; + full -> + Handshake + end, + State0#state{session = Session#session{time_stamp = TimeStamp}, static_env = InitStatEnv0#static_env{ file_ref_db = FileRefHandle, @@ -170,8 +182,9 @@ ssl_config(Opts, Role, #state{static_env = InitStatEnv0, crl_db = CRLDbHandle, session_cache = CacheHandle }, - handshake_env = HsEnv#handshake_env{diffie_hellman_params = DHParams}, - connection_env = CEnv#connection_env{cert_key_pairs = CertKeyPairs}, + handshake_env = HsEnv#handshake_env{diffie_hellman_params = DHParams, + continue_status = ContinueStatus}, + connection_env = CEnv#connection_env{cert_key_alts = CertKeyAlts}, ssl_options = Opts}. %%-------------------------------------------------------------------- @@ -709,8 +722,6 @@ handle_common_event(internal, {protocol_record, TLSorDTLSRecord}, StateName, Connection:handle_protocol_record(TLSorDTLSRecord, StateName, State); handle_common_event(timeout, hibernate, _, _) -> {keep_state_and_data, [hibernate]}; -handle_common_event(internal, #change_cipher_spec{type = <<1>>}, StateName, State) -> - handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE), StateName, State); handle_common_event({timeout, handshake}, close, _StateName, #state{start_or_recv_from = StartFrom} = State) -> {stop_and_reply, {shutdown, user_timeout}, @@ -840,10 +851,9 @@ handle_info({ErrorTag, Socket, econnaborted}, StateName, handle_info({ErrorTag, Socket, Reason}, StateName, #state{static_env = #static_env{ role = Role, socket = Socket, - error_tag = ErrorTag}, - ssl_options = #{log_level := Level}} = State) -> - ssl_logger:log(info, Level, #{description => "Socket error", - reason => [{error_tag, ErrorTag}, {description, Reason}]}, ?LOCATION), + error_tag = ErrorTag} + } = State) -> + ?SSL_LOG(info, "Socket error", [{error_tag, ErrorTag}, {description, Reason}]), Alert = ?ALERT_REC(?FATAL, ?CLOSE_NOTIFY, {transport_error, Reason}), handle_normal_shutdown(Alert#alert{role = Role}, StateName, State), {stop, {shutdown,normal}, State}; @@ -870,11 +880,9 @@ handle_info({'EXIT', Socket, Reason}, _StateName, #state{static_env = #static_en {stop,{shutdown, Reason}, State}; handle_info(allow_renegotiate, StateName, #state{handshake_env = HsEnv} = State) -> %% PRE TLS-1.3 {next_state, StateName, State#state{handshake_env = HsEnv#handshake_env{allow_renegotiate = true}}}; -handle_info(Msg, StateName, #state{static_env = #static_env{socket = Socket, error_tag = ErrorTag}, - ssl_options = #{log_level := Level}} = State) -> - ssl_logger:log(notice, Level, #{description => "Unexpected INFO message", - reason => [{message, Msg}, {socket, Socket}, - {error_tag, ErrorTag}]}, ?LOCATION), +handle_info(Msg, StateName, #state{static_env = #static_env{socket = Socket, error_tag = ErrorTag}} = State) -> + ?SSL_LOG(notice, "Unexpected INFO message", + [{message, Msg}, {socket, Socket}, {error_tag, ErrorTag}]), {next_state, StateName, State}. %%==================================================================== @@ -896,9 +904,25 @@ read_application_data(Data, try read_application_dist_data(DHandle, Front, BufferSize, Rear) of Buffer -> {no_record, State#state{user_data_buffer = Buffer}} - catch error:_ -> - {stop,disconnect, - State#state{user_data_buffer = {Front,BufferSize,Rear}}} + catch + error:notsup -> + %% Distribution controller has shut down + %% so we are no longer input handler and therefore + %% erlang:dist_ctrl_put_data/2 raises this exception + {stop, {shutdown, dist_closed}, + %% This buffers known data, but we might have delivered + %% some of it to the VM, which makes buffering all + %% incorrect, as would be wasting all. + %% But we are stopping the server so + %% user_data_buffer is not important at all... + State#state{ + user_data_buffer = {Front,BufferSize,Rear}}}; + error:Reason:Stacktrace -> + %% Unforeseen exception in parsing application data + {stop, + {disconnect,{error,Reason,Stacktrace}}, + State#state{ + user_data_buffer = {Front,BufferSize,Rear}}} end end. passive_receive(#state{user_data_buffer = {Front,BufferSize,Rear}, @@ -1151,7 +1175,7 @@ terminate(Reason, connection, #state{static_env = #static_env{ handle_trusted_certs_db(State), Alert = terminate_alert(Reason), %% Send the termination ALERT if possible - catch (ok = Connection:send_alert_in_connection(Alert, State)), + catch Connection:send_alert_in_connection(Alert, State), Connection:close({timeout, ?DEFAULT_TIMEOUT}, Socket, Transport, ConnectionStates); terminate(Reason, _StateName, #state{static_env = #static_env{transport_cb = Transport, protocol_cb = Connection, @@ -1278,7 +1302,7 @@ handle_sni_hostname(Hostname, fileref_db_handle := FileRefHandle, session_cache := CacheHandle, crl_db_info := CRLDbHandle, - cert_key_pairs := CertKeyPairs, + cert_key_alts := CertKeyAlts, dh_params := DHParams}} = ssl_config:init(NewOptions, Role), State0#state{ @@ -1289,7 +1313,7 @@ handle_sni_hostname(Hostname, crl_db = CRLDbHandle, session_cache = CacheHandle }, - connection_env = CEnv#connection_env{cert_key_pairs = CertKeyPairs}, + connection_env = CEnv#connection_env{cert_key_alts = CertKeyAlts}, ssl_options = NewOptions, handshake_env = HsEnv#handshake_env{sni_hostname = Hostname, diffie_hellman_params = DHParams} @@ -1333,7 +1357,9 @@ filter_for_versions(['tlsv1.1'], OrigSSLOptions) -> maps:without(Opts, OrigSSLOptions); filter_for_versions(['tlsv1.1'| Rest], OrigSSLOptions) -> Opts = ?'TLS-1_3_ONLY_OPTIONS' ++ ?'FROM_TLS-1_2_ONLY_OPTIONS', - maybe_exclude_tlsv1(Rest, maps:without(Opts, OrigSSLOptions)). + maybe_exclude_tlsv1(Rest, maps:without(Opts, OrigSSLOptions)); +filter_for_versions(['tlsv1'], OrigSSLOptions) -> + OrigSSLOptions. maybe_exclude_tlsv1(Versions, Options) -> case lists:member('tlsv1', Versions) of diff --git a/lib/ssl/src/ssl_handshake.erl b/lib/ssl/src/ssl_handshake.erl index ee102077af..56a1ca81b9 100644 --- a/lib/ssl/src/ssl_handshake.erl +++ b/lib/ssl/src/ssl_handshake.erl @@ -358,7 +358,8 @@ certify(#certificate{asn1_certificates = ASN1Certs}, CertDbHandle, CertDbRef, error:{_,{error, {asn1, Asn1Reason}}} -> %% ASN-1 decode of certificate somehow failed ?ALERT_REC(?FATAL, ?CERTIFICATE_UNKNOWN, {failed_to_decode_certificate, Asn1Reason}); - error:OtherReason -> + error:OtherReason:ST -> + ?SSL_LOG(info, internal_error, [{error, OtherReason}, {stacktrace, ST}]), ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, {unexpected_error, OtherReason}) end. %%-------------------------------------------------------------------- @@ -427,7 +428,8 @@ master_secret(Version, #session{master_secret = Mastersecret}, try master_secret(Version, Mastersecret, SecParams, ConnectionStates, Role) catch - exit:_ -> + exit:Reason:ST -> + ?SSL_LOG(info, handshake_error, [{error, Reason}, {stacktrace, ST}]), ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, key_calculation_failure) end; @@ -443,7 +445,8 @@ master_secret(Version, PremasterSecret, ConnectionStates, Role) -> ClientRandom, ServerRandom), SecParams, ConnectionStates, Role) catch - exit:_ -> + exit:Reason:ST -> + ?SSL_LOG(info, handshake_error, [{error, Reason}, {stacktrace, ST}]), ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, master_secret_calculation_failure) end. @@ -561,25 +564,27 @@ encode_handshake(#server_key_params{params_bin = Keys, hashsign = HashSign, encode_handshake(#certificate_request{certificate_types = CertTypes, hashsign_algorithms = #hash_sign_algos{hash_sign_algos = HashSignAlgos}, certificate_authorities = CertAuths}, - {Major, Minor}) when Major == 3, Minor >= 3 -> + {3,3}) -> HashSigns = << <<(ssl_cipher:signature_scheme(SignatureScheme)):16 >> || SignatureScheme <- HashSignAlgos >>, + EncCertAuths = encode_cert_auths(CertAuths), CertTypesLen = byte_size(CertTypes), HashSignsLen = byte_size(HashSigns), - CertAuthsLen = byte_size(CertAuths), + CertAuthsLen = byte_size(EncCertAuths), {?CERTIFICATE_REQUEST, - <<?BYTE(CertTypesLen), CertTypes/binary, - ?UINT16(HashSignsLen), HashSigns/binary, - ?UINT16(CertAuthsLen), CertAuths/binary>> + <<?BYTE(CertTypesLen), CertTypes/binary, + ?UINT16(HashSignsLen), HashSigns/binary, + ?UINT16(CertAuthsLen), EncCertAuths/binary>> }; encode_handshake(#certificate_request{certificate_types = CertTypes, certificate_authorities = CertAuths}, _Version) -> + EncCertAuths = encode_cert_auths(CertAuths), CertTypesLen = byte_size(CertTypes), - CertAuthsLen = byte_size(CertAuths), + CertAuthsLen = byte_size(EncCertAuths), {?CERTIFICATE_REQUEST, <<?BYTE(CertTypesLen), CertTypes/binary, - ?UINT16(CertAuthsLen), CertAuths/binary>> + ?UINT16(CertAuthsLen), EncCertAuths/binary>> }; encode_handshake(#server_hello_done{}, _Version) -> {?SERVER_HELLO_DONE, <<>>}; @@ -764,10 +769,11 @@ encode_extensions([#early_data_indication_nst{indication = MaxSize} | Rest], Acc encode_extensions(Rest, <<?UINT16(?EARLY_DATA_EXT), ?UINT16(4), ?UINT32(MaxSize), Acc/binary>>); encode_extensions([#certificate_authorities{authorities = CertAuths}| Rest], Acc) -> - CertAuthsLen = byte_size(CertAuths), + EncCertAuths = encode_cert_auths(CertAuths), + CertAuthsLen = byte_size(EncCertAuths), Len = CertAuthsLen + 2, encode_extensions(Rest, <<?UINT16(?CERTIFICATE_AUTHORITIES_EXT), ?UINT16(Len), - ?UINT16(CertAuthsLen), CertAuths/binary, Acc/binary>>). + ?UINT16(CertAuthsLen), EncCertAuths/binary, Acc/binary>>). encode_cert_status_req( StatusType, @@ -816,6 +822,16 @@ encode_protocols_advertised_on_server(Protocols) -> #next_protocol_negotiation{ extension_data = lists:foldl(fun encode_protocol/2, <<>>, Protocols)}. +encode_cert_auths(Auths) -> + encode_cert_auths(Auths, []). + +encode_cert_auths([], Acc) -> + list_to_binary(lists:reverse(Acc)); +encode_cert_auths([Auth | Auths], Acc) -> + DNEncodedBin = public_key:pkix_encode('Name', Auth, otp), + DNEncodedLen = byte_size(DNEncodedBin), + encode_cert_auths(Auths, [<<?UINT16(DNEncodedLen), DNEncodedBin/binary>> | Acc]). + %%==================================================================== %% Decode handshake %%==================================================================== @@ -866,16 +882,16 @@ decode_handshake(_Version, ?SERVER_KEY_EXCHANGE, Keys) -> decode_handshake({3, 3} = Version, ?CERTIFICATE_REQUEST, <<?BYTE(CertTypesLen), CertTypes:CertTypesLen/binary, ?UINT16(HashSignsLen), HashSigns:HashSignsLen/binary, - ?UINT16(CertAuthsLen), CertAuths:CertAuthsLen/binary>>) -> + ?UINT16(CertAuthsLen), EncCertAuths:CertAuthsLen/binary>>) -> HashSignAlgos = decode_sign_alg(Version, HashSigns), #certificate_request{certificate_types = CertTypes, hashsign_algorithms = #hash_sign_algos{hash_sign_algos = HashSignAlgos}, - certificate_authorities = CertAuths}; + certificate_authorities = decode_cert_auths(EncCertAuths, [])}; decode_handshake(_Version, ?CERTIFICATE_REQUEST, <<?BYTE(CertTypesLen), CertTypes:CertTypesLen/binary, - ?UINT16(CertAuthsLen), CertAuths:CertAuthsLen/binary>>) -> + ?UINT16(CertAuthsLen), EncCertAuths:CertAuthsLen/binary>>) -> #certificate_request{certificate_types = CertTypes, - certificate_authorities = CertAuths}; + certificate_authorities = decode_cert_auths(EncCertAuths, [])}; decode_handshake(_Version, ?SERVER_HELLO_DONE, <<>>) -> #server_hello_done{}; decode_handshake({Major, Minor}, ?CERTIFICATE_VERIFY,<<HashSign:2/binary, ?UINT16(SignLen), @@ -888,8 +904,8 @@ decode_handshake(_Version, ?CLIENT_KEY_EXCHANGE, PKEPMS) -> #client_key_exchange{exchange_keys = PKEPMS}; decode_handshake(_Version, ?FINISHED, VerifyData) -> #finished{verify_data = VerifyData}; -decode_handshake(_, Message, _) -> - throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, {unknown_or_malformed_handshake, Message})). +decode_handshake(_, MessageType, _) -> + throw(?ALERT_REC(?FATAL, ?DECODE_ERROR, {unknown_or_malformed_handshake, MessageType})). %%-------------------------------------------------------------------- @@ -970,6 +986,7 @@ decode_suites('2_bytes', Dec) -> decode_suites('3_bytes', Dec) -> from_3bytes(Dec). + %%==================================================================== %% Cipher suite handling %%==================================================================== @@ -1045,7 +1062,8 @@ cipher_suites(Suites, true) -> prf({3,_N}, PRFAlgo, Secret, Label, Seed, WantedLength) -> {ok, tls_v1:prf(PRFAlgo, Secret, Label, Seed, WantedLength)}. -select_session(SuggestedSessionId, CipherSuites, HashSigns, Compressions, SessIdTracker, Session0, Version, SslOpts, CertKeyPairs) -> +select_session(SuggestedSessionId, CipherSuites, HashSigns, Compressions, SessIdTracker, Session0, Version, SslOpts, CertKeyAlts) -> + CertKeyPairs = ssl_certificate:available_cert_key_pairs(CertKeyAlts, Version), {SessionId, Resumed} = ssl_session:server_select_session(Version, SessIdTracker, SuggestedSessionId, SslOpts, CertKeyPairs), case Resumed of @@ -1063,7 +1081,7 @@ select_session(SuggestedSessionId, CipherSuites, HashSigns, Compressions, SessId new_session_parameters(SessionId, #session{ecc = ECCCurve0} = Session, CipherSuites, SslOpts, Version, Compressions, HashSigns, CertKeyPairs) -> Compression = select_compression(Compressions), - {Certs, Key, {ECCCurve, CipherSuite}} = select_cert_key_pair_and_params(CipherSuites, CertKeyPairs, HashSigns, + {Certs, Key, {ECCCurve, CipherSuite}} = server_select_cert_key_pair_and_params(CipherSuites, CertKeyPairs, HashSigns, ECCCurve0, SslOpts, Version), Session#session{session_id = SessionId, ecc = ECCCurve, @@ -1074,30 +1092,44 @@ new_session_parameters(SessionId, #session{ecc = ECCCurve0} = Session, CipherSui %% Possibly support part of "trusted_ca_keys" extension that corresponds to TLS-1.3 certificate_authorities?! -select_cert_key_pair_and_params(CipherSuites, [#{private_key := NoKey, certs := [[]] = NoCerts}], HashSigns, ECCCurve0, +server_select_cert_key_pair_and_params(CipherSuites, [#{private_key := NoKey, certs := [[]] = NoCerts}], HashSigns, ECCCurve0, #{ciphers := UserSuites, honor_cipher_order := HonorCipherOrder}, Version) -> %% This can happen if anonymous cipher suites are enabled Suites = available_suites(undefined, UserSuites, Version, HashSigns, ECCCurve0), CipherSuite0 = select_cipher_suite(CipherSuites, Suites, HonorCipherOrder), CurveAndSuite = cert_curve(undefined, ECCCurve0, CipherSuite0), {NoCerts, NoKey, CurveAndSuite}; -select_cert_key_pair_and_params(CipherSuites, [#{private_key := Key, certs := [Cert | _] = Certs}], HashSigns, ECCCurve0, +server_select_cert_key_pair_and_params(CipherSuites, [#{private_key := Key, certs := [Cert | _] = Certs}], HashSigns, ECCCurve0, #{ciphers := UserSuites, honor_cipher_order := HonorCipherOrder}, Version) -> Suites = available_suites(Cert, UserSuites, Version, HashSigns, ECCCurve0), CipherSuite0 = select_cipher_suite(CipherSuites, Suites, HonorCipherOrder), CurveAndSuite = cert_curve(Cert, ECCCurve0, CipherSuite0), {Certs, Key, CurveAndSuite}; -select_cert_key_pair_and_params(CipherSuites, [#{private_key := Key, certs := [Cert | _] = Certs} | Rest], HashSigns, ECCCurve0, +server_select_cert_key_pair_and_params(CipherSuites, [#{private_key := Key, certs := [Cert | _] = Certs} | Rest], HashSigns, ECCCurve0, #{ciphers := UserSuites, honor_cipher_order := HonorCipherOrder} = Opts, Version) -> Suites = available_suites(Cert, UserSuites, Version, HashSigns, ECCCurve0), case select_cipher_suite(CipherSuites, Suites, HonorCipherOrder) of no_suite -> - select_cert_key_pair_and_params(CipherSuites, Rest, HashSigns, ECCCurve0, Opts, Version); + server_select_cert_key_pair_and_params(CipherSuites, Rest, HashSigns, ECCCurve0, Opts, Version); CipherSuite0 -> - CurveAndSuite = cert_curve(Cert, ECCCurve0, CipherSuite0), - {Certs, Key, CurveAndSuite} + case is_acceptable_cert(Cert, HashSigns, ssl:tls_version(Version)) of + true -> + CurveAndSuite = cert_curve(Cert, ECCCurve0, CipherSuite0), + {Certs, Key, CurveAndSuite}; + false -> + server_select_cert_key_pair_and_params(CipherSuites, Rest, HashSigns, ECCCurve0, Opts, Version) + end end. +is_acceptable_cert(Cert, HashSigns, {Major, Minor}) when Major == 3, + Minor >= 3 -> + {SignAlgo0, Param, _, _, _} = get_cert_params(Cert), + SignAlgo = sign_algo(SignAlgo0, Param), + is_acceptable_hash_sign(SignAlgo, HashSigns); +is_acceptable_cert(_,_,_) -> + %% Not negotiable pre TLS-1.2. So if cert is available for version it is acceptable + true. + supported_ecc({Major, Minor}) when ((Major == 3) and (Minor >= 1)) orelse (Major > 3) -> Curves = tls_v1:ecc_curves(Minor), #elliptic_curves{elliptic_curve_list = Curves}; @@ -1105,26 +1137,27 @@ supported_ecc(_) -> #elliptic_curves{elliptic_curve_list = []}. premaster_secret(OtherPublicDhKey, MyPrivateKey, #'DHParameter'{} = Params) -> - try - public_key:compute_key(OtherPublicDhKey, MyPrivateKey, Params) - catch - error:computation_failed -> + try + public_key:compute_key(OtherPublicDhKey, MyPrivateKey, Params) + catch + error:Reason:ST -> + ?SSL_LOG(debug, crypto_error, [{reason, Reason}, {stacktrace, ST}]), throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)) - end; + end; premaster_secret(PublicDhKey, PrivateDhKey, #server_dh_params{dh_p = Prime, dh_g = Base}) -> - try + try crypto:compute_key(dh, PublicDhKey, PrivateDhKey, [Prime, Base]) - catch - error:computation_failed -> + catch + error:Reason:ST -> + ?SSL_LOG(debug, crypto_error, [{reason, Reason}, {stacktrace, ST}]), throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)) end; premaster_secret(#client_srp_public{srp_a = ClientPublicKey}, ServerKey, #srp_user{prime = Prime, verifier = Verifier}) -> - try crypto:compute_key(srp, ClientPublicKey, ServerKey, {host, [Verifier, Prime, '6a']}) of - PremasterSecret -> - PremasterSecret + try crypto:compute_key(srp, ClientPublicKey, ServerKey, {host, [Verifier, Prime, '6a']}) catch - error:_ -> + error:Reason:ST -> + ?SSL_LOG(debug, crypto_error, [{reason, Reason}, {stacktrace, ST}]), throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)) end; premaster_secret(#server_srp_params{srp_n = Prime, srp_g = Generator, srp_s = Salt, srp_b = Public}, @@ -1132,14 +1165,13 @@ premaster_secret(#server_srp_params{srp_n = Prime, srp_g = Generator, srp_s = Sa case ssl_srp_primes:check_srp_params(Generator, Prime) of ok -> DerivedKey = crypto:hash(sha, [Salt, crypto:hash(sha, [Username, <<$:>>, Password])]), - try crypto:compute_key(srp, Public, ClientKeys, {user, [DerivedKey, Prime, Generator, '6a']}) of - PremasterSecret -> - PremasterSecret + try crypto:compute_key(srp, Public, ClientKeys, {user, [DerivedKey, Prime, Generator, '6a']}) catch - error -> + error:Reason:ST -> + ?SSL_LOG(debug, crypto_error, [{reason, Reason}, {stacktrace, ST}]), throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)) end; - _ -> + not_accepted -> throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)) end; premaster_secret(#client_rsa_psk_identity{ @@ -1185,14 +1217,16 @@ premaster_secret(EncSecret, #'RSAPrivateKey'{} = RSAPrivateKey) -> try public_key:decrypt_private(EncSecret, RSAPrivateKey, [{rsa_pad, rsa_pkcs1_padding}]) catch - _:_ -> + _:Reason:ST -> + ?SSL_LOG(debug, decrypt_error, [{reason, Reason}, {stacktrace, ST}]), throw(?ALERT_REC(?FATAL, ?DECRYPT_ERROR)) end; premaster_secret(EncSecret, #{algorithm := rsa} = Engine) -> try crypto:private_decrypt(rsa, EncSecret, maps:remove(algorithm, Engine), [{rsa_pad, rsa_pkcs1_padding}]) catch - _:_ -> + _:Reason:ST -> + ?SSL_LOG(debug, decrypt_error, [{reason, Reason}, {stacktrace, ST}]), throw(?ALERT_REC(?FATAL, ?DECRYPT_ERROR)) end. %%==================================================================== @@ -1923,14 +1957,10 @@ handle_ocsp_extension(false = Stapling, Extensions) -> end. certificate_authorities(CertDbHandle, CertDbRef) -> - Authorities = [ Cert || #cert{otp = Cert} <- certificate_authorities_from_db(CertDbHandle, CertDbRef)], - Enc = fun(#'OTPCertificate'{tbsCertificate=TBSCert}) -> - OTPSubj = TBSCert#'OTPTBSCertificate'.subject, - DNEncodedBin = public_key:pkix_encode('Name', OTPSubj, otp), - DNEncodedLen = byte_size(DNEncodedBin), - <<?UINT16(DNEncodedLen), DNEncodedBin/binary>> - end, - list_to_binary([Enc(Cert) || Cert <- Authorities]). + Auths = fun(#'OTPCertificate'{tbsCertificate = TBSCert}) -> + TBSCert#'OTPTBSCertificate'.subject + end, + [Auths(Cert) || #cert{otp = Cert} <- certificate_authorities_from_db(CertDbHandle, CertDbRef)]. %%-------------------------------------------------------------------- %%% Internal functions @@ -2020,8 +2050,8 @@ validation_fun_and_state(undefined, VerifyState, CertPath, LogLevel) -> apply_user_fun(Fun, OtpCert, VerifyResult0, UserState0, SslState, CertPath, LogLevel) when (VerifyResult0 == valid) or (VerifyResult0 == valid_peer) -> VerifyResult = maybe_check_hostname(OtpCert, VerifyResult0, SslState), - case Fun(OtpCert, VerifyResult, UserState0) of - {Valid, UserState} when (Valid == valid) or (Valid == valid_peer) -> + case apply_fun(Fun, OtpCert, VerifyResult, UserState0, CertPath) of + {Valid, UserState} when (Valid == valid) orelse (Valid == valid_peer) -> case cert_status_check(OtpCert, SslState, VerifyResult, CertPath, LogLevel) of valid -> {Valid, {SslState, UserState}}; @@ -2031,9 +2061,9 @@ apply_user_fun(Fun, OtpCert, VerifyResult0, UserState0, SslState, CertPath, LogL {fail, _} = Fail -> Fail end; -apply_user_fun(Fun, OtpCert, ExtensionOrError, UserState0, SslState, _CertPath, _LogLevel) -> - case Fun(OtpCert, ExtensionOrError, UserState0) of - {Valid, UserState} when (Valid == valid) or (Valid == valid_peer)-> +apply_user_fun(Fun, OtpCert, ExtensionOrError, UserState0, SslState, CertPath, _LogLevel) -> + case apply_fun(Fun, OtpCert, ExtensionOrError, UserState0, CertPath) of + {Valid, UserState} when (Valid == valid) orelse (Valid == valid_peer)-> {Valid, {SslState, UserState}}; {fail, _} = Fail -> Fail; @@ -2041,6 +2071,14 @@ apply_user_fun(Fun, OtpCert, ExtensionOrError, UserState0, SslState, _CertPath, {unknown, {SslState, UserState}} end. +apply_fun(Fun, OtpCert, ExtensionOrError, UserState, CertPath) -> + if is_function(Fun, 4) -> + #cert{der=DerCert} = lists:keyfind(OtpCert, #cert.otp, CertPath), + Fun(OtpCert, DerCert, ExtensionOrError, UserState); + is_function(Fun, 3) -> + Fun(OtpCert, ExtensionOrError, UserState) + end. + maybe_check_hostname(OtpCert, valid_peer, SslState) -> case ssl_certificate:validate(OtpCert, valid_peer, SslState) of {valid, _} -> @@ -2076,11 +2114,10 @@ path_validation_alert(Reason) -> digitally_signed(Version, Msg, HashAlgo, PrivateKey, SignAlgo) -> - try do_digitally_signed(Version, Msg, HashAlgo, PrivateKey, SignAlgo) of - Signature -> - Signature + try do_digitally_signed(Version, Msg, HashAlgo, PrivateKey, SignAlgo) catch - error:badkey-> + error:Reason:ST -> + ?SSL_LOG(info, sign_error, [{error, Reason}, {stacktrace, ST}]), throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, bad_key(PrivateKey))) end. @@ -2263,7 +2300,8 @@ encrypted_premaster_secret(Secret, RSAPublicKey) -> rsa_pkcs1_padding}]), #encrypted_premaster_secret{premaster_secret = PreMasterSecret} catch - _:_-> + _:Reason:ST-> + ?SSL_LOG(debug, encrypt_error, [{reason, Reason}, {stacktrace, ST}]), throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, premaster_encryption_failed)) end. @@ -3060,13 +3098,13 @@ decode_extensions(<<?UINT16(?EARLY_DATA_EXT), ?UINT16(4), ?UINT32(MaxSize), Acc#{early_data => #early_data_indication_nst{indication = MaxSize}}); decode_extensions(<<?UINT16(?CERTIFICATE_AUTHORITIES_EXT), ?UINT16(Len), - CertAuts0:Len/binary, Rest/binary>>, + CertAutsExt:Len/binary, Rest/binary>>, Version, MessageType, Acc) -> CertAutsLen = Len - 2, - <<?UINT16(CertAutsLen), CertAuts/binary>> = CertAuts0, + <<?UINT16(CertAutsLen), EncCertAuts/binary>> = CertAutsExt, decode_extensions(Rest, Version, MessageType, Acc#{certificate_authorities => - #certificate_authorities{authorities = CertAuts}}); + #certificate_authorities{authorities = decode_cert_auths(EncCertAuts, [])}}); %% Ignore data following the ClientHello (i.e., %% extensions) if not understood. decode_extensions(<<?UINT16(_), ?UINT16(Len), _Unknown:Len/binary, Rest/binary>>, Version, MessageType, Acc) -> @@ -3087,18 +3125,10 @@ decode_sign_alg({3,3}, SignSchemeList) -> {true, {Hash, Sign}}; {Hash, rsa_pss_pss = Sign, _} -> {true,{Hash, Sign}}; - {sha1, rsa_pkcs1, _} -> - {true,{sha, rsa}}; {Hash, rsa_pkcs1, _} -> {true,{Hash, rsa}}; - {sha1, ecdsa, _} -> - {true,{sha, ecdsa}}; - {sha512,ecdsa, _} -> - {true,{sha512, ecdsa}}; - {sha384,ecdsa, _} -> - {true,{sha384, ecdsa}}; - {sha256,ecdsa, _}-> - {true,{sha256, ecdsa}}; + {Hash, ecdsa, _} -> + {true,{Hash, ecdsa}}; _ -> false end; @@ -3213,6 +3243,11 @@ decode_psk_binders(<<>>, Acc) -> decode_psk_binders(<<?BYTE(Len), Binder:Len/binary, Rest/binary>>, Acc) -> decode_psk_binders(Rest, [Binder|Acc]). +decode_cert_auths(<<>>, Acc) -> + lists:reverse(Acc); +decode_cert_auths(<<?UINT16(Len), Auth:Len/binary, Rest/binary>>, Acc) -> + decode_cert_auths(Rest, [public_key:pkix_normalize_name(Auth) | Acc]). + %% encode/decode stream of certificate data to/from list of certificate data certs_to_list(ASN1Certs) -> certs_to_list(ASN1Certs, []). @@ -3477,21 +3512,6 @@ is_acceptable_cert_type(Sign, Types) -> is_supported_sign(SignAlgo, _, HashSigns, []) -> ssl_cipher:is_supported_sign(SignAlgo, HashSigns); %% {'SignatureAlgorithm',{1,2,840,113549,1,1,11},'NULL'} -is_supported_sign({Hash, Sign}, 'NULL', _, SignatureSchemes) -> - Fun = fun (Scheme, Acc) -> - {H0, S0, _} = ssl_cipher:scheme_to_components(Scheme), - S1 = case S0 of - rsa_pkcs1 -> rsa; - S -> S - end, - H1 = case H0 of - sha1 -> sha; - H -> H - end, - Acc orelse (Sign =:= S1 andalso - Hash =:= H1) - end, - lists:foldl(Fun, false, SignatureSchemes); %% TODO: Implement validation for the curve used in the signature %% RFC 3279 - 2.2.3 ECDSA Signature Algorithm %% When the ecdsa-with-SHA1 algorithm identifier appears as the @@ -3503,20 +3523,15 @@ is_supported_sign({Hash, Sign}, 'NULL', _, SignatureSchemes) -> %% the certificate of the issuer SHALL apply to the verification of the %% signature. is_supported_sign({Hash, Sign}, _Param, _, SignatureSchemes) -> - Fun = fun (Scheme, Acc) -> - {H0, S0, _} = ssl_cipher:scheme_to_components(Scheme), + Fun = fun (Scheme) -> + {H, S0, _} = ssl_cipher:scheme_to_components(Scheme), S1 = case S0 of rsa_pkcs1 -> rsa; S -> S end, - H1 = case H0 of - sha1 -> sha; - H -> H - end, - Acc orelse (Sign =:= S1 andalso - Hash =:= H1) + (Sign =:= S1) andalso (Hash =:= H) end, - lists:foldl(Fun, false, SignatureSchemes). + lists:any(Fun, SignatureSchemes). %% SupportedSignatureAlgorithms SIGNATURE-ALGORITHM-CLASS ::= { @@ -3843,12 +3858,12 @@ path_validation(TrustedCert, Path, ServerName, Role, CertDbHandle, CertDbRef, CR customize_hostname_check := CustomizeHostnameCheck, crl_check := CrlCheck, log_level := Level, - signature_algs := SignAlgos, - signature_algs_cert := SignAlgosCert, - depth := Depth}, + depth := Depth} = Opts, #{cert_ext := CertExt, ocsp_responder_certs := OcspResponderCerts, ocsp_state := OcspState}) -> + SignAlgos = maps:get(signature_algs, Opts, undefined), + SignAlgosCert = maps:get(signature_algs_cert, Opts, undefined), ValidationFunAndState = validation_fun_and_state(VerifyFun, #{role => Role, certdb => CertDbHandle, diff --git a/lib/ssl/src/ssl_handshake.hrl b/lib/ssl/src/ssl_handshake.hrl index 3c770f4e25..6dd47019f4 100644 --- a/lib/ssl/src/ssl_handshake.hrl +++ b/lib/ssl/src/ssl_handshake.hrl @@ -54,6 +54,7 @@ dh_public_value %% TLS 1.3 DH Public Value from peer }). +-define(EMPTY_ID, <<>>). -define(NUM_OF_SESSION_ID_BYTES, 32). % TSL 1.1 & SSL 3 -define(NUM_OF_PREMASTERSECRET_BYTES, 48). -define(DEFAULT_DIFFIE_HELLMAN_GENERATOR, ssl_dh_groups:modp2048_generator()). diff --git a/lib/ssl/src/ssl_internal.hrl b/lib/ssl/src/ssl_internal.hrl index a611f237d0..cdb3154cb6 100644 --- a/lib/ssl/src/ssl_internal.hrl +++ b/lib/ssl/src/ssl_internal.hrl @@ -23,7 +23,8 @@ -ifndef(ssl_internal). -define(ssl_internal, true). --include_lib("public_key/include/public_key.hrl"). +-include_lib("kernel/include/logger.hrl"). +-include_lib("public_key/include/public_key.hrl"). -define(SECRET_PRINTOUT, "***"). @@ -132,6 +133,7 @@ cacerts]}, cacerts => {undefined, [versions]}, cert => {undefined, [versions]}, + certs_keys => {undefined, [versions]}, certfile => {<<>>, [versions]}, certificate_authorities => {false, [versions]}, ciphers => {[], [versions]}, @@ -154,6 +156,7 @@ hibernate_after => {infinity, [versions]}, honor_cipher_order => {false, [versions]}, honor_ecc_order => {undefined, [versions]}, + keep_secrets => {false, [versions]}, key => {undefined, [versions]}, keyfile => {undefined, [versions, certfile]}, @@ -177,11 +180,12 @@ password => {"", [versions]}, protocol => {tls, []}, psk_identity => {undefined, [versions]}, + receiver_spawn_opts => {[], [versions]}, renegotiate_at => {?DEFAULT_RENEGOTIATE_AT, [versions]}, reuse_session => {undefined, [versions]}, reuse_sessions => {true, [versions]}, secure_renegotiate => {true, [versions]}, - keep_secrets => {false, [versions]}, + sender_spawn_opts => {[], [versions]}, server_name_indication => {undefined, [versions]}, session_tickets => {disabled, [versions]}, signature_algs => {undefined, [versions]}, @@ -217,10 +221,30 @@ versions => {[], [protocol]} }). --define('TLS-1_3_ONLY_OPTIONS', [anti_replay, cookie, early_data, key_update_at, middlebox_comp_mode, session_tickets, supported_groups, use_ticket]). --define('FROM_TLS-1_2_ONLY_OPTIONS', [signature_algs, signature_algs_cert]). --define('PRE_TLS-1_3_ONLY_OPTIONS', [client_renegotiation, secure_renegotiate]). --define('TLS-1_0_ONLY_OPTIONS', [padding_check, beast_mitigation]). +-define('TLS-1_3_ONLY_OPTIONS', [anti_replay, + certificate_authorities, + cookie, + early_data, + key_update_at, + middlebox_comp_mode, + session_tickets, + supported_groups, + use_ticket]). +-define('FROM_TLS-1_2_ONLY_OPTIONS', [signature_algs, + signature_algs_cert]). +-define('PRE_TLS-1_3_ONLY_OPTIONS', [client_renegotiation, + dh_file, + eccs, + fallback, + secure_renegotiate, + psk_identity, + reuse_session, + reuse_sessions, + srp_identity, + user_lookup_fun + ]). +-define('TLS-1_0_ONLY_OPTIONS', [padding_check, + beast_mitigation]). -record(socket_options, { @@ -247,6 +271,24 @@ {stop, any(), any()}. -type ssl_options() :: map(). + +-define(SSL_LOG(Level, Descr, Reason), + fun() -> + case get(log_level) of + undefined -> + %% Use debug here, i.e. log everything and let loggers + %% log_level decide if it should be logged + ssl_logger:log(Level, debug, + #{description => Descr, reason => Reason}, + ?LOCATION); + __LogLevel__ -> + ssl_logger:log(Level, __LogLevel__, + #{description => Descr, reason => Reason}, + ?LOCATION) + end + end()). + + %% Internal ticket data record holding pre-processed ticket data. -record(ticket_data, {key, %% key in client ticket store diff --git a/lib/ssl/src/ssl_logger.erl b/lib/ssl/src/ssl_logger.erl index 9e872587e2..8777233b81 100644 --- a/lib/ssl/src/ssl_logger.erl +++ b/lib/ssl/src/ssl_logger.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1999-2021. All Rights Reserved. +%% Copyright Ericsson AB 1999-2022. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -57,10 +57,10 @@ log(Level, LogLevel, ReportMap, Meta) -> ok end. -debug(Level, Direction, Protocol, Message) +debug(LogLevel, Direction, Protocol, Message) when (Direction =:= inbound orelse Direction =:= outbound) andalso (Protocol =:= 'record' orelse Protocol =:= 'handshake') -> - case logger:compare_levels(Level, debug) of + case logger:compare_levels(LogLevel, debug) of lt -> ?LOG_DEBUG(#{direction => Direction, protocol => Protocol, diff --git a/lib/ssl/src/ssl_manager.erl b/lib/ssl/src/ssl_manager.erl index a4fb07c795..2e96624a2b 100644 --- a/lib/ssl/src/ssl_manager.erl +++ b/lib/ssl/src/ssl_manager.erl @@ -20,6 +20,8 @@ %%---------------------------------------------------------------------- %% Purpose: Manages ssl sessions and trusted certifacates +%% (Note: See the document internal_doc/pem_and_cert_cache.md for additional +%% information) %%---------------------------------------------------------------------- -module(ssl_manager). @@ -274,23 +276,23 @@ init([ManagerName, PemCacheName, Opts]) -> %% Description: Handling call messages %%-------------------------------------------------------------------- handle_call({{connection_init, <<>>, Role, {CRLCb, UserCRLDb}}, _Pid}, _From, - #state{certificate_db = [CertDb, FileRefDb, PemChace | _] = Db} = State) -> + #state{certificate_db = [CertDb, FileRefDb, PemCache | _] = Db} = State) -> Ref = make_ref(), {reply, {ok, #{cert_db_ref => Ref, cert_db_handle => CertDb, fileref_db_handle => FileRefDb, - pem_cache => PemChace, + pem_cache => PemCache, session_cache => session_cache(Role, State), crl_db_info => {CRLCb, crl_db_info(Db, UserCRLDb)}}}, State}; handle_call({{connection_init, Trustedcerts, Role, {CRLCb, UserCRLDb}}, Pid}, _From, - #state{certificate_db = [CertDb, FileRefDb, PemChace | _] = Db} = State) -> + #state{certificate_db = [CertDb, FileRefDb, PemCache | _] = Db} = State) -> case add_trusted_certs(Pid, Trustedcerts, Db) of {ok, Ref} -> {reply, {ok, #{cert_db_ref => Ref, cert_db_handle => CertDb, fileref_db_handle => FileRefDb, - pem_cache => PemChace, + pem_cache => PemCache, session_cache => session_cache(Role, State), crl_db_info => {CRLCb, crl_db_info(Db, UserCRLDb)}}}, State}; {error, _} = Error -> @@ -310,10 +312,12 @@ handle_call({{register_session, Host, Port, Session},_}, _, State0) -> State = client_register_session(Host, Port, Session, State0), {reply, ok, State}; handle_call({refresh_trusted_db, _}, _, #state{certificate_db = Db} = State) -> - ssl_pkix_db:refresh_trusted_certs(Db), + PemCache = get(ssl_pem_cache), + ssl_pkix_db:refresh_trusted_certs(Db, PemCache), {reply, ok, State}; handle_call({{refresh_trusted_db, File}, _}, _, #state{certificate_db = Db} = State) -> - ssl_pkix_db:refresh_trusted_certs(File, Db), + PemCache = get(ssl_pem_cache), + ssl_pkix_db:refresh_trusted_certs(File, Db, PemCache), {reply, ok, State}. %%-------------------------------------------------------------------- @@ -461,7 +465,7 @@ invalidate_session(Cache, CacheCb, Key, _Session, clean_cert_db(Ref, CertDb, RefDb, FileMapDb, File) -> case ssl_pkix_db:ref_count(Ref, RefDb, 0) of - 0 -> + 0 -> ssl_pkix_db:remove(Ref, RefDb), ssl_pkix_db:remove(File, FileMapDb), ssl_pkix_db:remove_trusted_certs(Ref, CertDb); diff --git a/lib/ssl/src/ssl_pem_cache.erl b/lib/ssl/src/ssl_pem_cache.erl index 2c24351714..cc1c04da9a 100644 --- a/lib/ssl/src/ssl_pem_cache.erl +++ b/lib/ssl/src/ssl_pem_cache.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 20016-2021. All Rights Reserved. +%% Copyright Ericsson AB 20016-2022. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -20,6 +20,8 @@ %%---------------------------------------------------------------------- %% Purpose: Manages ssl sessions and trusted certifacates +%% (Note: See the document internal_doc/pem_and_cert_cache.md for additional +%% information) %%---------------------------------------------------------------------- -module(ssl_pem_cache). @@ -50,7 +52,6 @@ }). -define(CLEAR_PEM_CACHE, 120000). --define(DEFAULT_MAX_SESSION_CACHE, 1000). %%==================================================================== %% API diff --git a/lib/ssl/src/ssl_pkix_db.erl b/lib/ssl/src/ssl_pkix_db.erl index dbdae5307c..eac9e2a8b3 100644 --- a/lib/ssl/src/ssl_pkix_db.erl +++ b/lib/ssl/src/ssl_pkix_db.erl @@ -31,11 +31,11 @@ -export([create/1, create_pem_cache/1, add_crls/3, remove_crls/2, remove/1, add_trusted_certs/3, - refresh_trusted_certs/1, refresh_trusted_certs/2, + refresh_trusted_certs/3, extract_trusted_certs/1, remove_trusted_certs/2, insert/3, remove/2, clear/1, db_size/1, - ref_count/3, lookup_trusted_cert/4, foldl/3, select_cert_by_issuer/2, + ref_count/3, lookup_trusted_cert/4, foldl/3, select_certentries_by_ref/2, decode_pem_file/1, lookup/2]). %%==================================================================== @@ -117,7 +117,7 @@ lookup_trusted_cert(_DbHandle, {extracted,Certs}, SerialNumber, Issuer) -> CertSerial =:= SerialNumber, CertIssuer =:= Issuer], undefined catch - Cert -> + throw:Cert -> {ok, Cert} end. @@ -132,12 +132,12 @@ lookup_trusted_cert(_DbHandle, {extracted,Certs}, SerialNumber, Issuer) -> add_trusted_certs(_Pid, {extracted, _} = Certs, _) -> {ok, Certs}; -add_trusted_certs(_Pid, {der, DerList}, [CertDb, _,_ | _]) -> +add_trusted_certs(_Pid, {der, DerList}, [CertDb, _, _ | _]) -> NewRef = make_ref(), add_certs_from_der(DerList, NewRef, CertDb), {ok, NewRef}; -add_trusted_certs(_Pid, File, [ _, {RefDb, FileMapDb} | _] = Db) -> +add_trusted_certs(_Pid, File, [_, {RefDb, FileMapDb} | _] = Db) -> case lookup(File, FileMapDb) of [Ref] -> ref_count(Ref, RefDb, 1), @@ -146,18 +146,18 @@ add_trusted_certs(_Pid, File, [ _, {RefDb, FileMapDb} | _] = Db) -> new_trusted_cert_entry(File, Db) end. -refresh_trusted_certs(File, [CertsDb, {_, FileMapDb} | _]) -> +refresh_trusted_certs(File, [CertsDb, {_, FileMapDb} | _], PemCache) -> case lookup(File, FileMapDb) of [Ref] -> - {ok, Content} = decode_pem_file(File), - remove_trusted_certs(Ref, CertsDb), - add_certs_from_pem(Content, Ref, CertsDb); + Certs = ssl_certificate:file_to_certificats(File, PemCache), + KeyList = select_certentries_by_ref(Ref,CertsDb), + update_certs(Ref, Certs, KeyList, CertsDb); undefined -> ok end. -refresh_trusted_certs([_, {_, FileMapDb} | _] = Db) -> +refresh_trusted_certs([_, {_, FileMapDb} | _] = Db, PemCache) -> Refresh = fun({File, _}, Acc) -> - refresh_trusted_certs(File, Db), + refresh_trusted_certs(File, Db, PemCache), Acc end, foldl(Refresh, refresh, FileMapDb). @@ -243,9 +243,13 @@ lookup(Key, Db) -> foldl(Fun, Acc0, Cache) -> ets:foldl(Fun, Acc0, Cache). - -select_cert_by_issuer(Cache, Issuer) -> - ets:select(Cache, [{{{'_','_', Issuer},{'_', '$1'}},[],['$$']}]). +%%-------------------------------------------------------------------- +-spec select_certentries_by_ref(reference(), db_handle()) -> term(). +%% +%% Description: Select certs entries originating from same source +%%-------------------------------------------------------------------- +select_certentries_by_ref(Ref, Cache) -> + ets:select(Cache, [{{{Ref,'_', '_'}, '_'},[],['$_']}]). %%-------------------------------------------------------------------- -spec ref_count(term(), db_handle(), integer()) -> integer(). @@ -297,40 +301,40 @@ remove_certs(Ref, CertsDb) -> ok. add_certs_from_der(DerList, Ref, CertsDb) -> - Add = fun(Cert) -> add_certs(Cert, Ref, CertsDb) end, + Add = fun(Cert) -> add_cert(Cert, Ref, CertsDb) end, [Add(Cert) || Cert <- DerList], ok. certs_from_der(DerList) -> Ref = make_ref(), [Decoded || Cert <- DerList, - Decoded <- [decode_certs(Ref, Cert)], + Decoded <- [decode_cert(Ref, Cert)], Decoded =/= undefined]. add_certs_from_pem(PemEntries, Ref, CertsDb) -> - Add = fun(Cert) -> add_certs(Cert, Ref, CertsDb) end, + Add = fun(Cert) -> add_cert(Cert, Ref, CertsDb) end, [Add(Cert) || {'Certificate', Cert, not_encrypted} <- PemEntries], ok. -add_certs(Cert, Ref, CertsDb) -> +add_cert(Cert, Ref, CertsDb) -> try - {decoded, {Key, Val}} = decode_certs(Ref, Cert), + {decoded, {Key, Val}} = decode_cert(Ref, Cert), insert(Key, Val, CertsDb) catch error:_ -> ok end. -decode_certs(Ref, #cert{otp=ErlCert} = Cert) -> +decode_cert(Ref, #cert{otp=ErlCert} = Cert) -> TBSCertificate = ErlCert#'OTPCertificate'.tbsCertificate, SerialNumber = TBSCertificate#'OTPTBSCertificate'.serialNumber, Issuer = public_key:pkix_normalize_name( TBSCertificate#'OTPTBSCertificate'.issuer), {decoded, {{Ref, SerialNumber, Issuer}, Cert}}; -decode_certs(Ref, Der) -> +decode_cert(Ref, Der) -> try public_key:pkix_decode_cert(Der, otp) of ErlCert -> - decode_certs(Ref, #cert{der=Der, otp=ErlCert}) + decode_cert(Ref, #cert{der=Der, otp=ErlCert}) catch error:_ -> ?LOG_NOTICE("SSL WARNING: Ignoring a CA cert as " "it could not be correctly decoded.~n"), @@ -352,7 +356,7 @@ new_trusted_cert_entry(File, [CertsDb, RefsDb, _ | _]) -> add_crls([_,_,_, {_, Mapping} | _], ?NO_DIST_POINT, CRLs) -> [add_crls(CRL, Mapping) || CRL <- CRLs]; add_crls([_,_,_, {Cache, Mapping} | _], Path, CRLs) -> - insert(Path, CRLs, Cache), + insert(Path, CRLs, Cache), [add_crls(CRL, Mapping) || CRL <- CRLs]. add_crls(CRL, Mapping) -> @@ -365,7 +369,7 @@ remove_crls([_,_,_, {Cache, Mapping} | _], Path) -> case lookup(Path, Cache) of undefined -> ok; - CRLs -> + [CRLs] -> remove(Path, Cache), [rm_crls(CRL, Mapping) || CRL <- CRLs] end. @@ -378,3 +382,27 @@ crl_issuer(DerCRL) -> TBSCRL = CRL#'CertificateList'.tbsCertList, TBSCRL#'TBSCertList'.issuer. +update_certs(Ref, CertList, KeyList, CertsDb) -> + {Insert, Delete} = insert_delete_lists(Ref, CertList, CertsDb, [], KeyList), + insert_cert_entries(Insert, CertsDb), + remove_cert_entries(Delete, CertsDb). + +insert_delete_lists(_, [], _, Insert, Delete) -> + {Insert, Delete}; +insert_delete_lists(Ref, [Cert | Rest], CertsDb, Insert, Delete) -> + case decode_cert(Ref, Cert) of + {decoded, {Key, Value} = Entry} -> + case lookup(Key, CertsDb) of + [Value] -> %% Entry already exists and is unchanged + insert_delete_lists(Ref, Rest, CertsDb, Insert, lists:keydelete(Key, 1, Delete)); + _ -> + insert_delete_lists(Ref, Rest, CertsDb, [Entry | Insert], lists:keydelete(Key, 1, Delete)) + end; + undefined -> + insert_delete_lists(Ref, Rest, CertsDb, Insert, Delete) + end. + +insert_cert_entries(EntryList, CertsDb) -> + ets:insert(CertsDb, EntryList). +remove_cert_entries(EntryList, CertsDb) -> + lists:foreach(fun({Key, Value}) -> remove(Key, Value, CertsDb) end, EntryList). diff --git a/lib/ssl/src/ssl_record.erl b/lib/ssl/src/ssl_record.erl index c3c31e8767..b7b68edd82 100644 --- a/lib/ssl/src/ssl_record.erl +++ b/lib/ssl/src/ssl_record.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2013-2021. All Rights Reserved. +%% Copyright Ericsson AB 2013-2022. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -447,11 +447,14 @@ decipher_aead(Type, #cipher_state{key = Key} = CipherState, AAD0, CipherFragment case ssl_cipher:aead_decrypt(Type, Key, Nonce, CipherText, CipherTag, AAD) of Content when is_binary(Content) -> Content; - _ -> + Reason -> + ?SSL_LOG(debug, decrypt_error, [{reason,Reason}, + {stacktrace, process_info(self(), current_stacktrace)}]), ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC, decryption_failed) end catch - _:_ -> + _:Reason2:ST -> + ?SSL_LOG(debug, decrypt_error, [{reason,Reason2}, {stacktrace, ST}]), ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC, decryption_failed) end. @@ -480,10 +483,10 @@ empty_connection_state(ConnectionEnd, Version, secure_renegotiation => undefined, client_verify_data => undefined, server_verify_data => undefined, - max_early_data_size => MaxEarlyDataSize, + pending_early_data_size => MaxEarlyDataSize, max_fragment_length => undefined, trial_decryption => false, - early_data_limit => false + early_data_accepted => false }. init_security_parameters(?CLIENT, Version) -> diff --git a/lib/ssl/src/ssl_session.erl b/lib/ssl/src/ssl_session.erl index 2f35c54c64..3999b2fc0e 100644 --- a/lib/ssl/src/ssl_session.erl +++ b/lib/ssl/src/ssl_session.erl @@ -1,8 +1,8 @@ %% %% %CopyrightBegin% -%% +%% %% Copyright Ericsson AB 2007-2022. All Rights Reserved. -%% +%% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. %% You may obtain a copy of the License at @@ -14,7 +14,7 @@ %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %% See the License for the specific language governing permissions and %% limitations under the License. -%% +%% %% %CopyrightEnd% %% @@ -30,9 +30,13 @@ -include("ssl_api.hrl"). %% Internal application API --export([is_new/2, client_select_session/5, server_select_session/5, valid_session/2, legacy_session_id/0]). +-export([is_new/2, + client_select_session/5, + server_select_session/5, + valid_session/2, + legacy_session_id/1]). --type seconds() :: integer(). +-type seconds() :: integer(). %%-------------------------------------------------------------------- -spec legacy_session_id() -> ssl:session_id(). @@ -42,51 +46,71 @@ %% If now lower versions are configured this function can be called %% for a dummy value. %%-------------------------------------------------------------------- -legacy_session_id() -> - crypto:strong_rand_bytes(32). - +legacy_session_id(#{middlebox_comp_mode := true}) -> + legacy_session_id(); +legacy_session_id(_) -> + ?EMPTY_ID. %%-------------------------------------------------------------------- --spec is_new(ssl:session_id(), ssl:session_id()) -> boolean(). -%% -%% Description: Checks if the session id decided by the server is a -%% new or resumed sesion id. +-spec is_new(ssl:session_id() | #session{}, ssl:session_id()) -> boolean(). + %% + %% Description: Checks if the session id decided by the server is a +%% new or resumed sesion id. TLS-1.3 middlebox negotiation +%% requies that client also needs to check "is_resumable" in +%% its current session data when pre TLS-1.3 version is +%% negotiated. %%-------------------------------------------------------------------- -is_new(<<>>, _) -> +is_new(?EMPTY_ID, _) -> true; is_new(SessionId, SessionId) -> false; +is_new(#session{session_id = ?EMPTY_ID}, _) -> + true; +is_new(#session{session_id = SessionId, + is_resumable = true}, SessionId) -> + false; is_new(_ClientSuggestion, _ServerDecision) -> true. %%-------------------------------------------------------------------- --spec client_select_session({ssl:host(), inet:port_number(), map()}, db_handle(), atom(), - #session{}, list()) -> #session{}. +-spec client_select_session({ssl:host(), inet:port_number(), map()}, + db_handle(), atom(), #session{}, list()) -> #session{}. %% %% Description: Should be called by the client side to get an id %% for the client hello message. %%-------------------------------------------------------------------- client_select_session({_, _, #{versions := Versions, - protocol := Protocol}} = ClientInfo, + protocol := Protocol} = Opts} = ClientInfo, Cache, CacheCb, NewSession, CertKeyPairs) -> - + RecordCb = record_cb(Protocol), - Version = RecordCb:lowest_protocol_version(Versions), - - case Version of - {3, N} when N >= 4 -> - NewSession#session{session_id = legacy_session_id()}; + LVersion = RecordCb:lowest_protocol_version(Versions), + HVersion = RecordCb:highest_protocol_version(Versions), + + case LVersion of + {3, 4} -> + %% Session reuse is not supported, do pure legacy + %% middlebox comp mode negotiation, by providing either + %% empty session id (no middle box) or random id (middle + %% box mode). + NewSession#session{session_id = legacy_session_id(Opts)}; _ -> - do_client_select_session(ClientInfo, Cache, CacheCb, NewSession, CertKeyPairs) - end. + Session = do_client_select_session(ClientInfo, Cache, CacheCb, + NewSession, CertKeyPairs), + %% If TLS-1.3 is highest version and there was no previous + %% session id that could be reused, if TLS-1.3 is not + %% negotiated, possibly use random id for middle box mode + %% negotiation. + maybe_handle_middlebox(HVersion, Session, Opts) + end. %%-------------------------------------------------------------------- --spec server_select_session(ssl_record:ssl_version(), pid(), binary(), map(), +-spec server_select_session(ssl_record:ssl_version(), pid(), ssl:session_id(), map(), list()) -> {binary(), #session{} | undefined}. %% %% Description: Should be called by the server side to get an id %% for the client hello message. %%-------------------------------------------------------------------- -server_select_session(_, SessIdTracker, <<>>, _SslOpts, _CertKeyPairs) -> +server_select_session(_, SessIdTracker, ?EMPTY_ID, _SslOpts, _CertKeyPairs) -> {ssl_server_session_cache:new_session_id(SessIdTracker), undefined}; server_select_session(_, SessIdTracker, SuggestedId, Options, CertKeyPairs) -> case is_resumable(SuggestedId, SessIdTracker, Options, CertKeyPairs) @@ -111,26 +135,28 @@ valid_session(#session{time_stamp = TimeStamp}, LifeTime) -> %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- -do_client_select_session({_, _, #{reuse_session := {SessionId, SessionData}}}, _, _, NewSession, _) when is_binary(SessionId) andalso - is_binary(SessionData) -> +do_client_select_session({_, _, #{reuse_session := {SessionId, SessionData}}}, + _, _, NewSession, _) when is_binary(SessionId) andalso + is_binary(SessionData) -> try binary_to_term(SessionData, [safe]) of Session -> - Session + Session#session{is_resumable = true} catch _:_ -> - NewSession#session{session_id = <<>>} + NewSession#session{session_id = ?EMPTY_ID} end; -do_client_select_session({Host, Port, #{reuse_session := SessionId}}, Cache, CacheCb, NewSession, _) when is_binary(SessionId)-> +do_client_select_session({Host, Port, #{reuse_session := SessionId}}, + Cache, CacheCb, NewSession, _) when is_binary(SessionId)-> case CacheCb:lookup(Cache, {{Host, Port}, SessionId}) of undefined -> - NewSession#session{session_id = <<>>}; + NewSession#session{session_id = ?EMPTY_ID}; #session{} = Session-> Session end; do_client_select_session(ClientInfo, Cache, CacheCb, NewSession, CertKeyPairs) -> case select_session(ClientInfo, Cache, CacheCb, CertKeyPairs) of no_session -> - NewSession#session{session_id = <<>>}; + NewSession#session{session_id = ?EMPTY_ID}; Session -> Session end. @@ -156,7 +182,8 @@ select_session(Sessions, #{ciphers := Ciphers}, CertKeyPairs) -> end, not (resumable(Session#session.is_resumable) andalso lists:member(Session#session.cipher_suite, Ciphers) - andalso (is_owncert(SessionOwnCert, CertKeyPairs) orelse (SessionOwnCert == undefined))) + andalso (is_owncert(SessionOwnCert, CertKeyPairs) + orelse (SessionOwnCert == undefined))) end, case lists:dropwhile(IsNotResumable, Sessions) of [] -> no_session; @@ -165,7 +192,8 @@ select_session(Sessions, #{ciphers := Ciphers}, CertKeyPairs) -> is_resumable(_, _, #{reuse_sessions := false}, _) -> {false, undefined}; -is_resumable(SuggestedSessionId, SessIdTracker, #{reuse_session := ReuseFun} = Options, OwnCertKeyPairs) -> +is_resumable(SuggestedSessionId, SessIdTracker, + #{reuse_session := ReuseFun} = Options, OwnCertKeyPairs) -> case ssl_server_session_cache:reuse_session(SessIdTracker, SuggestedSessionId) of #session{cipher_suite = CipherSuite, own_certificates = [SessionOwnCert | _], @@ -207,3 +235,11 @@ record_cb(tls) -> tls_record; record_cb(dtls) -> dtls_record. + +legacy_session_id() -> + crypto:strong_rand_bytes(32). + +maybe_handle_middlebox({3, 4}, #session{session_id = ?EMPTY_ID} = Session, #{middlebox_comp_mode := true})-> + Session#session{session_id = legacy_session_id()}; +maybe_handle_middlebox(_, Session, _) -> + Session. diff --git a/lib/ssl/src/tls_client_ticket_store.erl b/lib/ssl/src/tls_client_ticket_store.erl index eb10adc9f1..146fd4c8cc 100644 --- a/lib/ssl/src/tls_client_ticket_store.erl +++ b/lib/ssl/src/tls_client_ticket_store.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2020. All Rights Reserved. +%% Copyright Ericsson AB 2007-2022. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -202,8 +202,8 @@ iterate_tickets(Iter0, Pid, Ciphers, Hash, SNI, Lifetime, EarlyDataSize, Acc) -> lock = Lock}, Iter} when Lock =:= undefined orelse Lock =:= Pid -> MaxEarlyData = tls_handshake_1_3:get_max_early_data(Extensions), - Age = erlang:system_time(seconds) - Timestamp, - if Age < Lifetime -> + Age = erlang:monotonic_time(millisecond) - Timestamp, + if Age < Lifetime * 1000 -> case verify_ticket_sni(SNI, TicketSNI) of match -> case lists:member(Cipher, Ciphers) of @@ -274,7 +274,7 @@ get_tickets(#state{db = Db} = State, Pid, [Key|T], Acc) -> ticket = Ticket, extensions = Extensions } = NewSessionTicket, - TicketAge = erlang:system_time(seconds) - Timestamp, + TicketAge = erlang:monotonic_time(millisecond) - Timestamp, ObfuscatedTicketAge = obfuscate_ticket_age(TicketAge, AgeAdd), Identity = #psk_identity{ identity = Ticket, @@ -329,8 +329,8 @@ collect_invalid_tickets(Iter0, Lifetime, Acc) -> case gb_trees:next(Iter0) of {Key, #data{timestamp = Timestamp, lock = undefined}, Iter} -> - Age = erlang:system_time(seconds) - Timestamp, - if Age < Lifetime -> + Age = erlang:monotonic_time(millisecond) - Timestamp, + if Age < Lifetime * 1000 -> collect_invalid_tickets(Iter, Lifetime, Acc); true -> collect_invalid_tickets(Iter, Lifetime, [Key|Acc]) @@ -343,7 +343,7 @@ collect_invalid_tickets(Iter0, Lifetime, Acc) -> store_ticket(#state{db = Db0, max = Max} = State, Ticket, CipherSuite, SNI, PSK) -> - Timestamp = erlang:system_time(seconds), + Timestamp = erlang:monotonic_time(millisecond), Size = gb_trees:size(Db0), Db1 = if Size =:= Max -> delete_oldest(Db0); diff --git a/lib/ssl/src/tls_connection.erl b/lib/ssl/src/tls_connection.erl index 05cf5bb6c3..6cc9e21cfb 100644 --- a/lib/ssl/src/tls_connection.erl +++ b/lib/ssl/src/tls_connection.erl @@ -34,6 +34,7 @@ %% ClientKeyExchange \ %% CertificateVerify* Flight 3 part 1 %% [ChangeCipherSpec] / +%% NextProtocol* %% Finished --------> / Flight 3 part 2 %% [ChangeCipherSpec] %% <-------- Finished Flight 4 @@ -48,6 +49,7 @@ %% [ChangeCipherSpec] %% <-------- Finished Abbrev Flight 2 part 2 %% [ChangeCipherSpec] +%% NextProtocol* %% Finished --------> Abbrev Flight 3 %% Application Data <-------> Application Data %% @@ -70,13 +72,14 @@ %% | %% New session | Resumed session %% WAIT_OCSP_STAPELING CERTIFY <----------------------------------> ABBRIVIATED -%% +%% WAIT_CERT_VERIFY %% <- Possibly Receive -- | | -%% OCSP Stapel ------> | Flight 3 part 1 | +%% OCSP Stapel/CertVerify -> | Flight 3 part 1 | %% | | %% V | Abbrev Flight 2 part 2 to Abbrev Flight 3 %% CIPHER | %% | | +%% | | %% | Fligth 3 part 2 to Flight 4 | %% | | %% V V @@ -111,7 +114,8 @@ %% Setup -export([init/1]). --export([renegotiate/2]). +-export([renegotiate/2, + choose_tls_fsm/2]). %% gen_statem state functions -export([initial_hello/3, @@ -121,6 +125,7 @@ user_hello/3, wait_ocsp_stapling/3, certify/3, + wait_cert_verify/3, cipher/3, abbreviated/3, connection/3]). @@ -135,17 +140,17 @@ %% Internal application API %%==================================================================== init([Role, Sender, Host, Port, Socket, Options, User, CbInfo]) -> - State0 = #state{protocol_specific = Map} = initial_state(Role, Sender, - Host, Port, Socket, Options, User, CbInfo), - try - State1 = #state{static_env = #static_env{session_cache = Cache, + State0 = initial_state(Role, Sender, Host, Port, Socket, Options, User, CbInfo), + try + State1 = #state{static_env = #static_env{session_cache = Cache, session_cache_cb = CacheCb }, - connection_env = #connection_env{cert_key_pairs = CertKeyPairs}, + connection_env = #connection_env{cert_key_alts = CertKeyAlts}, ssl_options = SslOptions, session = Session0} = ssl_gen_statem:ssl_config(State0#state.ssl_options, Role, State0), State = case Role of client -> + CertKeyPairs = ssl_certificate:available_cert_key_pairs(CertKeyAlts), Session = ssl_session:client_select_session({Host, Port, SslOptions}, Cache, CacheCb, Session0, CertKeyPairs), State1#state{session = Session}; server -> @@ -154,6 +159,7 @@ init([Role, Sender, Host, Port, Socket, Options, User, CbInfo]) -> tls_gen_connection:initialize_tls_sender(State), gen_statem:enter_loop(?MODULE, [], initial_hello, State) catch throw:Error -> + #state{protocol_specific = Map} = State0, EState = State0#state{protocol_specific = Map#{error => Error}}, gen_statem:enter_loop(?MODULE, [], config_error, EState) end. @@ -207,31 +213,26 @@ config_error(Type, Event, State) -> #state{}) -> gen_statem:state_function_result(). %%-------------------------------------------------------------------- -hello(internal, #client_hello{extensions = Extensions} = Hello, - #state{ssl_options = #{handshake := hello}, - static_env = #static_env{role = server}, - handshake_env = HsEnv, +hello(internal, #client_hello{extensions = Extensions}, + #state{static_env = #static_env{role = server}, + handshake_env = #handshake_env{continue_status = pause}, start_or_recv_from = From} = State) -> - {next_state, user_hello, State#state{start_or_recv_from = undefined, - handshake_env = HsEnv#handshake_env{hello = Hello}}, - [{reply, From, {ok, Extensions}}]}; -hello(internal, #server_hello{extensions = Extensions} = Hello, - #state{ssl_options = #{handshake := hello}, + {next_state, user_hello, State#state{start_or_recv_from = undefined}, + [{postpone, true}, {reply, From, {ok, Extensions}}]}; +hello(internal, #server_hello{extensions = Extensions}, + #state{handshake_env = #handshake_env{continue_status = pause}, static_env = #static_env{role = client}, - handshake_env = HsEnv, - start_or_recv_from = From} = State) -> + start_or_recv_from = From} = State) -> {next_state, user_hello, - State#state{start_or_recv_from = undefined, - handshake_env = HsEnv#handshake_env{ - hello = Hello}}, [{reply, From, {ok, Extensions}}]}; + State#state{start_or_recv_from = undefined}, [{postpone, true}, {reply, From, {ok, Extensions}}]}; hello(internal, #client_hello{client_version = ClientVersion} = Hello, #state{static_env = #static_env{role = server}, connection_env = CEnv} = State0) -> try #state{ssl_options = SslOpts} = State1 = tls_dtls_connection:handle_sni_extension(State0, Hello), case choose_tls_fsm(SslOpts, Hello) of tls_1_3_fsm -> - %% Continue in TLS 1.3 'start' state - {next_state, start, State1, [{change_callback_module, tls_connection_1_3}, {next_event, internal, Hello}]}; + {next_state, start, State1, + [{change_callback_module, tls_connection_1_3}, {next_event, internal, Hello}]}; tls_1_0_to_1_2_fsm -> {ServerHelloExt, Type, State} = handle_client_hello(Hello, State1), {next_state, hello, State, [{next_event, internal, {common_client_hello, Type, ServerHelloExt}}]} @@ -319,6 +320,19 @@ certify(Type, Event, State) -> ssl_gen_statem:handle_own_alert(Alert, ?FUNCTION_NAME, State) end. + +%%-------------------------------------------------------------------- +-spec wait_cert_verify(gen_statem:event_type(), term(), #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +wait_cert_verify(info, Event, State) -> + gen_info(Event, ?FUNCTION_NAME, State); +wait_cert_verify(Type, Event, State) -> + try tls_dtls_connection:gen_handshake(?FUNCTION_NAME, Type, Event, State) + catch throw:#alert{} = Alert -> + ssl_gen_statem:handle_own_alert(Alert, ?FUNCTION_NAME, State) + end. + %%-------------------------------------------------------------------- -spec cipher(gen_statem:event_type(), term(), #state{}) -> gen_statem:state_function_result(). @@ -353,13 +367,14 @@ connection(internal, #hello_request{}, handshake_env = #handshake_env{ renegotiation = {Renegotiation, peer}, ocsp_stapling_state = OcspState}, - connection_env = #connection_env{cert_key_pairs = CertKeyPairs}, + connection_env = #connection_env{cert_key_alts = CertKeyAlts}, session = Session0, ssl_options = SslOpts, protocol_specific = #{sender := Pid}, connection_states = ConnectionStates} = State0) -> try tls_sender:peer_renegotiate(Pid) of {ok, Write} -> + CertKeyPairs = ssl_certificate:available_cert_key_pairs(CertKeyAlts), Session = ssl_session:client_select_session({Host, Port, SslOpts}, Cache, CacheCb, Session0, CertKeyPairs), Hello = tls_handshake:client_hello(Host, Port, ConnectionStates, SslOpts, Session#session.session_id, @@ -371,8 +386,9 @@ connection(internal, #hello_request{}, ConnectionStates#{current_write => Write}, session = Session}), tls_gen_connection:next_event(hello, no_record, State, Actions) - catch - _:_ -> + catch + _:Reason:ST -> + ?SSL_LOG(info, internal_error, [{error, Reason}, {stacktrace, ST}]), {stop, {shutdown, sender_blocked}, State0} end; connection(internal, #hello_request{}, @@ -459,6 +475,7 @@ code_change(_OldVsn, StateName, State, _) -> %%-------------------------------------------------------------------- initial_state(Role, Sender, Host, Port, Socket, {SSLOptions, SocketOptions, Trackers}, User, {CbModule, DataTag, CloseTag, ErrorTag, PassiveTag}) -> + put(log_level, maps:get(log_level, SSLOptions)), #{erl_dist := IsErlDist, %% Use highest supported version for client/server random nonce generation versions := [Version|_], @@ -513,7 +530,7 @@ handle_client_hello(#client_hello{client_version = ClientVersion} = Hello, State renegotiation = {Renegotiation, _}, negotiated_protocol = CurrentProtocol, sni_guided_cert_selection = SNICertSelection} = HsEnv, - connection_env = #connection_env{cert_key_pairs = CertKeyPairs} = CEnv, + connection_env = #connection_env{cert_key_alts = CertKeyAlts} = CEnv, session = Session0, ssl_options = SslOpts} = State, SessionTracker = proplists:get_value(session_id_tracker, Trackers), @@ -522,7 +539,7 @@ handle_client_hello(#client_hello{client_version = ClientVersion} = Hello, State tls_handshake:hello(Hello, SslOpts, {SessionTracker, Session0, - ConnectionStates0, CertKeyPairs, KeyExAlg}, + ConnectionStates0, CertKeyAlts, KeyExAlg}, Renegotiation), Protocol = case Protocol0 of undefined -> CurrentProtocol; @@ -549,7 +566,8 @@ gen_info(Event, connection = StateName, State) -> try tls_gen_connection:handle_info(Event, StateName, State) catch - _:_ -> + _:Reason:ST -> + ?SSL_LOG(info, internal_error, [{error, Reason}, {stacktrace, ST}]), ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?INTERNAL_ERROR, malformed_data), StateName, State) @@ -559,7 +577,8 @@ gen_info(Event, StateName, State) -> try tls_gen_connection:handle_info(Event, StateName, State) catch - _:_ -> + _:Reason:ST -> + ?SSL_LOG(info, handshake_error, [{error, Reason}, {stacktrace, ST}]), ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, malformed_handshake_data), StateName, State) diff --git a/lib/ssl/src/tls_connection_1_3.erl b/lib/ssl/src/tls_connection_1_3.erl index 19c9a05a2f..8041e48eb0 100644 --- a/lib/ssl/src/tls_connection_1_3.erl +++ b/lib/ssl/src/tls_connection_1_3.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2021. All Rights Reserved. +%% Copyright Ericsson AB 2007-2023. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -125,6 +125,8 @@ config_error/3, user_hello/3, start/3, + hello_middlebox_assert/3, + hello_retry_middlebox_assert/3, negotiated/3, wait_cert/3, wait_cv/3, @@ -183,7 +185,7 @@ update_cipher_key(ConnStateName, CS0) -> %% gen_statem callbacks %%-------------------------------------------------------------------- callback_mode() -> - state_functions. + [state_functions, state_enter]. init([Role, Sender, Host, Port, Socket, Options, User, CbInfo]) -> State0 = #state{protocol_specific = Map} = initial_state(Role, Sender, @@ -220,6 +222,8 @@ code_change(_OldVsn, StateName, State, _) -> {start, timeout()} | term(), #state{}) -> gen_statem:state_function_result(). %%-------------------------------------------------------------------- +initial_hello(enter, _, State) -> + {keep_state, State}; initial_hello(Type, Event, State) -> ssl_gen_statem:?FUNCTION_NAME(Type, Event, State). @@ -228,77 +232,126 @@ initial_hello(Type, Event, State) -> {start, timeout()} | term(), #state{}) -> gen_statem:state_function_result(). %%-------------------------------------------------------------------- +config_error(enter, _, State) -> + {keep_state, State}; config_error(Type, Event, State) -> ssl_gen_statem:?FUNCTION_NAME(Type, Event, State). - +user_hello(enter, _, State) -> + {keep_state, State}; user_hello({call, From}, cancel, State) -> gen_statem:reply(From, ok), ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?USER_CANCELED, user_canceled), ?FUNCTION_NAME, State); user_hello({call, From}, {handshake_continue, NewOptions, Timeout}, - #state{static_env = #static_env{role = Role}, - handshake_env = #handshake_env{hello = Hello}, + #state{static_env = #static_env{role = client = Role}, + handshake_env = HSEnv, + ssl_options = Options0} = State0) -> + Options = ssl:handle_options(NewOptions, Role, Options0), + State = ssl_gen_statem:ssl_config(Options, Role, State0), + {next_state, wait_sh, State#state{start_or_recv_from = From, + handshake_env = HSEnv#handshake_env{continue_status = continue}}, + [{{timeout, handshake}, Timeout, close}]}; +user_hello({call, From}, {handshake_continue, NewOptions, Timeout}, + #state{static_env = #static_env{role = server = Role}, + handshake_env = #handshake_env{continue_status = {pause, ClientVersions}} = HSEnv, ssl_options = Options0} = State0) -> - Options = ssl:handle_options(NewOptions, Role, Options0#{handshake => full}), + Options = #{versions := Versions} = ssl:handle_options(NewOptions, Role, Options0), State = ssl_gen_statem:ssl_config(Options, Role, State0), - Next = case Role of - client -> - wait_sh; - server -> - start - end, - {next_state, Next, State#state{start_or_recv_from = From}, - [{next_event, internal, Hello}, {{timeout, handshake}, Timeout, close}]}; + case ssl_handshake:select_supported_version(ClientVersions, Versions) of + {3,4} -> + {next_state, start, State#state{start_or_recv_from = From, + handshake_env = HSEnv#handshake_env{continue_status = continue}}, + [{{timeout, handshake}, Timeout, close}]}; + undefined -> + ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?PROTOCOL_VERSION), ?FUNCTION_NAME, State); + _Else -> + {next_state, hello, State#state{start_or_recv_from = From, + handshake_env = HSEnv#handshake_env{continue_status = continue}}, + [{change_callback_module, tls_connection}, + {{timeout, handshake}, Timeout, close}]} + end; user_hello(info, {'DOWN', _, _, _, _} = Event, State) -> ssl_gen_statem:handle_info(Event, ?FUNCTION_NAME, State); user_hello(_, _, _) -> {keep_state_and_data, [postpone]}. -start(internal, #change_cipher_spec{}, State) -> - tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State); -start(internal, #client_hello{extensions = Extensions} = Hello, - #state{ssl_options = #{handshake := hello}, - start_or_recv_from = From, - handshake_env = HsEnv} = State) -> +start(enter, _, State0) -> + State = handle_middlebox(State0), + {next_state, ?FUNCTION_NAME, State,[]}; +start(internal = Type, #change_cipher_spec{} = Msg, + #state{static_env = #static_env{role = server}, + handshake_env = #handshake_env{tls_handshake_history = Hist}} = State) -> + case ssl_handshake:init_handshake_history() of + Hist -> %% First message must always be client hello + ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State); + _ -> + handle_change_cipher_spec(Type, Msg, ?FUNCTION_NAME, State) + end; +start(internal = Type, #change_cipher_spec{} = Msg, State) -> + handle_change_cipher_spec(Type, Msg, ?FUNCTION_NAME, State); +start(internal, #client_hello{extensions = #{client_hello_versions := + #client_hello_versions{versions = ClientVersions} + }} = Hello, + #state{ssl_options = #{handshake := full}} = State) -> + case tls_record:is_acceptable_version({3,4}, ClientVersions) of + true -> + do_server_start(Hello, State); + false -> + ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?PROTOCOL_VERSION), ?FUNCTION_NAME, State) + end; +start(internal, #client_hello{extensions = #{client_hello_versions := + #client_hello_versions{versions = ClientVersions} + }= Extensions}, + #state{start_or_recv_from = From, + handshake_env = #handshake_env{continue_status = pause} = HSEnv} = State) -> {next_state, user_hello, - State#state{start_or_recv_from = undefined, - handshake_env = HsEnv#handshake_env{ - hello = Hello}}, [{reply, From, {ok, Extensions}}]}; -start(internal, #client_hello{} = Hello, State0) -> - case tls_handshake_1_3:do_start(Hello, State0) of - #alert{} = Alert -> - ssl_gen_statem:handle_own_alert(Alert, start, State0); - {State, start} -> - {next_state, start, State, []}; - {State, negotiated} -> - {next_state, negotiated, State, [{next_event, internal, {start_handshake, undefined}}]}; - {State, negotiated, PSK} -> %% Session Resumption with PSK - {next_state, negotiated, State, [{next_event, internal, {start_handshake, PSK}}]} + State#state{start_or_recv_from = undefined, handshake_env = HSEnv#handshake_env{continue_status = {pause, ClientVersions}}}, + [{postpone, true}, {reply, From, {ok, Extensions}}]}; +start(internal, #client_hello{} = Hello, + #state{handshake_env = #handshake_env{continue_status = continue}} = State) -> + do_server_start(Hello, State); +start(internal, #client_hello{}, State0) -> %% Missing mandantory TLS-1.3 extensions, so it is a previous version hello. + ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?PROTOCOL_VERSION), ?FUNCTION_NAME, State0); +start(internal, #server_hello{extensions = #{server_hello_selected_version := + #server_hello_selected_version{selected_version = Version}}} = ServerHello, + #state{ssl_options = #{handshake := full, + versions := SupportedVersions}} = State) -> + case tls_record:is_acceptable_version(Version, SupportedVersions) of + true -> + do_client_start(ServerHello, State); + false -> + ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?PROTOCOL_VERSION), ?FUNCTION_NAME, State) end; -start(internal, #server_hello{extensions = Extensions} = ServerHello, - #state{ssl_options = #{handshake := hello}, - handshake_env = HsEnv, - start_or_recv_from = From} +start(internal, #server_hello{extensions = #{server_hello_selected_version := + #server_hello_selected_version{selected_version = Version}} + = Extensions}, + #state{ssl_options = #{versions := SupportedVersions}, + start_or_recv_from = From, + handshake_env = #handshake_env{continue_status = pause}} = State) -> - {next_state, user_hello, - State#state{start_or_recv_from = undefined, - handshake_env = HsEnv#handshake_env{ - hello = ServerHello}}, [{reply, From, {ok, Extensions}}]}; -start(internal, #server_hello{} = ServerHello, State0) -> - case tls_handshake_1_3:do_start(ServerHello, State0) of - #alert{} = Alert -> - ssl_gen_statem:handle_own_alert(Alert, start, State0); - {State, NextState} -> - {next_state, NextState, State, []} + case tls_record:is_acceptable_version(Version, SupportedVersions) of + true -> + {next_state, user_hello, + State#state{start_or_recv_from = undefined}, [{postpone, true}, {reply, From, {ok, Extensions}}]}; + false -> + ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?PROTOCOL_VERSION), ?FUNCTION_NAME, State) end; +start(internal, #server_hello{} = ServerHello, + #state{handshake_env = #handshake_env{continue_status = continue}} = State) -> + do_client_start(ServerHello, State); +start(internal, #server_hello{}, State0) -> %% Missing mandantory TLS-1.3 extensions, so it is a previous version hello. + ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?PROTOCOL_VERSION), ?FUNCTION_NAME, State0); start(info, Msg, State) -> tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State); start(Type, Msg, State) -> ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State). -negotiated(internal, #change_cipher_spec{}, State) -> - tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State); +negotiated(enter, _, State0) -> + State = handle_middlebox(State0), + {next_state, ?FUNCTION_NAME, State,[]}; +negotiated(internal = Type, #change_cipher_spec{} = Msg, State) -> + handle_change_cipher_spec(Type, Msg, ?FUNCTION_NAME, State); negotiated(internal, Message, State0) -> case tls_handshake_1_3:do_negotiated(Message, State0) of #alert{} = Alert -> @@ -309,8 +362,11 @@ negotiated(internal, Message, State0) -> negotiated(info, Msg, State) -> tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State). -wait_cert(internal, #change_cipher_spec{}, State0) -> - tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State0); +wait_cert(enter, _, State0) -> + State = handle_middlebox(State0), + {next_state, ?FUNCTION_NAME, State,[]}; +wait_cert(internal = Type, #change_cipher_spec{} = Msg, State) -> + handle_change_cipher_spec(Type, Msg, ?FUNCTION_NAME, State); wait_cert(internal, #certificate_1_3{} = Certificate, State0) -> case tls_handshake_1_3:do_wait_cert(Certificate, State0) of @@ -324,8 +380,11 @@ wait_cert(info, Msg, State) -> wait_cert(Type, Msg, State) -> ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State). -wait_cv(internal, #change_cipher_spec{}, State) -> - tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State); +wait_cv(enter, _, State0) -> + State = handle_middlebox(State0), + {next_state, ?FUNCTION_NAME, State,[]}; +wait_cv(internal = Type, #change_cipher_spec{} = Msg, State) -> + handle_change_cipher_spec(Type, Msg, ?FUNCTION_NAME, State); wait_cv(internal, #certificate_verify_1_3{} = CertificateVerify, State0) -> case tls_handshake_1_3:do_wait_cv(CertificateVerify, State0) of @@ -339,8 +398,11 @@ wait_cv(info, Msg, State) -> wait_cv(Type, Msg, State) -> ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State). -wait_finished(internal, #change_cipher_spec{}, State0) -> - tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State0); +wait_finished(enter, _, State0) -> + State = handle_middlebox(State0), + {next_state, ?FUNCTION_NAME, State,[]}; +wait_finished(internal = Type, #change_cipher_spec{} = Msg, State) -> + handle_change_cipher_spec(Type, Msg, ?FUNCTION_NAME, State); wait_finished(internal, #finished{} = Finished, State0) -> case tls_handshake_1_3:do_wait_finished(Finished, State0) of @@ -356,34 +418,71 @@ wait_finished(info, Msg, State) -> wait_finished(Type, Msg, State) -> ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State). - -wait_sh(internal, #change_cipher_spec{}, State) -> - tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State); -wait_sh(internal, #server_hello{extensions = Extensions} = Hello, #state{ssl_options = #{handshake := hello}, - start_or_recv_from = From, - handshake_env = HsEnv} = State) -> +wait_sh(enter, _, State0) -> + State = handle_middlebox(State0), + {next_state, ?FUNCTION_NAME, State,[]}; +wait_sh(internal = Type, #change_cipher_spec{} = Msg, State)-> + handle_change_cipher_spec(Type, Msg, ?FUNCTION_NAME, State); +wait_sh(internal, #server_hello{extensions = Extensions}, + #state{handshake_env = #handshake_env{continue_status = pause}, + start_or_recv_from = From} = State) -> {next_state, user_hello, - State#state{start_or_recv_from = undefined, - handshake_env = HsEnv#handshake_env{ - hello = Hello}}, [{reply, From, {ok, Extensions}}]}; -wait_sh(internal, #server_hello{} = Hello, State0) -> + State#state{start_or_recv_from = undefined}, [{postpone, true},{reply, From, {ok, Extensions}}]}; +wait_sh(internal, #server_hello{session_id = ?EMPTY_ID} = Hello, #state{session = #session{session_id = ?EMPTY_ID}, + ssl_options = #{middlebox_comp_mode := false}} = State0) -> case tls_handshake_1_3:do_wait_sh(Hello, State0) of - #alert{} = Alert -> + #alert{} = Alert -> ssl_gen_statem:handle_own_alert(Alert, wait_sh, State0); {State1, start, ServerHello} -> %% hello_retry_request: go to start - {next_state, start, State1, [{next_event, internal, ServerHello}]}; + {next_state, start, State1, [{next_event, internal, ServerHello}]}; {State1, wait_ee} -> - tls_gen_connection:next_event(wait_ee, no_record, State1) + tls_gen_connection:next_event(wait_ee, no_record, State1) + end; +wait_sh(internal, #server_hello{} = Hello, + #state{protocol_specific = PS, ssl_options = #{middlebox_comp_mode := true}} = State0) -> + IsRetry = maps:get(hello_retry, PS, false), + case tls_handshake_1_3:do_wait_sh(Hello, State0) of + #alert{} = Alert -> + ssl_gen_statem:handle_own_alert(Alert, wait_sh, State0); + {State1 = #state{}, start, ServerHello} -> + %% hello_retry_request: go to start + {next_state, start, State1, [{next_event, internal, ServerHello}]}; + {State1, wait_ee} when IsRetry == true -> + tls_gen_connection:next_event(wait_ee, no_record, State1); + {State1, wait_ee} when IsRetry == false -> + tls_gen_connection:next_event(hello_middlebox_assert, no_record, State1) end; wait_sh(info, Msg, State) -> tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State); wait_sh(Type, Msg, State) -> ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State). +hello_middlebox_assert(enter, _, State) -> + {keep_state, State}; +hello_middlebox_assert(internal, #change_cipher_spec{}, State) -> + tls_gen_connection:next_event(wait_ee, no_record, State); +hello_middlebox_assert(info, Msg, State) -> + tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State); +hello_middlebox_assert(Type, Msg, State) -> + ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State). -wait_ee(internal, #change_cipher_spec{}, State) -> - tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State); +hello_retry_middlebox_assert(enter, _, State) -> + {keep_state, State}; +hello_retry_middlebox_assert(internal, #change_cipher_spec{}, State) -> + tls_gen_connection:next_event(wait_sh, no_record, State); +hello_retry_middlebox_assert(internal, #server_hello{}, State) -> + tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State, [postpone]); +hello_retry_middlebox_assert(info, Msg, State) -> + tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State); +hello_retry_middlebox_assert(Type, Msg, State) -> + ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State). + +wait_ee(enter, _, State0) -> + State = handle_middlebox(State0), + {next_state, ?FUNCTION_NAME, State,[]}; +wait_ee(internal = Type, #change_cipher_spec{} = Msg, State) -> + handle_change_cipher_spec(Type, Msg, ?FUNCTION_NAME, State); wait_ee(internal, #encrypted_extensions{} = EE, State0) -> case tls_handshake_1_3:do_wait_ee(EE, State0) of #alert{} = Alert -> @@ -396,9 +495,12 @@ wait_ee(info, Msg, State) -> wait_ee(Type, Msg, State) -> ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State). - -wait_cert_cr(internal, #change_cipher_spec{}, State) -> - tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State); +wait_cert_cr(enter, _, State0) -> + State = handle_middlebox(State0), + {next_state, ?FUNCTION_NAME, State,[]}; +wait_cert_cr(internal = Type, #change_cipher_spec{} = Msg, + State) -> + handle_change_cipher_spec(Type, Msg, ?FUNCTION_NAME, State); wait_cert_cr(internal, #certificate_1_3{} = Certificate, State0) -> case tls_handshake_1_3:do_wait_cert_cr(Certificate, State0) of {#alert{} = Alert, State} -> @@ -418,8 +520,11 @@ wait_cert_cr(info, Msg, State) -> wait_cert_cr(Type, Msg, State) -> ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State). -wait_eoed(internal, #change_cipher_spec{}, State) -> - tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State); +wait_eoed(enter, _, State0) -> + State = handle_middlebox(State0), + {next_state, ?FUNCTION_NAME, State,[]}; +wait_eoed(internal = Type, #change_cipher_spec{} = Msg, State) -> + handle_change_cipher_spec(Type, Msg, ?FUNCTION_NAME, State); wait_eoed(internal, #end_of_early_data{} = EOED, State0) -> case tls_handshake_1_3:do_wait_eoed(EOED, State0) of {#alert{} = Alert, State} -> @@ -432,6 +537,8 @@ wait_eoed(info, Msg, State) -> wait_eoed(Type, Msg, State) -> ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State). +connection(enter, _, State) -> + {keep_state, State}; connection(internal, #new_session_ticket{} = NewSessionTicket, State) -> handle_new_session_ticket(NewSessionTicket, State), tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State); @@ -455,6 +562,8 @@ connection({call, From}, negotiated_protocol, connection(Type, Event, State) -> ssl_gen_statem:?FUNCTION_NAME(Type, Event, State). +downgrade(enter, _, State) -> + {keep_state, State}; downgrade(internal, #new_session_ticket{} = NewSessionTicket, State) -> _ = handle_new_session_ticket(NewSessionTicket, State), {next_state, ?FUNCTION_NAME, State}; @@ -464,8 +573,30 @@ downgrade(Type, Event, State) -> %-------------------------------------------------------------------- %% internal functions %%-------------------------------------------------------------------- + +do_server_start(ClientHello, State0) -> + case tls_handshake_1_3:do_start(ClientHello, State0) of + #alert{} = Alert -> + ssl_gen_statem:handle_own_alert(Alert, start, State0); + {State, start} -> + {next_state, start, State, []}; + {State, negotiated} -> + {next_state, negotiated, State, [{next_event, internal, {start_handshake, undefined}}]}; + {State, negotiated, PSK} -> %% Session Resumption with PSK + {next_state, negotiated, State, [{next_event, internal, {start_handshake, PSK}}]} + end. + +do_client_start(ServerHello, State0) -> + case tls_handshake_1_3:do_start(ServerHello, State0) of + #alert{} = Alert -> + ssl_gen_statem:handle_own_alert(Alert, start, State0); + {State, NextState} -> + {next_state, NextState, State, []} + end. + initial_state(Role, Sender, Host, Port, Socket, {SSLOptions, SocketOptions, Trackers}, User, {CbModule, DataTag, CloseTag, ErrorTag, PassiveTag}) -> + put(log_level, maps:get(log_level, SSLOptions)), #{erl_dist := IsErlDist, %% Use highest supported version for client/server random nonce generation versions := [Version|_], @@ -500,7 +631,7 @@ initial_state(Role, Sender, Host, Port, Socket, {SSLOptions, SocketOptions, Trac socket_options = SocketOptions, ssl_options = SSLOptions, session = #session{is_resumable = false, - session_id = ssl_session:legacy_session_id()}, + session_id = ssl_session:legacy_session_id(SSLOptions)}, connection_states = ConnectionStates, protocol_buffers = #protocol_buffers{}, user_data_buffer = {[],0,[]}, @@ -560,7 +691,7 @@ handle_new_session_ticket(#new_session_ticket{ticket_nonce = Nonce} = NewSession tls_client_ticket_store:store_ticket(NewSessionTicket, {Cipher, HKDF}, SNI, PSK). send_ticket_data(User, NewSessionTicket, CipherSuite, SNI, PSK) -> - Timestamp = erlang:system_time(seconds), + Timestamp = erlang:system_time(millisecond), TicketData = #{cipher_suite => CipherSuite, sni => SNI, psk => PSK, @@ -591,3 +722,19 @@ init_max_early_data_size(client) -> init_max_early_data_size(server) -> ssl_config:get_max_early_data_size(). +handle_middlebox(#state{protocol_specific = PS} = State0) -> + %% Always be prepared to ignore one change cipher spec + %% for maximum interopablility, even if middlebox mode + %% is not enabled. + State0#state{protocol_specific = PS#{change_cipher_spec => ignore}}. + + +handle_change_cipher_spec(Type, Msg, StateName, #state{protocol_specific = PS0} = State) -> + case maps:get(change_cipher_spec, PS0) of + ignore -> + PS = PS0#{change_cipher_spec => fail}, + tls_gen_connection:next_event(StateName, no_record, + State#state{protocol_specific = PS}); + fail -> + ssl_gen_statem:handle_common_event(Type, Msg, StateName, State) + end. diff --git a/lib/ssl/src/tls_dtls_connection.erl b/lib/ssl/src/tls_dtls_connection.erl index 9f0bf8132e..6882e8af34 100644 --- a/lib/ssl/src/tls_dtls_connection.erl +++ b/lib/ssl/src/tls_dtls_connection.erl @@ -56,6 +56,7 @@ user_hello/3, abbreviated/3, certify/3, + wait_cert_verify/3, wait_ocsp_stapling/3, cipher/3, connection/3, @@ -107,7 +108,7 @@ prf(ConnectionPid, Secret, Label, Seed, WantedLength) -> handle_session(#server_hello{cipher_suite = CipherSuite, compression_method = Compression}, Version, NewId, ConnectionStates, ProtoExt, Protocol0, - #state{session = #session{session_id = OldId}, + #state{session = Session, handshake_env = #handshake_env{negotiated_protocol = CurrentProtocol} = HsEnv, connection_env = #connection_env{negotiated_version = ReqVersion} = CEnv} = State0) -> #{key_exchange := KeyAlgorithm} = @@ -128,7 +129,7 @@ handle_session(#server_hello{cipher_suite = CipherSuite, negotiated_protocol = Protocol}, connection_env = CEnv#connection_env{negotiated_version = Version}}, - case ssl_session:is_new(OldId, NewId) of + case ssl_session:is_new(Session, NewId) of true -> handle_new_session(NewId, CipherSuite, Compression, State#state{connection_states = ConnectionStates}); @@ -168,12 +169,14 @@ user_hello({call, From}, cancel, _State) -> throw(?ALERT_REC(?FATAL, ?USER_CANCELED, user_canceled)); user_hello({call, From}, {handshake_continue, NewOptions, Timeout}, #state{static_env = #static_env{role = Role}, - handshake_env = #handshake_env{hello = Hello}, + handshake_env = HSEnv, ssl_options = Options0} = State0) -> - Options = ssl:handle_options(NewOptions, Role, Options0#{handshake => full}), + Options = ssl:handle_options(NewOptions, Role, Options0), State = ssl_gen_statem:ssl_config(Options, Role, State0), - {next_state, hello, State#state{start_or_recv_from = From}, - [{next_event, internal, Hello}, {{timeout, handshake}, Timeout, close}]}; + {next_state, hello, State#state{start_or_recv_from = From, + handshake_env = HSEnv#handshake_env{continue_status = continue} + }, + [{{timeout, handshake}, Timeout, close}]}; user_hello(info, {'DOWN', _, _, _, _} = Event, State) -> ssl_gen_statem:handle_info(Event, ?FUNCTION_NAME, State); user_hello(_, _, _) -> @@ -316,7 +319,7 @@ certify(internal, #certificate{asn1_certificates = []}, ssl_options = #{verify := verify_peer, fail_if_no_peer_cert := false}} = State0) -> - Connection:next_event(?FUNCTION_NAME, no_record, State0#state{client_certificate_requested = false}); + Connection:next_event(?FUNCTION_NAME, no_record, State0#state{client_certificate_status = empty}); certify(internal, #certificate{}, #state{static_env = #static_env{role = server}, ssl_options = #{verify := verify_none}}) -> @@ -338,14 +341,19 @@ certify(internal, #certificate{asn1_certificates = [Peer|_]} = Cert, ocsp_stapling_state = #{ocsp_expect := Status} = OcspState}, connection_env = #connection_env{ negotiated_version = Version}, - ssl_options = Opts} = State) when Status =/= staple -> + ssl_options = Opts} = State0) when Status =/= staple -> OcspInfo = ocsp_info(OcspState, Opts, Peer), case ssl_handshake:certify(Cert, CertDbHandle, CertDbRef, Opts, CRLDbInfo, Role, Host, ensure_tls(Version), OcspInfo) of {PeerCert, PublicKeyInfo} -> - handle_peer_cert(Role, PeerCert, PublicKeyInfo, - State#state{client_certificate_requested = false}, Connection, []); + State = case Role of + server -> + State0#state{client_certificate_status = needs_verifying}; + client -> + State0 + end, + handle_peer_cert(Role, PeerCert, PublicKeyInfo, State, Connection, []); #alert{} = Alert -> throw(Alert) end; @@ -409,11 +417,11 @@ certify(internal, #certificate_request{}, #state{static_env = #static_env{role = client, protocol_cb = Connection}, session = Session0, - connection_env = #connection_env{cert_key_pairs = [#{certs := [[]]}]}} = State) -> + connection_env = #connection_env{cert_key_alts = [#{certs := [[]]}]}} = State) -> %% 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 algorithms needed as there is %% no certificate to verify. - Connection:next_event(?FUNCTION_NAME, no_record, State#state{client_certificate_requested = true, + Connection:next_event(?FUNCTION_NAME, no_record, State#state{client_certificate_status = requested, session = Session0#session{own_certificates = [[]], private_key = #{}}}); certify(internal, #certificate_request{} = CertRequest, @@ -422,16 +430,17 @@ certify(internal, #certificate_request{} = CertRequest, cert_db = CertDbHandle, cert_db_ref = CertDbRef}, connection_env = #connection_env{negotiated_version = Version, - cert_key_pairs = CertKeyPairs + cert_key_alts = CertKeyAlts }, session = Session0, ssl_options = #{signature_algs := SupportedHashSigns}} = State) -> TLSVersion = ssl:tls_version(Version), + CertKeyPairs = ssl_certificate:available_cert_key_pairs(CertKeyAlts, ssl:tls_version(Version)), Session = select_client_cert_key_pair(Session0, CertRequest, CertKeyPairs, SupportedHashSigns, TLSVersion, CertDbHandle, CertDbRef), Connection:next_event(?FUNCTION_NAME, no_record, - State#state{client_certificate_requested = true, + State#state{client_certificate_status = requested, session = Session}); %% PSK and RSA_PSK might bypass the Server-Key-Exchange certify(internal, #server_hello_done{}, @@ -510,7 +519,7 @@ certify(internal, #server_hello_done{}, end; certify(internal = Type, #client_key_exchange{} = Msg, #state{static_env = #static_env{role = server}, - client_certificate_requested = true, + client_certificate_status = requested, ssl_options = #{fail_if_no_peer_cert := true}}) -> %% We expect a certificate here throw(?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE, {unexpected_msg, {Type, Msg}})); @@ -531,25 +540,22 @@ certify(Type, Event, State) -> ssl_gen_statem:handle_common_event(Type, Event, ?FUNCTION_NAME, State). %%-------------------------------------------------------------------- --spec cipher(gen_statem:event_type(), - #hello_request{} | #certificate_verify{} | #finished{} | term(), +-spec wait_cert_verify(gen_statem:event_type(), + #hello_request{} | #certificate_verify{} | term(), #state{}) -> gen_statem:state_function_result(). %%-------------------------------------------------------------------- -cipher({call, From}, Msg, State) -> - handle_call(Msg, From, ?FUNCTION_NAME, State); -cipher(info, Msg, State) -> - handle_info(Msg, ?FUNCTION_NAME, State); -cipher(internal, #certificate_verify{signature = Signature, - hashsign_algorithm = CertHashSign}, - #state{static_env = #static_env{role = server, - protocol_cb = Connection}, - handshake_env = #handshake_env{tls_handshake_history = Hist, - kex_algorithm = KexAlg, +wait_cert_verify(internal, #certificate_verify{signature = Signature, + hashsign_algorithm = CertHashSign}, + #state{static_env = #static_env{role = server, + protocol_cb = Connection}, + client_certificate_status = needs_verifying, + handshake_env = #handshake_env{tls_handshake_history = Hist, + kex_algorithm = KexAlg, public_key_info = PubKeyInfo}, - connection_env = #connection_env{negotiated_version = Version}, - session = #session{master_secret = MasterSecret} = Session0 - } = State) -> + connection_env = #connection_env{negotiated_version = Version}, + session = #session{master_secret = MasterSecret} = Session0 + } = State) -> TLSVersion = ssl:tls_version(Version), %% Use negotiated value if TLS-1.2 otherwise return default @@ -557,17 +563,27 @@ cipher(internal, #certificate_verify{signature = Signature, case ssl_handshake:certificate_verify(Signature, PubKeyInfo, TLSVersion, HashSign, MasterSecret, Hist) of valid -> - Connection:next_event(?FUNCTION_NAME, no_record, - State#state{session = Session0#session{sign_alg = HashSign}}); + Connection:next_event(cipher, no_record, + State#state{client_certificate_status = verified, + session = Session0#session{sign_alg = HashSign}}); #alert{} = Alert -> throw(Alert) end; -%% client must send a next protocol message if we are expecting it -cipher(internal, #finished{}, - #state{static_env = #static_env{role = server}, - handshake_env = #handshake_env{expecting_next_protocol_negotiation = true, - negotiated_protocol = undefined}}) -> - throw(?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE)); +wait_cert_verify(internal, #hello_request{}, _) -> + keep_state_and_data; +wait_cert_verify(Type, Event, State) -> + ssl_gen_statem:handle_common_event(Type, Event, ?FUNCTION_NAME, State). + +%%-------------------------------------------------------------------- +-spec cipher(gen_statem:event_type(), + #hello_request{} | #finished{} | term(), + #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +cipher({call, From}, Msg, State) -> + handle_call(Msg, From, ?FUNCTION_NAME, State); +cipher(info, Msg, State) -> + handle_info(Msg, ?FUNCTION_NAME, State); cipher(internal, #finished{verify_data = Data} = Finished, #state{static_env = #static_env{role = Role, host = Host, @@ -670,7 +686,8 @@ downgrade(Type, Event, State) -> gen_handshake(StateName, Type, Event, State) -> try tls_dtls_connection:StateName(Type, Event, State) - catch error:_ -> + catch error:Reason:ST -> + ?SSL_LOG(info, handshake_error, [{error, Reason}, {stacktrace, ST}]), throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, malformed_handshake_data)) end. @@ -702,8 +719,12 @@ handle_call({prf, Secret, Label, Seed, WantedLength}, From, _, end, [], Seed)), ssl_handshake:prf(ssl:tls_version(Version), PRFAlgorithm, SecretToUse, Label, SeedToUse, WantedLength) catch - exit:_ -> {error, badarg}; - error:Reason -> {error, Reason} + exit:Reason:ST -> + ?SSL_LOG(info, handshake_error, [{error, Reason}, {stacktrace, ST}]), + {error, badarg}; + error:Reason:ST -> + ?SSL_LOG(info, handshake_error, [{error, Reason}, {stacktrace, ST}]), + {error, Reason} end, {keep_state_and_data, [{reply, From, Reply}]}; handle_call(Msg, From, StateName, State) -> @@ -731,6 +752,7 @@ do_server_hello(Type, #{next_protocol_negotiation := NextProtocols} = ServerHello = ssl_handshake:server_hello(SessId, ssl:tls_version(Version), ConnectionStates1, ServerHelloExt), + State = server_hello(ServerHello, State1#state{handshake_env = HsEnv#handshake_env{expecting_next_protocol_negotiation = NextProtocols =/= undefined}}, Connection), @@ -862,18 +884,18 @@ handle_peer_cert_key(_, _, _, _, State) -> certify_client(#state{static_env = #static_env{role = client, cert_db = CertDbHandle, cert_db_ref = CertDbRef}, - client_certificate_requested = true, + client_certificate_status = requested, session = #session{own_certificates = OwnCerts}} = State, Connection) -> Certificate = ssl_handshake:certificate(OwnCerts, CertDbHandle, CertDbRef, client), Connection:queue_handshake(Certificate, State); -certify_client(#state{client_certificate_requested = false} = State, _) -> +certify_client(#state{client_certificate_status = not_requested} = State, _) -> State. verify_client_cert(#state{static_env = #static_env{role = client}, handshake_env = #handshake_env{tls_handshake_history = Hist}, connection_env = #connection_env{negotiated_version = Version}, - client_certificate_requested = true, + client_certificate_status = requested, session = #session{sign_alg = HashSign, master_secret = MasterSecret, private_key = PrivateKey, @@ -887,13 +909,13 @@ verify_client_cert(#state{static_env = #static_env{role = client}, #alert{} = Alert -> throw(Alert) end; -verify_client_cert(#state{client_certificate_requested = false} = State, _) -> +verify_client_cert(#state{client_certificate_status = not_requested} = State, _) -> State. client_certify_and_key_exchange(State0, Connection) -> State1 = do_client_certify_and_key_exchange(State0, Connection), {State2, Actions} = finalize_handshake(State1, certify, Connection), - State = State2#state{client_certificate_requested = false}, %% Reinitialize + State = State2#state{client_certificate_status = not_requested}, %% Reinitialize Connection:next_event(cipher, no_record, State, Actions). do_client_certify_and_key_exchange(State0, Connection) -> @@ -908,7 +930,8 @@ server_certify_and_key_exchange(State0, Connection) -> certify_client_key_exchange(#encrypted_premaster_secret{premaster_secret= EncPMS}, #state{session = #session{private_key = PrivateKey}, - handshake_env = #handshake_env{client_hello_version = {Major, Minor} = Version}} + handshake_env = #handshake_env{client_hello_version = {Major, Minor} = Version}, + client_certificate_status = CCStatus} = State, Connection) -> FakeSecret = make_premaster_secret(Version, rsa), %% Countermeasure for Bleichenbacher attack always provide some kind of premaster secret @@ -928,55 +951,74 @@ certify_client_key_exchange(#encrypted_premaster_secret{premaster_secret= EncPMS #alert{description = ?DECRYPT_ERROR} -> FakeSecret end, - calculate_master_secret(PremasterSecret, State, Connection, certify, cipher); + calculate_master_secret(PremasterSecret, State, Connection, certify, client_kex_next_state(CCStatus)); certify_client_key_exchange(#client_diffie_hellman_public{dh_public = ClientPublicDhKey}, #state{handshake_env = #handshake_env{diffie_hellman_params = #'DHParameter'{} = Params, - kex_keys = {_, ServerDhPrivateKey}} + kex_keys = {_, ServerDhPrivateKey}}, + client_certificate_status = CCStatus } = State, Connection) -> PremasterSecret = ssl_handshake:premaster_secret(ClientPublicDhKey, ServerDhPrivateKey, Params), - calculate_master_secret(PremasterSecret, State, Connection, certify, cipher); + calculate_master_secret(PremasterSecret, State, Connection, certify, client_kex_next_state(CCStatus)); certify_client_key_exchange(#client_ec_diffie_hellman_public{dh_public = ClientPublicEcDhPoint}, - #state{handshake_env = #handshake_env{kex_keys = ECDHKey}} = State, Connection) -> + #state{handshake_env = #handshake_env{kex_keys = ECDHKey}, + client_certificate_status = CCStatus + } = State, Connection) -> PremasterSecret = ssl_handshake:premaster_secret(#'ECPoint'{point = ClientPublicEcDhPoint}, ECDHKey), - calculate_master_secret(PremasterSecret, State, Connection, certify, cipher); + calculate_master_secret(PremasterSecret, State, Connection, certify, client_kex_next_state(CCStatus)); certify_client_key_exchange(#client_psk_identity{} = ClientKey, #state{ssl_options = - #{user_lookup_fun := PSKLookup}} = State0, + #{user_lookup_fun := PSKLookup}, + client_certificate_status = CCStatus + } = State0, Connection) -> PremasterSecret = ssl_handshake:premaster_secret(ClientKey, PSKLookup), - calculate_master_secret(PremasterSecret, State0, Connection, certify, cipher); + calculate_master_secret(PremasterSecret, State0, Connection, certify, client_kex_next_state(CCStatus)); certify_client_key_exchange(#client_dhe_psk_identity{} = ClientKey, #state{handshake_env = #handshake_env{diffie_hellman_params = #'DHParameter'{} = Params, kex_keys = {_, ServerDhPrivateKey}}, ssl_options = - #{user_lookup_fun := PSKLookup}} = State0, + #{user_lookup_fun := PSKLookup}, + client_certificate_status = CCStatus + } = State0, Connection) -> PremasterSecret = ssl_handshake:premaster_secret(ClientKey, ServerDhPrivateKey, Params, PSKLookup), - calculate_master_secret(PremasterSecret, State0, Connection, certify, cipher); + calculate_master_secret(PremasterSecret, State0, Connection, certify, client_kex_next_state(CCStatus)); certify_client_key_exchange(#client_ecdhe_psk_identity{} = ClientKey, #state{handshake_env = #handshake_env{kex_keys = ServerEcDhPrivateKey}, ssl_options = - #{user_lookup_fun := PSKLookup}} = State, + #{user_lookup_fun := PSKLookup}, + client_certificate_status = CCStatus + } = State, Connection) -> PremasterSecret = ssl_handshake:premaster_secret(ClientKey, ServerEcDhPrivateKey, PSKLookup), - calculate_master_secret(PremasterSecret, State, Connection, certify, cipher); + calculate_master_secret(PremasterSecret, State, Connection, certify, client_kex_next_state(CCStatus)); certify_client_key_exchange(#client_rsa_psk_identity{} = ClientKey, #state{session = #session{private_key = PrivateKey}, ssl_options = - #{user_lookup_fun := PSKLookup}} = State0, + #{user_lookup_fun := PSKLookup}, + client_certificate_status = CCStatus + } = State0, Connection) -> PremasterSecret = ssl_handshake:premaster_secret(ClientKey, PrivateKey, PSKLookup), - calculate_master_secret(PremasterSecret, State0, Connection, certify, cipher); + calculate_master_secret(PremasterSecret, State0, Connection, certify, client_kex_next_state(CCStatus)); certify_client_key_exchange(#client_srp_public{} = ClientKey, #state{handshake_env = #handshake_env{srp_params = Params, - kex_keys = Key} + kex_keys = Key}, + client_certificate_status = CCStatus } = State0, Connection) -> PremasterSecret = ssl_handshake:premaster_secret(ClientKey, Key, Params), - calculate_master_secret(PremasterSecret, State0, Connection, certify, cipher). + calculate_master_secret(PremasterSecret, State0, Connection, certify, client_kex_next_state(CCStatus)). + +client_kex_next_state(needs_verifying) -> + wait_cert_verify; +client_kex_next_state(empty) -> + cipher; +client_kex_next_state(not_requested) -> + cipher. certify_server(#state{handshake_env = #handshake_env{kex_algorithm = KexAlg}} = State, _) when KexAlg == dh_anon; @@ -1285,15 +1327,15 @@ request_client_cert(#state{handshake_env = #handshake_env{kex_algorithm = Alg}} request_client_cert(#state{static_env = #static_env{cert_db = CertDbHandle, cert_db_ref = CertDbRef}, connection_env = #connection_env{negotiated_version = Version}, - ssl_options = #{verify := verify_peer, - signature_algs := SupportedHashSigns}} = State0, Connection) -> + ssl_options = #{verify := verify_peer} = Opts} = State0, Connection) -> + SupportedHashSigns = maps:get(signature_algs, Opts, undefined), TLSVersion = ssl:tls_version(Version), HashSigns = ssl_handshake:available_signature_algs(SupportedHashSigns, TLSVersion), Msg = ssl_handshake:certificate_request(CertDbHandle, CertDbRef, HashSigns, TLSVersion), State = Connection:queue_handshake(Msg, State0), - State#state{client_certificate_requested = true}; + State#state{client_certificate_status = requested}; request_client_cert(#state{ssl_options = #{verify := verify_none}} = State, _) -> @@ -1447,22 +1489,20 @@ generate_srp_server_keys(_SrpParams, 10) -> generate_srp_server_keys(SrpParams = #srp_user{generator = Generator, prime = Prime, verifier = Verifier}, N) -> - try crypto:generate_key(srp, {host, [Verifier, Generator, Prime, '6a']}) of - Keys -> - Keys + try crypto:generate_key(srp, {host, [Verifier, Generator, Prime, '6a']}) catch - error:_ -> + error:Reason:ST -> + ?SSL_LOG(debug, crypto_error, [{error, Reason}, {stacktrace, ST}]), generate_srp_server_keys(SrpParams, N+1) end. generate_srp_client_keys(_Generator, _Prime, 10) -> throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)); generate_srp_client_keys(Generator, Prime, N) -> - try crypto:generate_key(srp, {user, [Generator, Prime, '6a']}) of - Keys -> - Keys + try crypto:generate_key(srp, {user, [Generator, Prime, '6a']}) catch - error:_ -> + error:Reason:ST -> + ?SSL_LOG(debug, crypto_error, [{error, Reason}, {stacktrace, ST}]), generate_srp_client_keys(Generator, Prime, N+1) end. @@ -1651,7 +1691,7 @@ select_client_cert_key_pair(Session0, CertRequest, CertKeyPairs, SupportedHashSi select_client_cert_key_pair(Session0, CertRequest, CertKeyPairs, SupportedHashSigns, TLSVersion, CertDbHandle, CertDbRef, undefined). select_client_cert_key_pair(Session0,_,[], _, _,_,_, undefined) -> - %% No certificate compliant with signing algorithms found: empty certificate will be sent + %% No certificate compliant with supported algorithms: empty certificate will be sent Session0#session{own_certificates = [[]], private_key = #{}}; select_client_cert_key_pair(_,_,[], _, _,_,_,#session{}=Session) -> @@ -1661,7 +1701,7 @@ select_client_cert_key_pair(Session0, #certificate_request{certificate_authoriti [#{private_key := PrivateKey, certs := [Cert| _] = Certs} | Rest], SupportedHashSigns, TLSVersion, CertDbHandle, CertDbRef, Default) -> case ssl_handshake:select_hashsign(CertRequest, Cert, SupportedHashSigns, TLSVersion) of - #alert {} -> + #alert{} -> select_client_cert_key_pair(Session0, CertRequest, Rest, SupportedHashSigns, TLSVersion, CertDbHandle, CertDbRef, Default); SelectedHashSign -> case ssl_certificate:handle_cert_auths(Certs, CertAuths, CertDbHandle, CertDbRef) of diff --git a/lib/ssl/src/tls_gen_connection.erl b/lib/ssl/src/tls_gen_connection.erl index cff0abdcb3..940666f104 100644 --- a/lib/ssl/src/tls_gen_connection.erl +++ b/lib/ssl/src/tls_gen_connection.erl @@ -70,42 +70,70 @@ close/4, protocol_name/0]). --define(DIST_CNTRL_SPAWN_OPTS, [{priority, max}]). - %%==================================================================== %% Internal application API -%%==================================================================== +%%==================================================================== %%==================================================================== %% Setup %%==================================================================== -start_fsm(Role, Host, Port, Socket, {#{erl_dist := false},_, Trackers} = Opts, - User, {CbModule, _,_, _, _} = CbInfo, - Timeout) -> - try - {ok, DynSup} = tls_connection_sup:start_child([]), - {ok, Sender} = tls_dyn_connection_sup:start_child(DynSup, sender, []), - {ok, Pid} = tls_dyn_connection_sup:start_child(DynSup, receiver, [Role, Sender, Host, Port, Socket, - Opts, User, CbInfo]), - {ok, SslSocket} = ssl_gen_statem:socket_control(?MODULE, Socket, [Pid, Sender], CbModule, Trackers), - ssl_gen_statem:handshake(SslSocket, Timeout) - catch - error:{badmatch, {error, _} = Error} -> - Error - end; +start_fsm(Role, Host, Port, Socket, + {#{erl_dist := ErlDist, sender_spawn_opts := SenderSpawnOpts}, _, Trackers} = Opts, + User, {CbModule, _, _, _, _} = CbInfo, + Timeout) -> + SenderOptions = handle_sender_options(ErlDist, SenderSpawnOpts), + Starter = start_connection_tree(User, ErlDist, SenderOptions, + Role, [Host, Port, Socket, Opts, User, CbInfo]), + receive + {Starter, SockReceiver, SockSender} -> + socket_control(Socket, SockReceiver, SockSender, CbModule, Trackers, Timeout); + {Starter, Error} -> + Error + end. -start_fsm(Role, Host, Port, Socket, {#{erl_dist := true},_, Trackers} = Opts, - User, {CbModule, _,_, _, _} = CbInfo, - Timeout) -> - try - {ok, DynSup} = tls_connection_sup:start_child_dist([]), - {ok, Sender} = tls_dyn_connection_sup:start_child(DynSup, sender, [[{spawn_opt, ?DIST_CNTRL_SPAWN_OPTS}]]), - {ok, Pid} = tls_dyn_connection_sup:start_child(DynSup, receiver, [Role, Sender, Host, Port, Socket, - Opts, User, CbInfo]), - {ok, SslSocket} = ssl_gen_statem:socket_control(?MODULE, Socket, [Pid, Sender], CbModule, Trackers), - ssl_gen_statem:handshake(SslSocket, Timeout) - catch - error:{badmatch, {error, _} = Error} -> - Error +handle_sender_options(ErlDist, SpawnOpts) -> + case ErlDist of + true -> + [[{spawn_opt, [{priority, max} | proplists:delete(priority, SpawnOpts)]}]]; + false -> + [[{spawn_opt, SpawnOpts}]] + end. + +start_connection_tree(User, IsErlDist, SenderOpts, Role, ReceiverOpts) -> + StartConnectionTree = + fun() -> + case start_dyn_connection_sup(IsErlDist) of + {ok, DynSup} -> + case tls_dyn_connection_sup:start_child(DynSup, sender, SenderOpts) of + {ok, Sender} -> + case tls_dyn_connection_sup:start_child(DynSup, receiver, + [Role, Sender | ReceiverOpts]) of + {ok, Receiver} -> + User ! {self(), Receiver, Sender}; + {error, Error} -> + User ! {self(), Error}, + exit(DynSup, shutdown) + end; + {error, Error} -> + User ! {self(), Error}, + exit(DynSup, shutdown) + end; + {error, Error} -> + User ! {self(), Error} + end + end, + spawn(StartConnectionTree). + +start_dyn_connection_sup(true) -> + tls_connection_sup:start_child_dist([]); +start_dyn_connection_sup(false) -> + tls_connection_sup:start_child([]). + +socket_control(Socket, SockReceiver, SockSender, CbModule, Trackers, Timeout) -> + case ssl_gen_statem:socket_control(?MODULE, Socket, [SockReceiver, SockSender], CbModule, Trackers) of + {ok, SslSocket} -> + ssl_gen_statem:handshake(SslSocket, Timeout); + Error -> + Error end. pids(#state{protocol_specific = #{sender := Sender}}) -> @@ -118,24 +146,26 @@ initialize_tls_sender(#state{static_env = #static_env{ trackers = Trackers }, connection_env = #connection_env{negotiated_version = Version}, - socket_options = SockOpts, + socket_options = SockOpts, ssl_options = #{renegotiate_at := RenegotiateAt, key_update_at := KeyUpdateAt, erl_dist := ErlDist, - log_level := LogLevel}, + log_level := LogLevel, + hibernate_after := HibernateAfter}, connection_states = #{current_write := ConnectionWriteState}, protocol_specific = #{sender := Sender}}) -> Init = #{current_write => ConnectionWriteState, role => Role, socket => Socket, socket_options => SockOpts, - erl_dist => ErlDist, + erl_dist => ErlDist, trackers => Trackers, transport_cb => Transport, negotiated_version => Version, renegotiate_at => RenegotiateAt, key_update_at => KeyUpdateAt, - log_level => LogLevel}, + log_level => LogLevel, + hibernate_after => HibernateAfter}, tls_sender:initialize(Sender, Init). %%==================================================================== @@ -355,16 +385,28 @@ handle_protocol_record(#ssl_tls{type = ?APPLICATION_DATA}, StateName, } = State) when StateName == initial_hello; StateName == hello; StateName == certify; + StateName == wait_cert_verify; + StateName == wait_ocsp_stapling; StateName == abbreviated; StateName == cipher -> %% Application data can not be sent before initial handshake pre TLS-1.3. Alert = ?ALERT_REC(?FATAL, ?UNEXPECTED_MESSAGE, application_data_before_initial_handshake), ssl_gen_statem:handle_own_alert(Alert, StateName, State); -handle_protocol_record(#ssl_tls{type = ?APPLICATION_DATA}, start = StateName, +handle_protocol_record(#ssl_tls{type = ?APPLICATION_DATA, early_data = false}, StateName, #state{static_env = #static_env{role = server} - } = State) -> - Alert = ?ALERT_REC(?FATAL, ?DECODE_ERROR, invalid_tls_13_message), + } = State) when StateName == start; + StateName == recvd_ch; + StateName == negotiated; + StateName == wait_eoed -> + Alert = ?ALERT_REC(?FATAL, ?UNEXPECTED_MESSAGE, none_early_application_data_before_handshake), + ssl_gen_statem:handle_own_alert(Alert, StateName, State); +handle_protocol_record(#ssl_tls{type = ?APPLICATION_DATA}, StateName, + #state{static_env = #static_env{role = server} + } = State) when StateName == wait_cert; + StateName == wait_cv; + StateName == wait_finished-> + Alert = ?ALERT_REC(?FATAL, ?UNEXPECTED_MESSAGE, application_data_before_handshake_or_intervened_in_post_handshake_auth), ssl_gen_statem:handle_own_alert(Alert, StateName, State); handle_protocol_record(#ssl_tls{type = ?APPLICATION_DATA, fragment = Data}, StateName, #state{start_or_recv_from = From, @@ -389,26 +431,17 @@ handle_protocol_record(#ssl_tls{type = ?APPLICATION_DATA, fragment = Data}, Stat next_event(StateName, Record, State) end; %%% TLS record protocol level handshake messages -handle_protocol_record(#ssl_tls{type = ?HANDSHAKE, fragment = Data}, - StateName, #state{protocol_buffers = - #protocol_buffers{tls_handshake_buffer = Buf0} = Buffers, - connection_env = #connection_env{negotiated_version = Version}, - static_env = #static_env{role = Role}, - ssl_options = Options} = State0) -> +handle_protocol_record(#ssl_tls{type = ?HANDSHAKE, fragment = Data}, StateName, + #state{ssl_options = Options, protocol_buffers = Buffers} = State0) -> try - %% Calculate the effective version that should be used when decoding an incoming handshake - %% message. - EffectiveVersion = effective_version(Version, Options, Role, StateName), - {Packets, Buf} = tls_handshake:get_tls_handshake(EffectiveVersion,Data,Buf0, Options), - State = - State0#state{protocol_buffers = - Buffers#protocol_buffers{tls_handshake_buffer = Buf}}, - case Packets of + {HSPackets, NewHSBuffer, RecordRest} = get_tls_handshakes(Data, StateName, State0), + State = State0#state{protocol_buffers = Buffers#protocol_buffers{tls_handshake_buffer = NewHSBuffer}}, + case HSPackets of [] -> - assert_buffer_sanity(Buf, Options), + assert_buffer_sanity(NewHSBuffer, Options), next_event(StateName, no_record, State); _ -> - Events = tls_handshake_events(Packets), + Events = tls_handshake_events(HSPackets, RecordRest), case StateName of connection -> ssl_gen_statem:hibernate_after(StateName, State, Events); @@ -437,7 +470,8 @@ handle_protocol_record(#ssl_tls{type = ?ALERT, fragment = EncAlerts}, StateName, #alert{} = Alert -> ssl_gen_statem:handle_own_alert(Alert, StateName, State) catch - _:_ -> + _:Reason:ST -> + ?SSL_LOG(info, handshake_error, [{error, Reason}, {stacktrace, ST}]), ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, alert_decode_error), StateName, State) @@ -494,7 +528,8 @@ send_sync_alert( Alert, #state{protocol_specific = #{sender := Sender}} = State) -> try tls_sender:send_and_ack_alert(Sender, Alert) catch - _:_ -> + _:Reason:ST -> + ?SSL_LOG(info, "Send failed", [{error, Reason}, {stacktrace, ST}]), throw({stop, {shutdown, own_alert}, State}) end. @@ -528,10 +563,30 @@ protocol_name() -> %%==================================================================== %% Internal functions %%==================================================================== -tls_handshake_events(Packets) -> - lists:map(fun(Packet) -> - {next_event, internal, {handshake, Packet}} - end, Packets). +get_tls_handshakes(Data, StateName, #state{protocol_buffers = #protocol_buffers{tls_handshake_buffer = HSBuffer}, + connection_env = #connection_env{negotiated_version = Version}, + static_env = #static_env{role = Role}, + ssl_options = Options}) -> + case handle_unnegotiated_version(Version, Options, Data, HSBuffer, Role, StateName) of + {HSPackets, NewHSBuffer} -> + %% Common case + NoRecordRest = <<>>, + {HSPackets, NewHSBuffer, NoRecordRest}; + {_Packets, _HSBuffer, _RecordRest} = Result -> + %% Possible coalesced TLS record data from pre TLS-1.3 server + Result + end. + +tls_handshake_events(HSPackets, <<>>) -> + lists:map(fun(HSPacket) -> + {next_event, internal, {handshake, HSPacket}} + end, HSPackets); + +tls_handshake_events(HSPackets, RecordRest) -> + %% Coalesced TLS record data to be handled after first handshake message has been handled + RestEvent = {next_event, internal, {protocol_record, #ssl_tls{type = ?HANDSHAKE, fragment = RecordRest}}}, + FirstHS = tls_handshake_events(HSPackets, <<>>), + FirstHS ++ [RestEvent]. unprocessed_events(Events) -> %% The first handshake event will be processed immediately @@ -675,32 +730,29 @@ activate_socket(#state{protocol_specific = #{active_n_toggle := true, active_n : %% Decipher next record and concatenate consecutive ?APPLICATION_DATA records into one %% next_record(State, CipherTexts, ConnectionStates, Check) -> - next_record(State, CipherTexts, ConnectionStates, Check, []). + next_record(State, CipherTexts, ConnectionStates, Check, [], false). %% next_record(#state{connection_env = #connection_env{negotiated_version = {3,4} = Version}} = State, - [CT|CipherTexts], ConnectionStates0, Check, Acc) -> + [CT|CipherTexts], ConnectionStates0, Check, Acc, IsEarlyData) -> case tls_record:decode_cipher_text(Version, CT, ConnectionStates0, Check) of - {#ssl_tls{type = ?APPLICATION_DATA, fragment = Fragment}, ConnectionStates} -> + {Record = #ssl_tls{type = ?APPLICATION_DATA, fragment = Fragment}, ConnectionStates} -> case CipherTexts of [] -> %% End of cipher texts - build and deliver an ?APPLICATION_DATA record %% from the accumulated fragments next_record_done(State, [], ConnectionStates, - #ssl_tls{type = ?APPLICATION_DATA, - fragment = iolist_to_binary(lists:reverse(Acc, [Fragment]))}); + Record#ssl_tls{type = ?APPLICATION_DATA, + fragment = iolist_to_binary(lists:reverse(Acc, [Fragment]))}); [_|_] -> - next_record(State, CipherTexts, ConnectionStates, Check, [Fragment|Acc]) + next_record(State, CipherTexts, ConnectionStates, Check, [Fragment|Acc], Record#ssl_tls.early_data) end; - {trial_decryption_failed, ConnectionStates} -> + {no_record, ConnectionStates} -> case CipherTexts of [] -> - %% End of cipher texts - build and deliver an ?APPLICATION_DATA record - %% from the accumulated fragments - next_record_done(State, [], ConnectionStates, - #ssl_tls{type = ?APPLICATION_DATA, - fragment = iolist_to_binary(lists:reverse(Acc))}); + Record = accumulated_app_record(Acc, IsEarlyData), + next_record_done(State, [], ConnectionStates, Record); [_|_] -> - next_record(State, CipherTexts, ConnectionStates, Check, Acc) + next_record(State, CipherTexts, ConnectionStates, Check, Acc, IsEarlyData) end; {Record, ConnectionStates} when Acc =:= [] -> %% Singleton non-?APPLICATION_DATA record - deliver @@ -711,33 +763,36 @@ next_record(#state{connection_env = #connection_env{negotiated_version = {3,4} = %% and forget about decrypting this record - we'll decrypt it again next time %% Will not work for stream ciphers next_record_done(State, [CT|CipherTexts], ConnectionStates0, - #ssl_tls{type = ?APPLICATION_DATA, fragment = iolist_to_binary(lists:reverse(Acc))}); + #ssl_tls{type = ?APPLICATION_DATA, + early_data = IsEarlyData, + fragment = iolist_to_binary(lists:reverse(Acc))}); #alert{} = Alert -> Alert end; next_record(#state{connection_env = #connection_env{negotiated_version = Version}} = State, - [#ssl_tls{type = ?APPLICATION_DATA} = CT |CipherTexts], ConnectionStates0, Check, Acc) -> + [#ssl_tls{type = ?APPLICATION_DATA} = CT |CipherTexts], ConnectionStates0, Check, Acc, NotRelevant) -> case tls_record:decode_cipher_text(Version, CT, ConnectionStates0, Check) of - {#ssl_tls{type = ?APPLICATION_DATA, fragment = Fragment}, ConnectionStates} -> + {Record = #ssl_tls{type = ?APPLICATION_DATA, fragment = Fragment}, ConnectionStates} -> case CipherTexts of [] -> %% End of cipher texts - build and deliver an ?APPLICATION_DATA record %% from the accumulated fragments next_record_done(State, [], ConnectionStates, - #ssl_tls{type = ?APPLICATION_DATA, - fragment = iolist_to_binary(lists:reverse(Acc, [Fragment]))}); + Record#ssl_tls{type = ?APPLICATION_DATA, + fragment = iolist_to_binary(lists:reverse(Acc, [Fragment]))}); [_|_] -> - next_record(State, CipherTexts, ConnectionStates, Check, [Fragment|Acc]) + next_record(State, CipherTexts, ConnectionStates, Check, [Fragment|Acc], NotRelevant) end; #alert{} = Alert -> Alert end; -next_record(State, CipherTexts, ConnectionStates, _, [_|_] = Acc) -> +next_record(State, CipherTexts, ConnectionStates, _, [_|_] = Acc, IsEarlyData) -> next_record_done(State, CipherTexts, ConnectionStates, #ssl_tls{type = ?APPLICATION_DATA, + early_data = IsEarlyData, fragment = iolist_to_binary(lists:reverse(Acc))}); next_record(#state{connection_env = #connection_env{negotiated_version = Version}} = State, - [CT|CipherTexts], ConnectionStates0, Check, []) -> + [CT|CipherTexts], ConnectionStates0, Check, [], _) -> case tls_record:decode_cipher_text(Version, CT, ConnectionStates0, Check) of {Record, ConnectionStates} -> %% Singleton non-?APPLICATION_DATA record - deliver @@ -746,6 +801,13 @@ next_record(#state{connection_env = #connection_env{negotiated_version = Version Alert end. +accumulated_app_record([], _) -> + no_record; +accumulated_app_record([_|_] = Acc, IsEarlyData) -> + #ssl_tls{type = ?APPLICATION_DATA, + early_data = IsEarlyData, + fragment = iolist_to_binary(lists:reverse(Acc))}. + next_record_done(#state{protocol_buffers = Buffers} = State, CipherTexts, ConnectionStates, Record) -> {Record, State#state{protocol_buffers = Buffers#protocol_buffers{tls_cipher_texts = CipherTexts}, @@ -755,21 +817,27 @@ next_record_done(#state{protocol_buffers = Buffers} = State, CipherTexts, Connec %% Pre TLS-1.3, on the client side, the connection state variable `negotiated_version` will initially be %% the requested version. On the server side the same variable is initially undefined. %% When the client can support TLS-1.3 and one or more prior versions and we are waiting -%% for the server hello (with or without a RetryRequest, that is in state hello or in state wait_sh), -%% the "initial requested version" kept in the connection state variable `negotiated_version` +%% for the server hello the "initial requested version" kept in the connection state variable `negotiated_version` %% (before the versions is actually negotiated) will always be the value of TLS-1.2 (which is a legacy %% field in TLS-1.3 client hello). The versions are instead negotiated with an hello extension. When %% decoding the server_hello messages we want to go through TLS-1.3 decode functions to be able %% to handle TLS-1.3 extensions if TLS-1.3 will be the negotiated version. -effective_version({3,3} , #{versions := [{3,4} = Version |_]}, client, StateName) when StateName == hello; - StateName == wait_sh -> - Version; +handle_unnegotiated_version({3,3} , #{versions := [{3,4} = Version |_]} = Options, Data, Buffer, client, hello) -> + %% The effective version for decoding the server hello message should be the TLS-1.3. Possible coalesced TLS-1.2 + %% server handshake messages should be decoded with the negotiated version in later state. + <<_:8, ?UINT24(Length), _/binary>> = Data, + <<FirstPacket:(Length+4)/binary, RecordRest/binary>> = Data, + {HSPacket, <<>> = NewHsBuffer} = tls_handshake:get_tls_handshakes(Version, FirstPacket, Buffer, Options), + {HSPacket, NewHsBuffer, RecordRest}; +%% TLS-1.3 RetryRequest +handle_unnegotiated_version({3,3} , #{versions := [{3,4} = Version |_]} = Options, Data, Buffer, client, wait_sh) -> + tls_handshake:get_tls_handshakes(Version, Data, Buffer, Options); %% When the `negotiated_version` variable is not yet set use the highest supported version. -effective_version(undefined, #{versions := [Version|_]}, _, _) -> - Version; +handle_unnegotiated_version(undefined, #{versions := [Version|_]} = Options, Data, Buff, _, _) -> + tls_handshake:get_tls_handshakes(Version, Data, Buff, Options); %% In all other cases use the version saved in the connection state variable `negotiated_version` -effective_version(Version, _, _, _) -> - Version. +handle_unnegotiated_version(Version, Options, Data, Buff, _, _) -> + tls_handshake:get_tls_handshakes(Version, Data, Buff, Options). assert_buffer_sanity(<<?BYTE(_Type), ?UINT24(Length), Rest/binary>>, #{max_handshake_size := Max}) when diff --git a/lib/ssl/src/tls_handshake.erl b/lib/ssl/src/tls_handshake.erl index 7a3d9712aa..7704ff7b6b 100644 --- a/lib/ssl/src/tls_handshake.erl +++ b/lib/ssl/src/tls_handshake.erl @@ -42,7 +42,7 @@ -export([encode_handshake/2]). %% Handshake decoding --export([get_tls_handshake/4, decode_handshake/3]). +-export([get_tls_handshakes/4, decode_handshake/3]). %% Handshake helper -export([ocsp_nonce/2]). @@ -245,7 +245,8 @@ hello(#client_hello{client_version = _ClientVersion, Version = ssl_handshake:select_supported_version(ClientVersions, Versions), do_hello(Version, Versions, CipherSuites, Hello, SslOpts, Info, Renegotiation) catch - error:_ -> + error:Reason:ST -> + ?SSL_LOG(info, handshake_error, [{reason,Reason}, {stacktrace, ST}]), throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, malformed_handshake_data)) end; @@ -260,7 +261,8 @@ hello(#client_hello{client_version = ClientVersion, error:{case_clause,{asn1, Asn1Reason}} -> %% ASN-1 decode of certificate somehow failed throw(?ALERT_REC(?FATAL, ?INTERNAL_ERROR, {failed_to_decode_own_certificate, Asn1Reason})); - error:_ -> + error:Reason:ST -> + ?SSL_LOG(info, handshake_error, [{reason,Reason}, {stacktrace, ST}]), throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, malformed_handshake_data)) end. @@ -285,7 +287,7 @@ encode_handshake(Package, Version) -> %%-------------------------------------------------------------------- %%-------------------------------------------------------------------- --spec get_tls_handshake(tls_record:tls_version(), binary(), binary() | iolist(), +-spec get_tls_handshakes(tls_record:tls_version(), binary(), binary() | iolist(), ssl_options()) -> {[{tls_handshake(), binary()}], binary()}. %% @@ -293,10 +295,10 @@ encode_handshake(Package, Version) -> %% and returns it as a list of handshake messages, also returns leftover %% data. %%-------------------------------------------------------------------- -get_tls_handshake(Version, Data, <<>>, Options) -> - get_tls_handshake_aux(Version, Data, Options, []); -get_tls_handshake(Version, Data, Buffer, Options) -> - get_tls_handshake_aux(Version, list_to_binary([Buffer, Data]), Options, []). +get_tls_handshakes(Version, Data, <<>>, Options) -> + get_tls_handshakes_aux(Version, Data, Options, []); +get_tls_handshakes(Version, Data, Buffer, Options) -> + get_tls_handshakes_aux(Version, list_to_binary([Buffer, Data]), Options, []). %%-------------------------------------------------------------------- %%% Handshake helper @@ -330,8 +332,8 @@ handle_client_hello(Version, true -> SupportedHashSigns = maps:get(signature_algs, SslOpts, undefined), Curves = maps:get(elliptic_curves, HelloExt, undefined), - ClientHashSigns = maps:get(signature_algs, HelloExt, undefined), - ClientSignatureSchemes = maps:get(signature_algs_cert, HelloExt, undefined), + ClientHashSigns = get_signature_ext(signature_algs, HelloExt, Version), + ClientSignatureSchemes = get_signature_ext(signature_algs_cert, HelloExt, Version), AvailableHashSigns = ssl_handshake:available_signature_algs( ClientHashSigns, SupportedHashSigns, Version), ECCCurve = ssl_handshake:select_curve(Curves, SupportedECCs, Version, ECCOrder), @@ -357,7 +359,7 @@ handle_client_hello(Version, CipherSuites, HelloExt, SslOpts, Session1, ConnectionStates0, - Renegotiation, HashSign) + Renegotiation, HashSign) end end; false -> @@ -426,19 +428,20 @@ enc_handshake(HandshakeMsg, Version) -> ssl_handshake:encode_handshake(HandshakeMsg, Version). %%-------------------------------------------------------------------- -get_tls_handshake_aux(Version, <<?BYTE(Type), ?UINT24(Length), +get_tls_handshakes_aux(Version, <<?BYTE(Type), ?UINT24(Length), Body:Length/binary,Rest/binary>>, #{log_level := LogLevel} = Opts, Acc) -> Raw = <<?BYTE(Type), ?UINT24(Length), Body/binary>>, try decode_handshake(Version, Type, Body) of Handshake -> ssl_logger:debug(LogLevel, inbound, 'handshake', Handshake), - get_tls_handshake_aux(Version, Rest, Opts, [{Handshake,Raw} | Acc]) + get_tls_handshakes_aux(Version, Rest, Opts, [{Handshake,Raw} | Acc]) catch - error:_ -> - throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, handshake_decode_error)) + error:Reason:ST -> + ?SSL_LOG(info, handshake_error, [{reason,Reason}, {stacktrace, ST}]), + throw(?ALERT_REC(?FATAL, ?DECODE_ERROR, handshake_decode_error)) end; -get_tls_handshake_aux(_Version, Data, _, Acc) -> +get_tls_handshakes_aux(_Version, Data, _, Acc) -> {lists:reverse(Acc), Data}. decode_handshake({3, N}, ?HELLO_REQUEST, <<>>) when N < 4 -> @@ -470,3 +473,22 @@ ocsp_expect(true) -> staple; ocsp_expect(_) -> no_staple. + +get_signature_ext(Ext, HelloExt, {3,3}) -> + case maps:get(Ext, HelloExt, undefined) of + %% Signature algorithms was not sent + undefined -> + undefined; + %% Can happen when connection is upgraded and sni_fun changes + %% the versions option from default + #signature_algorithms{signature_scheme_list = Schemes} -> + #hash_sign_algos{hash_sign_algos = ssl_cipher:signature_schemes_1_2(Schemes)}; + #signature_algorithms_cert{} = Algos -> + Algos; + #hash_sign_algos{} = Algos -> + Algos + end; +get_signature_ext(Ext, HelloExt, _) -> + maps:get(Ext, HelloExt, undefined). + + diff --git a/lib/ssl/src/tls_handshake_1_3.erl b/lib/ssl/src/tls_handshake_1_3.erl index 84ee036a46..750a79887f 100644 --- a/lib/ssl/src/tls_handshake_1_3.erl +++ b/lib/ssl/src/tls_handshake_1_3.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2022. All Rights Reserved. +%% Copyright Ericsson AB 2007-2023. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -58,8 +58,7 @@ maybe_add_binders/4, maybe_add_early_data_indication/3, maybe_automatic_session_resumption/1, - maybe_send_early_data/1, - update_current_read/3]). + maybe_send_early_data/1]). -export([get_max_early_data/1, is_valid_binder/4, @@ -205,18 +204,23 @@ encrypted_extensions(#state{handshake_env = HandshakeEnv}) -> }. -certificate_request(SignAlgs0, SignAlgsCert0, CertDbHandle, CertDbRef) -> +certificate_request(SignAlgs0, SignAlgsCert0, CertDbHandle, CertDbRef, CertAuthBool) -> %% Input arguments contain TLS 1.2 algorithms due to backward compatibility %% reasons. These {Hash, Algo} tuples must be filtered before creating the %% the extensions. SignAlgs = filter_tls13_algs(SignAlgs0), SignAlgsCert = filter_tls13_algs(SignAlgsCert0), Extensions0 = add_signature_algorithms(#{}, SignAlgs), - Extensions = add_signature_algorithms_cert(Extensions0, SignAlgsCert), - Auths = ssl_handshake:certificate_authorities(CertDbHandle, CertDbRef), + Extensions1 = add_signature_algorithms_cert(Extensions0, SignAlgsCert), + Extensions = if CertAuthBool =:= true -> + Auths = ssl_handshake:certificate_authorities(CertDbHandle, CertDbRef), + Extensions1#{certificate_authorities => #certificate_authorities{authorities = Auths}}; + true -> + Extensions1 + end, #certificate_request_1_3{ certificate_request_context = <<>>, - extensions = Extensions#{certificate_authorities => #certificate_authorities{authorities = Auths}}}. + extensions = Extensions}. add_signature_algorithms(Extensions, SignAlgs) -> @@ -587,21 +591,21 @@ certificate_entry(DER) -> %% 0101010101010101010101010101010101010101010101010101010101010101 sign(THash, Context, HashAlgo, PrivateKey, SignAlgo) -> Content = build_content(Context, THash), - try ssl_handshake:digitally_signed({3,4}, Content, HashAlgo, PrivateKey, SignAlgo) of - Signature -> - {ok, Signature} - catch - error:badarg -> - {error, ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, badarg)} + try + {ok, ssl_handshake:digitally_signed({3,4}, Content, HashAlgo, PrivateKey, SignAlgo)} + catch throw:Alert -> + {error, Alert} end. + verify(THash, Context, HashAlgo, SignAlgo, Signature, PublicKeyInfo) -> Content = build_content(Context, THash), try ssl_handshake:verify_signature({3, 4}, Content, {HashAlgo, SignAlgo}, Signature, PublicKeyInfo) of Result -> {ok, Result} catch - error:badarg -> + error:Reason:ST -> + ?SSL_LOG(debug, handshake_error, [{reason, Reason}, {stacktrace, ST}]), {error, ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, badarg)} end. @@ -646,13 +650,13 @@ do_start(#client_hello{cipher_suites = ClientCiphers, maps:get(signature_algs, Extensions, undefined)), ClientSignAlgsCert = get_signature_scheme_list( maps:get(signature_algs_cert, Extensions, undefined)), - CertAuths = get_certificate_authorites(maps:get(certificate_authorities, Extensions, undefined)), + CertAuths = get_certificate_authorities(maps:get(certificate_authorities, Extensions, undefined)), CookieExt = maps:get(cookie, Extensions, undefined), Cookie = get_cookie(CookieExt), #state{connection_states = ConnectionStates0, session = Session0, - connection_env = #connection_env{cert_key_pairs = CertKeyPairs}} = State1 = + connection_env = #connection_env{cert_key_alts = CertKeyAlts}} = State1 = Maybe(ssl_gen_statem:handle_sni_extension(SNI, State0)), Maybe(validate_cookie(Cookie, State1)), @@ -667,6 +671,7 @@ do_start(#client_hello{cipher_suites = ClientCiphers, Cipher = Maybe(select_cipher_suite(HonorCipherOrder, ClientCiphers, ServerCiphers)), Groups = Maybe(select_common_groups(ServerGroups, ClientGroups)), Maybe(validate_client_key_share(ClientGroups, ClientShares)), + CertKeyPairs = ssl_certificate:available_cert_key_pairs(CertKeyAlts, {3,4}), #session{own_certificates = [Cert|_]} = Session = Maybe(select_server_cert_key_pair(Session0, CertKeyPairs, ClientSignAlgs, ClientSignAlgsCert, CertAuths, State0, @@ -746,6 +751,7 @@ do_start(#server_hello{cipher_suite = SelectedCipherSuite, handshake_env = #handshake_env{renegotiation = {Renegotiation, _}, ocsp_stapling_state = OcspState}, connection_env = #connection_env{negotiated_version = NegotiatedVersion}, + protocol_specific = PS, ssl_options = #{ciphers := ClientCiphers, supported_groups := ClientGroups0, use_ticket := UseTicket, @@ -819,8 +825,17 @@ do_start(#server_hello{cipher_suite = SelectedCipherSuite, handshake_env = HsEnv#handshake_env{tls_handshake_history = HHistory}, key_share = ClientKeyShare}, - {State, wait_sh} - + %% If it is a hello_retry and middlebox mode is + %% used assert the change_cipher_spec message + %% that the server should send next + case (maps:get(hello_retry, PS, false)) andalso + (maps:get(middlebox_comp_mode, SslOpts, true)) + of + true -> + {State, hello_retry_middlebox_assert}; + false -> + {State, wait_sh} + end catch {Ref, #alert{} = Alert} -> Alert @@ -864,11 +879,11 @@ do_negotiated({start_handshake, PSK0}, ssl_record:step_encryption_state_write(State3); false -> %% Read state is overwritten when handshake secrets are set. - %% Trial_decryption and early_data_limit must be set here! + %% Trial_decryption and early_data_accepted must be set here! update_current_read( ssl_record:step_encryption_state(State3), true, %% trial_decryption - false %% early data limit + false %% early_data_accepted ) end, @@ -902,7 +917,8 @@ do_negotiated({start_handshake, PSK0}, catch {Ref, #alert{} = Alert} -> Alert; - error:badarg -> + error:badarg=Reason:ST -> + ?SSL_LOG(debug, crypto_error, [{reason, Reason}, {stacktrace, ST}]), ?ALERT_REC(?ILLEGAL_PARAMETER, illegal_parameter_to_compute_key) end. @@ -1128,8 +1144,9 @@ do_wait_eoed(#end_of_early_data{}, State0) -> %% Upon receiving a message with type server_hello, implementations MUST %% first examine the Random value and, if it matches this value, process %% it as described in Section 4.1.4). -maybe_hello_retry_request(#server_hello{random = ?HELLO_RETRY_REQUEST_RANDOM} = ServerHello, State0) -> - {error, {State0, start, ServerHello}}; +maybe_hello_retry_request(#server_hello{random = ?HELLO_RETRY_REQUEST_RANDOM} = ServerHello, + #state{protocol_specific = PS} = State0) -> + {error, {State0#state{protocol_specific = PS#{hello_retry => true}}, start, ServerHello}}; maybe_hello_retry_request(_, _) -> ok. @@ -1180,11 +1197,10 @@ maybe_queue_change_cipher_spec(#state{flight_buffer = FlightBuffer0} = State0, l %% first ClientHello. %% @end maybe_prepend_change_cipher_spec(#state{ - ssl_options = - #{middlebox_comp_mode := true}, + session = #session{session_id = Id}, handshake_env = #handshake_env{ - change_cipher_spec_sent = false} = HSEnv} = State, Bin) -> + change_cipher_spec_sent = false} = HSEnv} = State, Bin) when Id =/= ?EMPTY_ID -> CCSBin = create_change_cipher_spec(State), {State#state{handshake_env = HSEnv#handshake_env{change_cipher_spec_sent = true}}, @@ -1195,11 +1211,10 @@ maybe_prepend_change_cipher_spec(State, Bin) -> %% @doc Appends a change_cipher_spec record to the input binary %% @end maybe_append_change_cipher_spec(#state{ - ssl_options = - #{middlebox_comp_mode := true}, + session = #session{session_id = Id}, handshake_env = #handshake_env{ - change_cipher_spec_sent = false} = HSEnv} = State, Bin) -> + change_cipher_spec_sent = false} = HSEnv} = State, Bin) when Id =/= ?EMPTY_ID -> CCSBin = create_change_cipher_spec(State), {State#state{handshake_env = HSEnv#handshake_env{change_cipher_spec_sent = true}}, @@ -1207,7 +1222,7 @@ maybe_append_change_cipher_spec(#state{ maybe_append_change_cipher_spec(State, Bin) -> {State, Bin}. -maybe_queue_cert_cert_cv(#state{client_certificate_requested = false} = State) -> +maybe_queue_cert_cert_cv(#state{client_certificate_status = not_requested} = State) -> {ok, State}; maybe_queue_cert_cert_cv(#state{connection_states = _ConnectionStates0, session = #session{session_id = _SessionId, @@ -1347,8 +1362,9 @@ maybe_send_certificate_request(#state{static_env = #static_env{protocol_cb = Con cert_db_ref = CertDbRef}} = State, #{verify := verify_peer, signature_algs := SignAlgs, - signature_algs_cert := SignAlgsCert}, _) -> - CertificateRequest = certificate_request(SignAlgs, SignAlgsCert, CertDbHandle, CertDbRef), + signature_algs_cert := SignAlgsCert, + certificate_authorities := CertAuthBool}, _) -> + CertificateRequest = certificate_request(SignAlgs, SignAlgsCert, CertDbHandle, CertDbRef, CertAuthBool), {Connection:queue_handshake(CertificateRequest, State), wait_cert}. maybe_send_certificate(State, PSK) when PSK =/= undefined -> @@ -1431,7 +1447,8 @@ create_change_cipher_spec(#state{ssl_options = #{log_level := LogLevel}}) -> process_certificate_request(#certificate_request_1_3{ extensions = Extensions}, #state{ssl_options = #{signature_algs := ClientSignAlgs}, - connection_env = #connection_env{cert_key_pairs = CertKeyPairs}, + connection_env = #connection_env{cert_key_alts = CertKeyAlts, + negotiated_version = Version}, static_env = #static_env{cert_db = CertDbHandle, cert_db_ref = CertDbRef}, session = Session0} = State) -> @@ -1439,12 +1456,13 @@ process_certificate_request(#certificate_request_1_3{ maps:get(signature_algs, Extensions, undefined)), ServerSignAlgsCert = get_signature_scheme_list( maps:get(signature_algs_cert, Extensions, undefined)), - CertAuths = get_certificate_authorites(maps:get(certificate_authorities, Extensions, undefined)), + CertAuths = get_certificate_authorities(maps:get(certificate_authorities, Extensions, undefined)), + CertKeyPairs = ssl_certificate:available_cert_key_pairs(CertKeyAlts, Version), Session = select_client_cert_key_pair(Session0, CertKeyPairs, - ServerSignAlgs, ServerSignAlgsCert, ClientSignAlgs, - CertDbHandle, CertDbRef, CertAuths), - {ok, {State#state{client_certificate_requested = true, session = Session}, wait_cert}}. + ServerSignAlgs, ServerSignAlgsCert, filter_tls13_algs(ClientSignAlgs), + CertDbHandle, CertDbRef, CertAuths, undefined), + {ok, {State#state{client_certificate_status = requested, session = Session}, wait_cert}}. process_certificate(#certificate_1_3{ certificate_request_context = <<>>, @@ -1649,19 +1667,16 @@ calculate_client_early_traffic_secret( PendingRead0 = ssl_record:pending_connection_state(ConnectionStates, read), PendingRead1 = maybe_store_early_data_secret(KeepSecrets, ClientEarlyTrafficSecret, PendingRead0), - PendingRead2 = update_connection_state(PendingRead1, undefined, undefined, + PendingRead = update_connection_state(PendingRead1, undefined, undefined, undefined, Key, IV, undefined), - %% Signal start of early data. This is to prevent handshake messages to be - %% counted in max_early_data_size. - PendingRead = PendingRead2#{count_early_data => true}, State0#state{connection_states = ConnectionStates#{pending_read => PendingRead}} end. -update_current_read(#state{connection_states = CS} = State, TrialDecryption, EarlyDataLimit) -> +update_current_read(#state{connection_states = CS} = State, TrialDecryption, EarlyDataExpected) -> Read0 = ssl_record:current_connection_state(CS, read), Read = Read0#{trial_decryption => TrialDecryption, - early_data_limit => EarlyDataLimit}, + early_data_accepted => EarlyDataExpected}, State#state{connection_states = CS#{current_read => Read}}. maybe_store_early_data_secret(true, EarlySecret, State) -> @@ -2064,7 +2079,7 @@ verify_signature_algorithm(#state{ static_env = #static_env{role = Role}, ssl_options = #{signature_algs := LocalSignAlgs}} = State0, #certificate_verify_1_3{algorithm = PeerSignAlg}) -> - case lists:member(PeerSignAlg, LocalSignAlgs) of + case lists:member(PeerSignAlg, filter_tls13_algs(LocalSignAlgs)) of true -> {ok, maybe_update_selected_sign_alg(State0, PeerSignAlg, Role)}; false -> @@ -2312,14 +2327,14 @@ check_cert_sign_algo(SignAlgo, SignHash, _, ClientSignAlgsCert) -> %% DSA keys are not supported by TLS 1.3 -select_sign_algo(dsa, _RSAKeySize, _PeerSignAlgs, _OwnSignAlgs, _Curve) -> +select_sign_algo(dsa, _RSAKeySize, _CertSignAlg, _OwnSignAlgs, _Curve) -> {error, ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_public_key)}; select_sign_algo(_, _RSAKeySize, [], _, _) -> {error, ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_signature_algorithm)}; select_sign_algo(_, _RSAKeySize, undefined, _OwnSignAlgs, _) -> {error, ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_public_key)}; -select_sign_algo(PublicKeyAlgo, RSAKeySize, [PeerSignAlg|PeerSignAlgs], OwnSignAlgs, Curve) -> - {_, S, _} = ssl_cipher:scheme_to_components(PeerSignAlg), +select_sign_algo(PublicKeyAlgo, RSAKeySize, [CertSignAlg|CertSignAlgs], OwnSignAlgs, Curve) -> + {_, S, _} = ssl_cipher:scheme_to_components(CertSignAlg), %% RSASSA-PKCS1-v1_5 and Legacy algorithms are not defined for use in signed %% TLS handshake messages: filter sha-1 and rsa_pkcs1. %% @@ -2333,36 +2348,36 @@ select_sign_algo(PublicKeyAlgo, RSAKeySize, [PeerSignAlg|PeerSignAlgs], OwnSignA orelse (PublicKeyAlgo =:= eddsa andalso S =:= eddsa) ) andalso - lists:member(PeerSignAlg, OwnSignAlgs) of + lists:member(CertSignAlg, OwnSignAlgs) of true -> validate_key_compatibility(PublicKeyAlgo, RSAKeySize, - [PeerSignAlg|PeerSignAlgs], OwnSignAlgs, Curve); + [CertSignAlg|CertSignAlgs], OwnSignAlgs, Curve); false -> - select_sign_algo(PublicKeyAlgo, RSAKeySize, PeerSignAlgs, OwnSignAlgs, Curve) + select_sign_algo(PublicKeyAlgo, RSAKeySize, CertSignAlgs, OwnSignAlgs, Curve) end. -validate_key_compatibility(PublicKeyAlgo, RSAKeySize, [PeerSignAlg|PeerSignAlgs], OwnSignAlgs, Curve) +validate_key_compatibility(PublicKeyAlgo, RSAKeySize, [CertSignAlg|CertSignAlgs], OwnSignAlgs, Curve) when PublicKeyAlgo =:= rsa orelse PublicKeyAlgo =:= rsa_pss_pss -> - {Hash, Sign, _} = ssl_cipher:scheme_to_components(PeerSignAlg), + {Hash, Sign, _} = ssl_cipher:scheme_to_components(CertSignAlg), case (Sign =:= rsa_pss_rsae orelse Sign =:= rsa_pss_pss) andalso is_rsa_key_compatible(RSAKeySize, Hash) of true -> - {ok, PeerSignAlg}; + {ok, CertSignAlg}; false -> - select_sign_algo(PublicKeyAlgo, RSAKeySize, PeerSignAlgs, OwnSignAlgs, Curve) + select_sign_algo(PublicKeyAlgo, RSAKeySize, CertSignAlgs, OwnSignAlgs, Curve) end; -validate_key_compatibility(PublicKeyAlgo, RSAKeySize, [PeerSignAlg|PeerSignAlgs], OwnSignAlgs, Curve) +validate_key_compatibility(PublicKeyAlgo, RSAKeySize, [CertSignAlg|CertSignAlgs], OwnSignAlgs, Curve) when PublicKeyAlgo =:= ecdsa -> - {_ , Sign, PeerCurve} = ssl_cipher:scheme_to_components(PeerSignAlg), + {_ , Sign, PeerCurve} = ssl_cipher:scheme_to_components(CertSignAlg), case Sign =:= ecdsa andalso Curve =:= PeerCurve of true -> - {ok, PeerSignAlg}; + {ok, CertSignAlg}; false -> - select_sign_algo(PublicKeyAlgo, RSAKeySize, PeerSignAlgs, OwnSignAlgs, Curve) + select_sign_algo(PublicKeyAlgo, RSAKeySize, CertSignAlgs, OwnSignAlgs, Curve) end; -validate_key_compatibility(_, _, [PeerSignAlg|_], _, _) -> - {ok, PeerSignAlg}. +validate_key_compatibility(_, _, [CertSignAlg|_], _, _) -> + {ok, CertSignAlg}. is_rsa_key_compatible(KeySize, Hash) -> HashSize = ssl_cipher:hash_size(Hash), @@ -2419,18 +2434,14 @@ get_certificate_params(Cert) -> SubjectPublicKeyAlgo = public_key_algo(SubjectPublicKeyAlgo0), {SubjectPublicKeyAlgo, SignAlgo, SignHash, RSAKeySize, Curve}. -oids_to_atoms(?'id-RSASSA-PSS', #'RSASSA-PSS-params'{maskGenAlgorithm = +oids_to_atoms(?'id-RSASSA-PSS', #'RSASSA-PSS-params'{maskGenAlgorithm = #'MaskGenAlgorithm'{algorithm = ?'id-mgf1', parameters = #'HashAlgorithm'{algorithm = HashOid}}}) -> Hash = public_key:pkix_hash_type(HashOid), {Hash, rsa_pss_pss}; oids_to_atoms(SignAlgo, _) -> - case public_key:pkix_sign_types(SignAlgo) of - {sha, Sign} -> - {sha1, Sign}; - {_,_} = Algs -> - Algs - end. + public_key:pkix_sign_types(SignAlgo). + %% Note: copied from ssl_handshake public_key_algo(?'id-RSASSA-PSS') -> rsa_pss_pss; @@ -2458,9 +2469,9 @@ get_signature_scheme_list(#signature_algorithms{ lists:filter(fun (E) -> is_atom(E) andalso E =/= unassigned end, ClientSignatureSchemes). -get_certificate_authorites(#certificate_authorities{authorities = Auths}) -> +get_certificate_authorities(#certificate_authorities{authorities = Auths}) -> Auths; -get_certificate_authorites(undefined) -> +get_certificate_authorities(undefined) -> []. get_supported_groups(undefined = Groups) -> @@ -2737,8 +2748,8 @@ maybe_send_early_data(#state{ %% Set 0-RTT traffic keys for sending early_data and EndOfEarlyData State3 = ssl_record:step_encryption_state_write(State2), {ok, encode_early_data(Cipher, State3)}; - {ok, {_, _, _, _MaxSize}} -> - {error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER, too_much_early_data)}; + {ok, {_, _, _, MaxSize}} -> + {error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER, {too_much_early_data, {max, MaxSize}})}; {error, Alert} -> {error, Alert} end; @@ -2886,7 +2897,7 @@ process_ticket(#{cipher_suite := CipherSuite, ticket = Ticket, extensions = Extensions } = NewSessionTicket, - TicketAge = erlang:system_time(seconds) - Timestamp, + TicketAge = erlang:system_time(millisecond) - Timestamp, ObfuscatedTicketAge = obfuscate_ticket_age(TicketAge, AgeAdd), Identity = #psk_identity{ identity = Ticket, @@ -2966,55 +2977,83 @@ supported_groups_from_extensions(Extensions) -> {ok, undefined} end. -select_server_cert_key_pair(_,[], _,_,_,_, {error, _} = Return) -> - Return; select_server_cert_key_pair(_,[], _,_,_,_, #session{}=Session) -> + %% Conformant Cert-Key pair with advertised signature algorithm is + %% selected. + {ok, Session}; +select_server_cert_key_pair(_,[], _,_,_,_, {fallback, #session{}=Session}) -> + %% Use fallback Cert-Key pair as no conformant pair to the advertised + %% signature algorithms was found. {ok, Session}; select_server_cert_key_pair(_,[], _,_,_,_, undefined) -> - {error, ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, unable_to_send_certificate_verifiable_by_client)}; + {error, ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, unable_to_supply_acceptable_cert)}; select_server_cert_key_pair(Session, [#{private_key := Key, certs := [Cert| _] = Certs} | Rest], ClientSignAlgs, ClientSignAlgsCert, CertAuths, - #state{static_env = #static_env{cert_db = CertDbHandle, - cert_db_ref = CertDbRef} = State}, - Default) -> + #state{static_env = #static_env{cert_db = CertDbHandle, + cert_db_ref = CertDbRef} = State}, + Default0) -> {_, SignAlgo, SignHash, _, _} = get_certificate_params(Cert), %% TODO: We do validate the signature algorithm and signature hash but we could also check %% if the signing cert has a key on a curve supported by the client for ECDSA/EDDSA certs case check_cert_sign_algo(SignAlgo, SignHash, ClientSignAlgs, ClientSignAlgsCert) of ok -> case ssl_certificate:handle_cert_auths(Certs, CertAuths, CertDbHandle, CertDbRef) of - {ok, EncodeChain} -> + {ok, EncodeChain} -> %% Chain fullfills certificate_authorities extension {ok, Session#session{own_certificates = EncodeChain, private_key = Key}}; {error, EncodeChain, not_in_auth_domain} -> + %% If this is the first chain to fulfill the signing requirement, use it as default, + %% if not later alternative also fulfills certificate_authorities extension Default = Session#session{own_certificates = EncodeChain, private_key = Key}, - select_server_cert_key_pair(Session, Rest, ClientSignAlgs, ClientSignAlgsCert, CertAuths, State, Default) + select_server_cert_key_pair(Session, Rest, ClientSignAlgs, ClientSignAlgsCert, + CertAuths, State, default_or_fallback(Default0, Default)) end; - Error -> - select_server_cert_key_pair(Session, Rest, ClientSignAlgs, ClientSignAlgsCert, CertAuths, State, - default_cert_key_pair_return(Default, Error)) + _ -> + %% If the server cannot produce a certificate chain that is signed only + %% via the indicated supported algorithms, then it SHOULD continue the + %% handshake by sending the client a certificate chain of its choice + case SignHash of + sha -> + %% According to "Server Certificate Selection - RFC 8446" + %% Never send cert using sha1 unless client allows it + select_server_cert_key_pair(Session, Rest, ClientSignAlgs, ClientSignAlgsCert, + CertAuths, State, Default0); + _ -> + %% If there does not exist a default or fallback from previous alternatives + %% use this alternative as fallback. + Fallback = {fallback, Session#session{own_certificates = Certs, private_key = Key}}, + select_server_cert_key_pair(Session, Rest, ClientSignAlgs, ClientSignAlgsCert, + CertAuths, State, + default_or_fallback(Default0, Fallback)) + end end. +default_or_fallback(undefined, DefaultOrFallback) -> + DefaultOrFallback; +default_or_fallback({fallback, _}, #session{} = Default) -> + Default; +default_or_fallback(Default, _) -> + Default. + select_client_cert_key_pair(Session0, [#{private_key := NoKey, certs := [[]] = NoCerts}], - _,_,_,_,_,_) -> + _,_,_,_,_,_, _) -> %% No certificate supplied : send empty certificate Session0#session{own_certificates = NoCerts, private_key = NoKey}; -select_client_cert_key_pair(Session0, CertKeyPairs, ServerSignAlgs, ServerSignAlgsCert, - ClientSignAlgs, CertDbHandle, CertDbRef, CertAuths) -> - select_client_cert_key_pair(Session0, CertKeyPairs, ServerSignAlgs, ServerSignAlgsCert, - ClientSignAlgs, CertDbHandle, CertDbRef, CertAuths, undefined). - -select_client_cert_key_pair(Session, [],_,_,_,_,_,_, undefined = Default) -> - %% No certificate compliant with supported algorithms : send empty certificate in state 'wait_finished' - Session#session{own_certificates = Default, - private_key = Default}; -select_client_cert_key_pair(_,[],_,_,_,_,_,_,#session{}=Session) -> - %% No certificate compliant with guide lines send default - Session; - +select_client_cert_key_pair(Session, [],_,_,_,_,_,_, undefined) -> + %% No certificate compliant with supported algorithms and + %% extensison : send empty certificate in state 'wait_finished' + Session#session{own_certificates = [[]], + private_key = #{}}; +select_client_cert_key_pair(_,[],_,_,_,_,_,_, #session{} = Plausible) -> + %% If we do not find an alternative chain with a cert signed in auth_domain, + %% but have a single cert without chain certs it might be verifiable by + %% a server that has the means to recreate the chain + Plausible; select_client_cert_key_pair(Session0, [#{private_key := Key, certs := [Cert| _] = Certs} | Rest], - ServerSignAlgs, ServerSignAlgsCert, ClientSignAlgs, CertDbHandle, CertDbRef, CertAuths, Default) -> + ServerSignAlgs, ServerSignAlgsCert, + ClientSignAlgs, CertDbHandle, CertDbRef, + CertAuths, Plausible0) -> {PublicKeyAlgo, SignAlgo, SignHash, MaybeRSAKeySize, Curve} = get_certificate_params(Cert), case select_sign_algo(PublicKeyAlgo, MaybeRSAKeySize, ServerSignAlgs, ClientSignAlgs, Curve) of {ok, SelectedSignAlg} -> @@ -3028,24 +3067,25 @@ select_client_cert_key_pair(Session0, [#{private_key := Key, certs := [Cert| _] private_key = Key }; {error, EncodedChain, not_in_auth_domain} -> - Session = Session0#session{sign_alg = SelectedSignAlg, - own_certificates = EncodedChain, - private_key = Key - }, - select_client_cert_key_pair(Session, Rest, ServerSignAlgs, ServerSignAlgsCert, - ClientSignAlgs, CertDbHandle, CertDbRef, CertAuths, - default_cert_key_pair_return(Default, Session)) + Plausible = plausible_missing_chain(EncodedChain, Plausible0, + SelectedSignAlg, Key, Session0), + select_client_cert_key_pair(Session0, Rest, ServerSignAlgs, ServerSignAlgsCert, + ClientSignAlgs, CertDbHandle, CertDbRef, CertAuths, + Plausible) end; _ -> select_client_cert_key_pair(Session0, Rest, ServerSignAlgs, ServerSignAlgsCert, ClientSignAlgs, - CertDbHandle, CertDbRef, CertAuths, Default) + CertDbHandle, CertDbRef, CertAuths, Plausible0) end; {error, _} -> select_client_cert_key_pair(Session0, Rest, ServerSignAlgsCert, ServerSignAlgsCert, ClientSignAlgs, - CertDbHandle, CertDbRef, CertAuths, Default) + CertDbHandle, CertDbRef, CertAuths, Plausible0) end. -default_cert_key_pair_return(undefined, Session) -> - Session; -default_cert_key_pair_return(Default, _) -> - Default. +plausible_missing_chain([_] = EncodedChain, undefined, SignAlg, Key, Session0) -> + Session0#session{sign_alg = SignAlg, + own_certificates = EncodedChain, + private_key = Key + }; +plausible_missing_chain(_,Plausible,_,_,_) -> + Plausible. diff --git a/lib/ssl/src/tls_handshake_1_3.hrl b/lib/ssl/src/tls_handshake_1_3.hrl index 0c525bbb89..5af35208b9 100644 --- a/lib/ssl/src/tls_handshake_1_3.hrl +++ b/lib/ssl/src/tls_handshake_1_3.hrl @@ -26,10 +26,10 @@ -ifndef(tls_handshake_1_3). -define(tls_handshake_1_3, true). -%% Common to TLS-1.3 and previous TLS versions -%% Some definitions may not exist in TLS-1.3 this is +%% Common to TLS-1.3 and previous TLS versions +%% Some definitions may not exist in TLS-1.3 this is %% handled elsewhere --include("tls_handshake.hrl"). +-include("tls_handshake.hrl"). %% New handshake types in TLS-1.3 RFC 8446 B.3 -define(NEW_SESSION_TICKET, 4). diff --git a/lib/ssl/src/tls_record.erl b/lib/ssl/src/tls_record.erl index 30341f1598..ff23b2a99b 100644 --- a/lib/ssl/src/tls_record.erl +++ b/lib/ssl/src/tls_record.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2021. All Rights Reserved. +%% Copyright Ericsson AB 2007-2022. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -202,7 +202,7 @@ encode_data(Data, Version, %%-------------------------------------------------------------------- -spec decode_cipher_text(tls_version(), #ssl_tls{}, ssl_record:connection_states(), boolean()) -> - {#ssl_tls{} | trial_decryption_failed, + {#ssl_tls{} | no_record, ssl_record:connection_states()}| #alert{}. %% %% Description: Decode cipher text @@ -498,10 +498,10 @@ initial_connection_state(ConnectionEnd, BeastMitigation, MaxEarlyDataSize) -> secure_renegotiation => undefined, client_verify_data => undefined, server_verify_data => undefined, - max_early_data_size => MaxEarlyDataSize, + pending_early_data_size => MaxEarlyDataSize, max_fragment_length => undefined, trial_decryption => false, - early_data_limit => false + early_data_expected => false }. %% Used by logging to recreate the received bytes diff --git a/lib/ssl/src/tls_record.hrl b/lib/ssl/src/tls_record.hrl index a483e5ba5b..e446e481a7 100644 --- a/lib/ssl/src/tls_record.hrl +++ b/lib/ssl/src/tls_record.hrl @@ -30,10 +30,11 @@ -include("ssl_record.hrl"). %% Common TLS and DTLS records and Constantes %% Used to handle tls_plain_text, tls_compressed and tls_cipher_text --record(ssl_tls, { - type, - version, - fragment - }). +-record(ssl_tls, { + type, + version, + fragment, + early_data = false % TLS-1.3 + }). -endif. % -ifdef(tls_record). diff --git a/lib/ssl/src/tls_record_1_3.erl b/lib/ssl/src/tls_record_1_3.erl index ae375e4da1..440d9e0998 100644 --- a/lib/ssl/src/tls_record_1_3.erl +++ b/lib/ssl/src/tls_record_1_3.erl @@ -21,12 +21,14 @@ -include("tls_record.hrl"). -include("tls_record_1_3.hrl"). +-include("tls_handshake_1_3.hrl"). -include("ssl_internal.hrl"). -include("ssl_alert.hrl"). -include("ssl_cipher.hrl"). %% Encoding --export([encode_handshake/2, encode_alert_record/2, +-export([encode_handshake/2, + encode_alert_record/2, encode_data/2]). -export([encode_plain_text/3]). @@ -43,7 +45,8 @@ % %% Description: Encodes a handshake message to send on the tls-1.3-socket. %%-------------------------------------------------------------------- -encode_handshake(Frag, #{current_write := #{max_fragment_length := MaxFragmentLength}} = +encode_handshake(Frag, #{current_write := + #{max_fragment_length := MaxFragmentLength}} = ConnectionStates) -> MaxLength = if is_integer(MaxFragmentLength) -> MaxFragmentLength; @@ -75,7 +78,8 @@ encode_alert_record(#alert{level = Level, description = Description}, %% %% Description: Encodes data to send on the ssl-socket. %%-------------------------------------------------------------------- -encode_data(Frag, #{current_write := #{max_fragment_length := MaxFragmentLength}} = +encode_data(Frag, #{current_write := + #{max_fragment_length := MaxFragmentLength}} = ConnectionStates) -> MaxLength = if is_integer(MaxFragmentLength) -> MaxFragmentLength; @@ -85,7 +89,8 @@ encode_data(Frag, #{current_write := #{max_fragment_length := MaxFragmentLength} Data = tls_record:split_iovec(Frag, MaxLength), encode_iolist(?APPLICATION_DATA, Data, ConnectionStates). -encode_plain_text(Type, Data0, #{current_write := Write0} = ConnectionStates) -> +encode_plain_text(Type, Data0, #{current_write := Write0} = + ConnectionStates) -> PadLen = 0, %% TODO where to specify PadLen? Data = inner_plaintext(Type, Data0, PadLen), CipherFragment = encode_plain_text(Data, Write0), @@ -107,12 +112,13 @@ encode_iolist(Type, Data, ConnectionStates0) -> %%-------------------------------------------------------------------- -spec decode_cipher_text(#ssl_tls{}, ssl_record:connection_states()) -> - {#ssl_tls{} | trial_decryption_failed, + {#ssl_tls{} | no_record, ssl_record:connection_states()}| #alert{}. %% -%% Description: Decode cipher text, use legacy type ssl_tls instead of tls_cipher_text -%% in decoding context so that we can reuse the code from earlier versions. -%%-------------------------------------------------------------------- +%% Description: Decode cipher text, use legacy type ssl_tls instead of +%% tls_cipher_text in decoding context so that we can reuse the code +%% from earlier versions. +%% -------------------------------------------------------------------- decode_cipher_text(#ssl_tls{type = ?OPAQUE_TYPE, version = ?LEGACY_VERSION, fragment = CipherFragment}, @@ -126,22 +132,25 @@ decode_cipher_text(#ssl_tls{type = ?OPAQUE_TYPE, cipher_type = ?AEAD, bulk_cipher_algorithm = BulkCipherAlgo}, - max_early_data_size := MaxEarlyDataSize0, + pending_early_data_size := PendingMaxEarlyDataSize0, trial_decryption := TrialDecryption, - early_data_limit := EarlyDataLimit + early_data_accepted := EarlyDataAccepted } = ReadState0} = ConnectionStates0) -> case decipher_aead(CipherFragment, BulkCipherAlgo, Key, Seq, IV, TagLen) of #alert{} when TrialDecryption =:= true andalso - MaxEarlyDataSize0 > 0 -> %% Trial decryption - trial_decrypt(ConnectionStates0, ReadState0, MaxEarlyDataSize0, - BulkCipherAlgo, CipherFragment); + EarlyDataAccepted =:= false andalso + PendingMaxEarlyDataSize0 > 0 -> %% Trial decryption + ignore_early_data(ConnectionStates0, ReadState0, + PendingMaxEarlyDataSize0, + BulkCipherAlgo, CipherFragment); #alert{} = Alert -> Alert; - PlainFragment0 when EarlyDataLimit =:= true andalso - MaxEarlyDataSize0 > 0 -> + PlainFragment0 when EarlyDataAccepted =:= true andalso + PendingMaxEarlyDataSize0 > 0 -> PlainFragment = remove_padding(PlainFragment0), - process_early_data(ConnectionStates0, ReadState0, MaxEarlyDataSize0, Seq, - BulkCipherAlgo, CipherFragment, PlainFragment); + process_early_data(ConnectionStates0, ReadState0, + PendingMaxEarlyDataSize0, Seq, + PlainFragment); PlainFragment0 -> PlainFragment = remove_padding(PlainFragment0), ConnectionStates = @@ -159,20 +168,20 @@ decode_cipher_text(#ssl_tls{type = ?OPAQUE_TYPE, %% the signature algorithm of the client's certificate.) decode_cipher_text(#ssl_tls{type = ?ALERT, version = ?LEGACY_VERSION, - fragment = <<2,47>>}, + fragment = <<?FATAL,?ILLEGAL_PARAMETER>>}, ConnectionStates0) -> {#ssl_tls{type = ?ALERT, version = {3,4}, %% Internally use real version - fragment = <<2,47>>}, ConnectionStates0}; + fragment = <<?FATAL,?ILLEGAL_PARAMETER>>}, ConnectionStates0}; %% TLS 1.3 server can receive a User Cancelled Alert when handshake is %% paused and then cancelled on the client side. decode_cipher_text(#ssl_tls{type = ?ALERT, version = ?LEGACY_VERSION, - fragment = <<2,90>>}, + fragment = <<?FATAL,?USER_CANCELED>>}, ConnectionStates0) -> {#ssl_tls{type = ?ALERT, version = {3,4}, %% Internally use real version - fragment = <<2,90>>}, ConnectionStates0}; + fragment = <<?FATAL,?USER_CANCELED>>}, ConnectionStates0}; %% RFC8446 - TLS 1.3 %% D.4. Middlebox Compatibility Mode %% - If not offering early data, the client sends a dummy @@ -208,48 +217,49 @@ decode_cipher_text(#ssl_tls{type = Type}, _) -> %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- -trial_decrypt(ConnectionStates0, ReadState0, MaxEarlyDataSize0, +ignore_early_data(ConnectionStates0, ReadState0, PendingMaxEarlyDataSize0, BulkCipherAlgo, CipherFragment) -> - MaxEarlyDataSize = update_max_early_date_size(MaxEarlyDataSize0, BulkCipherAlgo, CipherFragment), - ConnectionStates = - ConnectionStates0#{current_read => - ReadState0#{max_early_data_size => MaxEarlyDataSize}}, - if MaxEarlyDataSize < 0 -> - %% More early data is trial decrypted as the configured limit - ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC, decryption_failed); - true -> - {trial_decryption_failed, ConnectionStates} - end. - -process_early_data(ConnectionStates0, ReadState0, _MaxEarlyDataSize0, Seq, - _BulkCipherAlgo, _CipherFragment, PlainFragment) - when PlainFragment =:= <<5,0,0,0,22>> -> - %% struct { - %% opaque content[TLSPlaintext.length]; <<5,0,0,0>> - 5 = EndOfEarlyData - %% 0 = (uint24) size - %% ContentType type; <<22>> - Handshake - %% uint8 zeros[length_of_padding]; <<>> - no padding - %% } TLSInnerPlaintext; - %% EndOfEarlyData should not be counted into early data + PendingMaxEarlyDataSize = + approximate_pending_early_data_size(PendingMaxEarlyDataSize0, + BulkCipherAlgo, CipherFragment), ConnectionStates = - ConnectionStates0#{current_read => - ReadState0#{sequence_number => Seq + 1}}, - {decode_inner_plaintext(PlainFragment), ConnectionStates}; -process_early_data(ConnectionStates0, ReadState0, MaxEarlyDataSize0, Seq, - BulkCipherAlgo, CipherFragment, PlainFragment) -> + ConnectionStates0#{current_read => + ReadState0#{pending_early_data_size => PendingMaxEarlyDataSize}}, + if PendingMaxEarlyDataSize < 0 -> + %% More early data is trial decrypted as the configured limit + ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC, {decryption_failed, + {max_early_data_threshold_exceeded, + PendingMaxEarlyDataSize}}); + true -> + {no_record, ConnectionStates} + end. +process_early_data(ConnectionStates0, ReadState0, PendingMaxEarlyDataSize0, Seq, + PlainFragment) -> %% First packet is deciphered anyway so we must check if more early data is received %% than the configured limit (max_early_data_size). - MaxEarlyDataSize = - update_max_early_date_size(MaxEarlyDataSize0, BulkCipherAlgo, CipherFragment), - if MaxEarlyDataSize < 0 -> - %% Too much early data received, send alert unexpected_message - ?ALERT_REC(?FATAL, ?UNEXPECTED_MESSAGE, too_much_early_data); - true -> + Record = decode_inner_plaintext(PlainFragment), + case {Record#ssl_tls.type, remove_padding(Record#ssl_tls.fragment)} of + {?HANDSHAKE, <<?END_OF_EARLY_DATA>>} -> ConnectionStates = ConnectionStates0#{current_read => - ReadState0#{sequence_number => Seq + 1, - max_early_data_size => MaxEarlyDataSize}}, - {decode_inner_plaintext(PlainFragment), ConnectionStates} + ReadState0#{sequence_number => Seq + 1}}, + {Record, ConnectionStates}; + {?APPLICATION_DATA, Data} -> + PendingMaxEarlyDataSize = + pending_early_data_size(PendingMaxEarlyDataSize0, Data), + if PendingMaxEarlyDataSize < 0 -> + %% Too much early data received, send alert unexpected_message + ?ALERT_REC(?FATAL, ?UNEXPECTED_MESSAGE, + {too_much_early_data, + {max_early_data_threshold_exceeded, + PendingMaxEarlyDataSize}}); + true -> + ConnectionStates = + ConnectionStates0#{current_read => + ReadState0#{sequence_number => Seq + 1, + pending_early_data_size => PendingMaxEarlyDataSize}}, + {Record#ssl_tls{early_data = true}, ConnectionStates} + end end. inner_plaintext(Type, Data, Length) -> @@ -276,7 +286,8 @@ encode_plain_text(#inner_plaintext{ }) -> PlainText = [Data, Type, Zeros], Encoded = cipher_aead(PlainText, BulkCipherAlgo, Key, Seq, IV, TagLen), - #tls_cipher_text{opaque_type = 23, %% 23 (application_data) for outward compatibility + %% 23 (application_data) for outward compatibility + #tls_cipher_text{opaque_type = ?OPAQUE_TYPE, legacy_version = {3,3}, encoded_record = Encoded}; encode_plain_text(#inner_plaintext{ @@ -320,7 +331,8 @@ cipher_aead(Fragment, BulkCipherAlgo, Key, Seq, IV, TagLen) -> encode_tls_cipher_text(#tls_cipher_text{opaque_type = Type, legacy_version = {MajVer, MinVer}, - encoded_record = Encoded}, #{sequence_number := Seq} = Write) -> + encoded_record = Encoded}, + #{sequence_number := Seq} = Write) -> Length = erlang:iolist_size(Encoded), {[<<?BYTE(Type), ?BYTE(MajVer), ?BYTE(MinVer), ?UINT16(Length)>>, Encoded], Write#{sequence_number => Seq +1}}. @@ -333,11 +345,14 @@ decipher_aead(CipherFragment, BulkCipherAlgo, Key, Seq, IV, TagLen) -> case ssl_cipher:aead_decrypt(BulkCipherAlgo, Key, Nonce, CipherText, CipherTag, AAD) of Content when is_binary(Content) -> Content; - _ -> + Reason -> + ?SSL_LOG(debug, decrypt_error, [{reason,Reason}, + {stacktrace, process_info(self(), current_stacktrace)}]), ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC, decryption_failed) end catch - _:_ -> + _:Reason2:ST -> + ?SSL_LOG(debug, decrypt_error, [{reason,Reason2}, {stacktrace, ST}]), ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC, decryption_failed) end. @@ -379,19 +394,19 @@ remove_padding(InnerPlainText) -> InnerPlainText end. -update_max_early_date_size(MaxEarlyDataSize, BulkCipherAlgo, CipherFragment) -> - %% CipherFragment is the binary encoded form of a TLSInnerPlaintext: - %% - %% struct { - %% opaque content[TLSPlaintext.length]; - %% ContentType type; - %% uint8 zeros[length_of_padding]; - %% } TLSInnerPlaintext; - %% - TypeLen = 1, - PaddingLen = 0, %% TODO Update formula when padding is implemented! - MaxEarlyDataSize - (byte_size(CipherFragment) - TypeLen - PaddingLen - - bca_tag_len(BulkCipherAlgo)). +pending_early_data_size(PendingMaxEarlyDataSize, PlainFragment) -> + %% The maximum amount of 0-RTT data that the client is allowed to + %% send when using this ticket, in bytes. Only Application Data + %% payload (i.e., plaintext but not padding or the inner content + %% type byte) is counted. + PendingMaxEarlyDataSize - (byte_size(PlainFragment)). + +approximate_pending_early_data_size(PendingMaxEarlyDataSize, + BulkCipherAlgo, CipherFragment) -> + %% We can not know how much is padding! + InnerContTypeLen = 1, + PendingMaxEarlyDataSize - (byte_size(CipherFragment) - + InnerContTypeLen - bca_tag_len(BulkCipherAlgo)). bca_tag_len(?AES_CCM_8) -> 8; diff --git a/lib/ssl/src/tls_record_1_3.hrl b/lib/ssl/src/tls_record_1_3.hrl index dbd1aafae5..c6214a5de3 100644 --- a/lib/ssl/src/tls_record_1_3.hrl +++ b/lib/ssl/src/tls_record_1_3.hrl @@ -27,6 +27,10 @@ -ifndef(tls_record_1_3). -define(tls_record_1_3, true). +%% Common to TLS-1.3 and previous TLS versions +%% Some definitions may not exist in TLS-1.3 this is +%% handled elsewhere +-include("tls_record.hrl"). %% enum { %% invalid(0), %% %% defined in ssl_record.hrl diff --git a/lib/ssl/src/tls_sender.erl b/lib/ssl/src/tls_sender.erl index 68e4eeed4e..b53a801b90 100644 --- a/lib/ssl/src/tls_sender.erl +++ b/lib/ssl/src/tls_sender.erl @@ -68,7 +68,8 @@ key_update_at, %% TLS 1.3 bytes_sent, %% TLS 1.3 dist_handle, - log_level + log_level, + hibernate_after }). -record(data, @@ -230,7 +231,8 @@ init({call, From}, {Pid, #{current_write := WriteState, negotiated_version := Version, renegotiate_at := RenegotiateAt, key_update_at := KeyUpdateAt, - log_level := LogLevel}}, + log_level := LogLevel, + hibernate_after := HibernateAfter}}, #data{connection_states = ConnectionStates, static = Static0} = StateData0) -> StateData = StateData0#data{connection_states = ConnectionStates#{current_write => WriteState}, @@ -245,10 +247,11 @@ init({call, From}, {Pid, #{current_write := WriteState, renegotiate_at = RenegotiateAt, key_update_at = KeyUpdateAt, bytes_sent = 0, - log_level = LogLevel}}, + log_level = LogLevel, + hibernate_after = HibernateAfter}}, {next_state, handshake, StateData, [{reply, From, ok}]}; init(info = Type, Msg, StateData) -> - handle_common(Type, Msg, StateData); + handle_common(?FUNCTION_NAME, Type, Msg, StateData); init(_, _, _) -> %% Just in case anything else sneaks through {keep_state_and_data, [postpone]}. @@ -281,14 +284,14 @@ connection({call, From}, downgrade, #data{connection_states = #{current_write := Write}} = StateData) -> {next_state, death_row, StateData, [{reply,From, {ok, Write}}]}; connection({call, From}, {set_opts, Opts}, StateData) -> - handle_set_opts(From, Opts, StateData); + handle_set_opts(?FUNCTION_NAME, From, Opts, StateData); connection({call, From}, dist_get_tls_socket, #data{static = #static{transport_cb = Transport, socket = Socket, connection_pid = Pid, trackers = Trackers}} = StateData) -> TLSSocket = tls_gen_connection:socket([Pid, self()], Transport, Socket, Trackers), - {next_state, ?FUNCTION_NAME, StateData, [{reply, From, {ok, TLSSocket}}]}; + hibernate_after(?FUNCTION_NAME, StateData, [{reply, From, {ok, TLSSocket}}]); connection({call, From}, {dist_handshake_complete, _Node, DHandle}, #data{static = #static{connection_pid = Pid} = Static} = StateData) -> false = erlang:dist_ctrl_set_opt(DHandle, get_size, true), @@ -296,15 +299,20 @@ connection({call, From}, {dist_handshake_complete, _Node, DHandle}, ok = ssl_gen_statem:dist_handshake_complete(Pid, DHandle), %% From now on we execute on normal priority process_flag(priority, normal), - {keep_state, StateData#data{static = Static#static{dist_handle = DHandle}}, - [{reply,From,ok}| - case dist_data(DHandle) of - [] -> - []; - Data -> - [{next_event, internal, - {application_packets,{self(),undefined},Data}}] - end]}; + + case dist_data(DHandle) of + [] -> + hibernate_after(?FUNCTION_NAME, + StateData#data{ + static = Static#static{dist_handle = DHandle}}, + [{reply,From,ok}]); + Data -> + {keep_state, + StateData#data{static = Static#static{dist_handle = DHandle}}, + [{reply,From,ok}, + {next_event, internal, + {application_packets, {self(),undefined}, Data}}]} + end; connection(internal, {application_packets, From, Data}, StateData) -> send_application_data(Data, From, ?FUNCTION_NAME, StateData); connection(internal, {post_handshake_data, From, HSData}, StateData) -> @@ -314,20 +322,22 @@ connection(cast, #alert{} = Alert, StateData0) -> {next_state, ?FUNCTION_NAME, StateData}; connection(cast, {new_write, WritesState, Version}, #data{connection_states = ConnectionStates, static = Static} = StateData) -> - {next_state, connection, - StateData#data{connection_states = - ConnectionStates#{current_write => WritesState}, - static = Static#static{negotiated_version = Version}}}; + hibernate_after(connection, + StateData#data{connection_states = + ConnectionStates#{current_write => WritesState}, + static = + Static#static{negotiated_version = Version}}, []); %% -connection(info, dist_data, #data{static = #static{dist_handle = DHandle}}) -> - {keep_state_and_data, +connection(info, dist_data, + #data{static = #static{dist_handle = DHandle}} = StateData) -> case dist_data(DHandle) of [] -> - []; + hibernate_after(?FUNCTION_NAME, StateData, []); Data -> - [{next_event, internal, - {application_packets,{self(),undefined},Data}}] - end}; + {keep_state_and_data, + [{next_event, internal, + {application_packets, {self(),undefined}, Data}}]} + end; connection(info, tick, StateData) -> consume_ticks(), Data = [<<0:32>>], % encode_packet(4, <<>>) @@ -342,8 +352,10 @@ connection(info, {send, From, Ref, Data}, _StateData) -> {keep_state_and_data, [{next_event, {call, {self(), undefined}}, {application_data, erlang:iolist_to_iovec(Data)}}]}; +connection(timeout, hibernate, _StateData) -> + {keep_state_and_data, [hibernate]}; connection(Type, Msg, StateData) -> - handle_common(Type, Msg, StateData). + handle_common(?FUNCTION_NAME, Type, Msg, StateData). %%-------------------------------------------------------------------- -spec handshake(gen_statem:event_type(), @@ -352,7 +364,7 @@ connection(Type, Msg, StateData) -> gen_statem:event_handler_result(atom()). %%-------------------------------------------------------------------- handshake({call, From}, {set_opts, Opts}, StateData) -> - handle_set_opts(From, Opts, StateData); + handle_set_opts(?FUNCTION_NAME, From, Opts, StateData); handshake({call, _}, _, _) -> %% Postpone all calls to the connection state {keep_state_and_data, [postpone]}; @@ -376,7 +388,7 @@ handshake(info, {send, _, _, _}, _) -> %% Testing only, OTP distribution test suites... {keep_state_and_data, [postpone]}; handshake(Type, Msg, StateData) -> - handle_common(Type, Msg, StateData). + handle_common(?FUNCTION_NAME, Type, Msg, StateData). %%-------------------------------------------------------------------- -spec death_row(gen_statem:event_type(), @@ -384,14 +396,18 @@ handshake(Type, Msg, StateData) -> StateData :: term()) -> gen_statem:event_handler_result(atom()). %%-------------------------------------------------------------------- -death_row(state_timeout, Reason, _State) -> +death_row(state_timeout, Reason, _StateData) -> {stop, {shutdown, Reason}}; -death_row(info = Type, Msg, State) -> - handle_common(Type, Msg, State); -death_row(_Type, _Msg, _State) -> +death_row(info = Type, Msg, StateData) -> + handle_common(?FUNCTION_NAME, Type, Msg, StateData); +death_row(_Type, _Msg, _StateData) -> %% Waste all other events keep_state_and_data. +%% State entry function that starts shutdown state_timeout +death_row_shutdown(Reason, StateData) -> + {next_state, death_row, StateData, [{state_timeout, 5000, Reason}]}. + %%-------------------------------------------------------------------- -spec terminate(Reason :: term(), State :: term(), Data :: term()) -> any(). @@ -413,36 +429,47 @@ code_change(_OldVsn, State, Data, _Extra) -> %%%=================================================================== %%% Internal functions %%%=================================================================== - -handle_set_opts(From, Opts, #data{static = #static{socket_options = SockOpts} = Static} = StateData) -> - {keep_state, StateData#data{static = Static#static{socket_options = set_opts(SockOpts, Opts)}}, - [{reply, From, ok}]}. - -handle_common({call, From}, {set_opts, Opts}, +handle_set_opts(StateName, From, Opts, + #data{static = #static{socket_options = SockOpts} = Static} + = StateData) -> + hibernate_after(StateName, + StateData#data{ + static = + Static#static{ + socket_options = set_opts(SockOpts, Opts)}}, + [{reply, From, ok}]). + +handle_common(StateName, {call, From}, {set_opts, Opts}, #data{static = #static{socket_options = SockOpts} = Static} = StateData) -> - {keep_state, StateData#data{static = Static#static{socket_options = set_opts(SockOpts, Opts)}}, - [{reply, From, ok}]}; -handle_common(info, {'EXIT', _Sup, shutdown}, + hibernate_after(StateName, + StateData#data{ + static = + Static#static{ + socket_options = set_opts(SockOpts, Opts)}}, + [{reply, From, ok}]); +handle_common(_StateName, info, {'EXIT', _Sup, shutdown = Reason}, #data{static = #static{erl_dist = true}} = StateData) -> %% When the connection is on its way down operations - %% begin to fail. We wait for 5 seconds to receive - %% possible exit signals for one of our links to the other - %% involved distribution parties, in which case we want to use - %% their exit reason for the connection teardown. - {next_state, death_row, StateData, [{state_timeout, 5000, shutdown}]}; -handle_common(info, {'EXIT', _Dist, Reason}, + %% begin to fail. We wait to receive possible exit signals + %% for one of our links to the other involved distribution parties, + %% in which case we want to use their exit reason + %% for the connection teardown. + death_row_shutdown(Reason, StateData); +handle_common(_StateName, info, {'EXIT', _Dist, Reason}, #data{static = #static{erl_dist = true}} = StateData) -> {stop, {shutdown, Reason}, StateData}; -handle_common(info, {'EXIT', _Sup, shutdown}, StateData) -> +handle_common(_StateName, info, {'EXIT', _Sup, shutdown}, StateData) -> {stop, shutdown, StateData}; -handle_common(info, Msg, #data{static = #static{log_level = Level}}) -> - ssl_logger:log(info, Level, #{event => "TLS sender received unexpected info", +handle_common(StateName, info, Msg, + #data{static = #static{log_level = Level}} = StateData) -> + ssl_logger:log(info, Level, #{event => "TLS sender received unexpected info", reason => [{message, Msg}]}, ?LOCATION), - keep_state_and_data; -handle_common(Type, Msg, #data{static = #static{log_level = Level}}) -> - ssl_logger:log(error, Level, #{event => "TLS sender received unexpected event", + hibernate_after(StateName, StateData, []); +handle_common(StateName, Type, Msg, + #data{static = #static{log_level = Level}} = StateData) -> + ssl_logger:log(error, Level, #{event => "TLS sender received unexpected event", reason => [{type, Type}, {message, Msg}]}, ?LOCATION), - keep_state_and_data. + hibernate_after(StateName, StateData, []). send_tls_alert(#alert{} = Alert, #data{static = #static{negotiated_version = Version, @@ -471,7 +498,6 @@ send_application_data(Data, From, StateName, key_update -> KeyUpdate = tls_handshake_1_3:key_update(update_requested), {keep_state_and_data, [{next_event, internal, {post_handshake_data, From, KeyUpdate}}, - {next_event, internal, {key_update, From}}, {next_event, internal, {application_packets, From, Data}}]}; renegotiate -> tls_dtls_connection:internal_renegotiation(Pid, ConnectionStates0), @@ -491,15 +517,15 @@ send_application_data(Data, From, StateName, ok when DistHandle =/= undefined -> ssl_logger:debug(LogLevel, outbound, 'record', Msgs), StateData1 = update_bytes_sent(Version, StateData, Data), - {next_state, StateName, StateData1, []}; + hibernate_after(StateName, StateData1, []); Reason when DistHandle =/= undefined -> - {next_state, death_row, StateData, [{state_timeout, 5000, Reason}]}; + death_row_shutdown(Reason, StateData); ok -> ssl_logger:debug(LogLevel, outbound, 'record', Msgs), StateData1 = update_bytes_sent(Version, StateData, Data), - {next_state, StateName, StateData1, [{reply, From, ok}]}; + hibernate_after(StateName, StateData1, [{reply, From, ok}]); Result -> - {next_state, StateName, StateData, [{reply, From, Result}]} + hibernate_after(StateName, StateData, [{reply, From, Result}]) end end. @@ -522,7 +548,7 @@ send_post_handshake_data(Handshake, From, StateName, StateData = maybe_update_cipher_key(StateData1, Handshake), {next_state, StateName, StateData, []}; Reason when DistHandle =/= undefined -> - {next_state, death_row, StateData1, [{state_timeout, 5000, Reason}]}; + death_row_shutdown(Reason, StateData1); ok -> ssl_logger:debug(LogLevel, outbound, 'record', Encoded), StateData = maybe_update_cipher_key(StateData1, Handshake), @@ -679,3 +705,10 @@ consume_ticks() -> after 0 -> ok end. + +hibernate_after(connection = StateName, + #data{static=#static{hibernate_after = HibernateAfter}} = State, + Actions) -> + {next_state, StateName, State, [{timeout, HibernateAfter, hibernate} | Actions]}; +hibernate_after(StateName, State, Actions) -> + {next_state, StateName, State, Actions}. diff --git a/lib/ssl/src/tls_server_session_ticket.erl b/lib/ssl/src/tls_server_session_ticket.erl index b625cbcd52..f6b91404fb 100644 --- a/lib/ssl/src/tls_server_session_ticket.erl +++ b/lib/ssl/src/tls_server_session_ticket.erl @@ -181,7 +181,7 @@ inital_state([stateful, Lifetime, TicketStoreSize, MaxEarlyDataSize|_]) -> }. ticket_age_add() -> - MaxTicketAge = 7 * 24 * 3600, + MaxTicketAge = 7 * 24 * 3600 * 1000, IntMax = round(math:pow(2,32)) - 1, MaxAgeAdd = IntMax - MaxTicketAge, <<?UINT32(I)>> = crypto:strong_rand_bytes(4), @@ -385,10 +385,10 @@ stateless_living_ticket(0, _, _, _, _) -> stateless_living_ticket(ObfAge, TicketAgeAdd, Lifetime, Timestamp, Window) -> ReportedAge = ObfAge - TicketAgeAdd, RealAge = erlang:system_time(second) - Timestamp, - (ReportedAge =< Lifetime) + (ReportedAge =< Lifetime * 1000) andalso (RealAge =< Lifetime) andalso (in_window(RealAge, Window)). - + in_window(_, undefined) -> true; in_window(Age, Window) when is_integer(Window) -> diff --git a/lib/ssl/src/tls_socket.erl b/lib/ssl/src/tls_socket.erl index 80073952fc..779043f1de 100644 --- a/lib/ssl/src/tls_socket.erl +++ b/lib/ssl/src/tls_socket.erl @@ -391,10 +391,10 @@ code_change(_OldVsn, State, _Extra) -> call(Pid, Msg) -> gen_server:call(Pid, Msg, infinity). -start_tls_server_connection(SslOpts, ConnectionCb, Transport, Port, Socket, EmOpts, Trackers, CbInfo) -> +start_tls_server_connection(#{sender_spawn_opts := SenderOpts} = SslOpts, ConnectionCb, Transport, Port, Socket, EmOpts, Trackers, CbInfo) -> try {ok, DynSup} = tls_connection_sup:start_child([]), - {ok, Sender} = tls_dyn_connection_sup:start_child(DynSup, sender, []), + {ok, Sender} = tls_dyn_connection_sup:start_child(DynSup, sender, [[{spawn_opt, SenderOpts}]]), ConnArgs = [server, Sender, "localhost", Port, Socket, {SslOpts, emulated_socket_options(EmOpts, #socket_options{}), Trackers}, self(), CbInfo], {ok, Pid} = tls_dyn_connection_sup:start_child(DynSup, receiver, ConnArgs), diff --git a/lib/ssl/src/tls_v1.erl b/lib/ssl/src/tls_v1.erl index 75d994f18c..dd891967bc 100644 --- a/lib/ssl/src/tls_v1.erl +++ b/lib/ssl/src/tls_v1.erl @@ -869,25 +869,47 @@ signature_algs({3, 3}, HashSigns) -> default_signature_algs([{3, 4} = Version]) -> default_signature_schemes(Version) ++ legacy_signature_schemes(Version); default_signature_algs([{3, 4}, {3,3} | _]) -> - default_signature_schemes({3,4}) ++ default_signature_algs([{3,3}]); + default_signature_schemes({3,4}) ++ default_pre_1_3_signature_algs_only(); default_signature_algs([{3, 3} = Version |_]) -> - Default = [%% SHA2 - {sha512, ecdsa}, - {sha512, rsa}, - {sha384, ecdsa}, - {sha384, rsa}, - {sha256, ecdsa}, - {sha256, rsa}, - {sha224, ecdsa}, - {sha224, rsa}, - %% SHA - {sha, ecdsa}, - {sha, rsa}, - {sha, dsa}], + Default = [%% SHA2 ++ PSS + {sha512, ecdsa}, + rsa_pss_pss_sha512, + rsa_pss_rsae_sha512, + {sha512, rsa}, + {sha384, ecdsa}, + rsa_pss_pss_sha384, + rsa_pss_rsae_sha384, + {sha384, rsa}, + {sha256, ecdsa}, + rsa_pss_pss_sha256, + rsa_pss_rsae_sha256, + {sha256, rsa}, + {sha224, ecdsa}, + {sha224, rsa}, + %% SHA + {sha, ecdsa}, + {sha, rsa}, + {sha, dsa}], signature_algs(Version, Default); default_signature_algs(_) -> undefined. +default_pre_1_3_signature_algs_only() -> + Default = [%% SHA2 + {sha512, ecdsa}, + {sha512, rsa}, + {sha384, ecdsa}, + {sha384, rsa}, + {sha256, ecdsa}, + {sha256, rsa}, + {sha224, ecdsa}, + {sha224, rsa}, + %% SHA + {sha, ecdsa}, + {sha, rsa}, + {sha, dsa}], + signature_algs({3,3}, Default). + signature_schemes(Version, [_|_] =SignatureSchemes) when is_tuple(Version) andalso Version >= {3, 3} -> @@ -898,7 +920,7 @@ signature_schemes(Version, [_|_] =SignatureSchemes) when is_tuple(Version) RSAPSSSupported = lists:member(rsa_pkcs1_pss_padding, proplists:get_value(rsa_opts, CryptoSupports)), Fun = fun (Scheme, Acc) when is_atom(Scheme) -> - {Hash0, Sign0, Curve} = + {Hash, Sign0, Curve} = ssl_cipher:scheme_to_components(Scheme), Sign = case Sign0 of rsa_pkcs1 -> @@ -909,11 +931,6 @@ signature_schemes(Version, [_|_] =SignatureSchemes) when is_tuple(Version) rsa; S -> S end, - Hash = case Hash0 of - sha1 -> - sha; - H -> H - end, case proplists:get_bool(Sign, PubKeys) andalso (proplists:get_bool(Hash, Hashes) @@ -957,7 +974,8 @@ signature_schemes(_, _) -> []. default_signature_schemes(Version) -> - Default = [ + Default = [eddsa_ed25519, + eddsa_ed448, ecdsa_secp521r1_sha512, ecdsa_secp384r1_sha384, ecdsa_secp256r1_sha256, @@ -966,9 +984,7 @@ default_signature_schemes(Version) -> rsa_pss_pss_sha256, rsa_pss_rsae_sha512, rsa_pss_rsae_sha384, - rsa_pss_rsae_sha256, - eddsa_ed25519, - eddsa_ed448 + rsa_pss_rsae_sha256 ], signature_schemes(Version, Default). |