Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
home:Ledest:erlang:24
erlang
4151-ssl-Convey-alert-information-to-passive-so...
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File 4151-ssl-Convey-alert-information-to-passive-socket-opera.patch of Package erlang
From f9013fad5a1121535e882be9a3d4713637110ec7 Mon Sep 17 00:00:00 2001 From: Ingela Anderton Andin <ingela@erlang.org> Date: Fri, 13 Sep 2024 14:22:06 +0200 Subject: [PATCH] ssl: Convey alert information to passive socket operations recv and setopts If a TLS-1.3 server fails client certification the alert might arrive in the connection state and even after data has been sent. Make sure the alert information will be available in error reason returned from passive socket API functions recv and setopt. Backport of 25f3c524809b6f2909d925205df2dc9532464c14 --- lib/ssl/src/ssl_connection.hrl | 3 +- lib/ssl/src/ssl_gen_statem.erl | 62 ++++++++++++++---- lib/ssl/src/tls_gen_connection.erl | 7 ++ lib/ssl/test/tls_1_3_version_SUITE.erl | 90 +++++++++++++++++++++++++- 4 files changed, 146 insertions(+), 16 deletions(-) diff --git a/lib/ssl/src/ssl_connection.hrl b/lib/ssl/src/ssl_connection.hrl index c8295f339f..5efdccfdfd 100644 --- a/lib/ssl/src/ssl_connection.hrl +++ b/lib/ssl/src/ssl_connection.hrl @@ -31,6 +31,7 @@ -include("ssl_handshake.hrl"). -include("ssl_srp.hrl"). -include("ssl_cipher.hrl"). +-include("ssl_alert.hrl"). -include_lib("public_key/include/public_key.hrl"). -record(static_env, { @@ -96,7 +97,7 @@ user_application :: {Monitor::reference(), User::pid()}, downgrade :: {NewController::pid(), From::gen_statem:from()} | 'undefined', socket_terminated = false ::boolean(), - socket_tls_closed = false ::boolean(), + socket_tls_closed = false ::boolean() | #alert{}, negotiated_version :: ssl_record:ssl_version() | 'undefined', erl_dist_handle = undefined :: erlang:dist_handle() | 'undefined', cert_key_alts = undefined :: #{eddsa => list(), diff --git a/lib/ssl/src/ssl_gen_statem.erl b/lib/ssl/src/ssl_gen_statem.erl index eed0025ad7..f795e7c24c 100644 --- a/lib/ssl/src/ssl_gen_statem.erl +++ b/lib/ssl/src/ssl_gen_statem.erl @@ -584,11 +584,10 @@ config_error(_Type, _Event, _State) -> gen_statem:state_function_result(). %%-------------------------------------------------------------------- connection({call, RecvFrom}, {recv, N, Timeout}, - #state{static_env = #static_env{protocol_cb = Connection}, - socket_options = + #state{socket_options = #socket_options{active = false}} = State0) -> passive_receive(State0#state{bytes_to_read = N, - start_or_recv_from = RecvFrom}, ?FUNCTION_NAME, Connection, + start_or_recv_from = RecvFrom}, ?FUNCTION_NAME, [{{timeout, recv}, Timeout, timeout}]); connection({call, From}, peer_certificate, #state{session = #session{peer_certificate = Cert}} = State) -> @@ -613,6 +612,18 @@ connection({call, From}, negotiated_protocol, negotiated_protocol = undefined}} = State) -> hibernate_after(?FUNCTION_NAME, State, [{reply, From, {ok, SelectedProtocol}}]); +connection({call, From}, + {close,{_NewController, _Timeout}}, + #state{static_env = #static_env{role = Role, + socket = Socket, + trackers = Trackers, + transport_cb = Transport, + protocol_cb = Connection}, + connection_env = #connection_env{socket_tls_closed = #alert{} = Alert} + } = State) -> + Pids = Connection:pids(State), + alert_user(Pids, Transport, Trackers, Socket, From, Alert, Role, connection, Connection), + {stop, {shutdown, normal}, State}; connection({call, From}, {close,{NewController, Timeout}}, #state{connection_states = ConnectionStates, @@ -663,9 +674,8 @@ connection(cast, {dist_handshake_complete, DHandle}, Connection:next_event(connection, Record, State); connection(info, Msg, #state{static_env = #static_env{protocol_cb = Connection}} = State) -> Connection:handle_info(Msg, ?FUNCTION_NAME, State); -connection(internal, {recv, RecvFrom}, #state{start_or_recv_from = RecvFrom, - static_env = #static_env{protocol_cb = Connection}} = State) -> - passive_receive(State, ?FUNCTION_NAME, Connection, []); +connection(internal, {recv, RecvFrom}, #state{start_or_recv_from = RecvFrom} = State) -> + passive_receive(State, ?FUNCTION_NAME, []); connection(Type, Msg, State) -> handle_common_event(Type, Msg, ?FUNCTION_NAME, State). @@ -844,7 +854,7 @@ handle_info({ErrorTag, Socket, econnaborted}, StateName, maybe_invalidate_session(Version, Type, Role, Host, Port, Session), Pids = Connection:pids(State), - alert_user(Pids, Transport, Trackers,Socket, + alert_user(Pids, Transport, Trackers, Socket, StartFrom, ?ALERT_REC(?FATAL, ?CLOSE_NOTIFY), Role, StateName, Connection), {stop, {shutdown, normal}, State}; @@ -925,10 +935,23 @@ read_application_data(Data, user_data_buffer = {Front,BufferSize,Rear}}} end end. + +passive_receive(#state{static_env = #static_env{role = Role, + socket = Socket, + trackers = Trackers, + transport_cb = Transport, + protocol_cb = Connection}, + start_or_recv_from = RecvFrom, + connection_env = #connection_env{socket_tls_closed = #alert{} = Alert}} = State, + StateName, _) -> + Pids = Connection:pids(State), + alert_user(Pids, Transport, Trackers, Socket, RecvFrom, Alert, Role, StateName, Connection), + {stop, {shutdown, normal}, State}; passive_receive(#state{user_data_buffer = {Front,BufferSize,Rear}, %% Assert! Erl distribution uses active sockets + static_env = #static_env{protocol_cb = Connection}, connection_env = #connection_env{erl_dist_handle = undefined}} - = State0, StateName, Connection, StartTimerAction) -> + = State0, StateName, StartTimerAction) -> case BufferSize of 0 -> Connection:next_event(StateName, no_record, State0, StartTimerAction); @@ -1396,14 +1419,27 @@ no_records(Extensions) -> handle_active_option(false, connection = StateName, To, Reply, State) -> hibernate_after(StateName, State, [{reply, To, Reply}]); -handle_active_option(_, connection = StateName, To, Reply, #state{static_env = #static_env{role = Role}, - connection_env = #connection_env{socket_tls_closed = true}, - user_data_buffer = {_,0,_}} = State) -> +handle_active_option(_, connection = StateName, To, Reply, + #state{static_env = #static_env{role = Role}, + connection_env = #connection_env{socket_tls_closed = true}, + user_data_buffer = {_,0,_}} = State) -> Alert = ?ALERT_REC(?FATAL, ?CLOSE_NOTIFY, all_data_delivered), handle_normal_shutdown(Alert#alert{role = Role}, StateName, State), {stop_and_reply,{shutdown, peer_close}, [{reply, To, Reply}]}; -handle_active_option(_, connection = StateName0, To, Reply, #state{static_env = #static_env{protocol_cb = Connection}, - user_data_buffer = {_,0,_}} = State0) -> +handle_active_option(_, connection = StateName, To, _Reply, + #state{static_env = #static_env{role = Role, + socket = Socket, + trackers = Trackers, + transport_cb = Transport, + protocol_cb = Connection}, + connection_env = #connection_env{socket_tls_closed = Alert = #alert{}}, + user_data_buffer = {_,0,_}} = State) -> + Pids = Connection:pids(State), + alert_user(Pids, Transport, Trackers, Socket, To, Alert, Role, StateName, Connection), + {stop, {shutdown, normal}, State}; +handle_active_option(_, connection = StateName0, To, Reply, + #state{static_env = #static_env{protocol_cb = Connection}, + user_data_buffer = {_,0,_}} = State0) -> case Connection:next_event(StateName0, no_record, State0) of {next_state, StateName, State} -> hibernate_after(StateName, State, [{reply, To, Reply}]); diff --git a/lib/ssl/src/tls_gen_connection.erl b/lib/ssl/src/tls_gen_connection.erl index 940666f104..bfdc8a4f2f 100644 --- a/lib/ssl/src/tls_gen_connection.erl +++ b/lib/ssl/src/tls_gen_connection.erl @@ -872,7 +872,14 @@ handle_alerts([#alert{level = ?WARNING, description = ?CLOSE_NOTIFY} | _Alerts], {next_state, connection = StateName, #state{connection_env = CEnv, socket_options = #socket_options{active = false}, start_or_recv_from = From} = State}) when From == undefined -> + %% Linger to allow recv and setopts to possibly fetch data not yet delivered to user to be fetched {next_state, StateName, State#state{connection_env = CEnv#connection_env{socket_tls_closed = true}}}; +handle_alerts([#alert{level = ?FATAL} = Alert | _Alerts], + {next_state, connection = StateName, #state{connection_env = CEnv, + socket_options = #socket_options{active = false}, + start_or_recv_from = From} = State}) when From == undefined -> + %% Linger to allow recv and setopts to retrieve alert reason + {next_state, StateName, State#state{connection_env = CEnv#connection_env{socket_tls_closed = Alert}}}; handle_alerts([Alert | Alerts], {next_state, StateName, State}) -> handle_alerts(Alerts, ssl_gen_statem:handle_alert(Alert, StateName, State)); handle_alerts([Alert | Alerts], {next_state, StateName, State, _Actions}) -> diff --git a/lib/ssl/test/tls_1_3_version_SUITE.erl b/lib/ssl/test/tls_1_3_version_SUITE.erl index 8a3ff288f7..5b6b40305f 100644 --- a/lib/ssl/test/tls_1_3_version_SUITE.erl +++ b/lib/ssl/test/tls_1_3_version_SUITE.erl @@ -57,7 +57,11 @@ middle_box_client_tls_v2_session_reused/0, middle_box_client_tls_v2_session_reused/1, renegotiate_error/0, - renegotiate_error/1 + renegotiate_error/1, + client_cert_fail_alert_active/0, + client_cert_fail_alert_active/1, + client_cert_fail_alert_passive/0, + client_cert_fail_alert_passive/1 ]). @@ -90,7 +94,9 @@ tls_1_3_1_2_tests() -> middle_box_tls13_client, middle_box_tls12_enabled_client, middle_box_client_tls_v2_session_reused, - renegotiate_error + renegotiate_error, + client_cert_fail_alert_active, + client_cert_fail_alert_passive ]. legacy_tests() -> [tls_client_tls10_server, @@ -329,6 +335,60 @@ renegotiate_error(Config) when is_list(Config) -> ct:fail(Reason) end. + +client_cert_fail_alert_active() -> + [{doc, "Check that we receive alert message"}]. +client_cert_fail_alert_active(Config) when is_list(Config) -> + ssl:clear_pem_cache(), + {_ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + ClientOpts0 = ssl_test_lib:ssl_options(extra_client, client_cert_opts, Config), + ServerOpts0 = ssl_test_lib:ssl_options(extra_server, server_cert_opts, Config), + PrivDir = proplists:get_value(priv_dir, Config), + NewClientCertFile = filename:join(PrivDir, "client_invalid_cert.pem"), + + create_bad_client_certfile(NewClientCertFile, ClientOpts0), + + ClientOpts = [{active, true}, + {verify, verify_peer}, + {certfile, NewClientCertFile} | proplists:delete(certfile, ClientOpts0)], + ServerOpts = [{verify, verify_peer}, {fail_if_no_peer_cert, true}| ServerOpts0], + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + {ok, Socket} = ssl:connect(Hostname, Port, ClientOpts), + receive + {Server, {error, {tls_alert, {unknown_ca, _}}}} -> + receive + {ssl_error, Socket, {tls_alert, {unknown_ca, _}}} -> + ok + after 500 -> + ct:fail(no_acticv_msg) + end + end. + +client_cert_fail_alert_passive() -> + [{doc, "Check that recv or setopts return alert"}]. +client_cert_fail_alert_passive(Config) when is_list(Config) -> + ssl:clear_pem_cache(), + {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + ClientOpts0 = ssl_test_lib:ssl_options(extra_client, client_cert_opts, Config), + ServerOpts0 = ssl_test_lib:ssl_options(extra_server, server_cert_opts, Config), + PrivDir = proplists:get_value(priv_dir, Config), + NewClientCertFile = filename:join(PrivDir, "client_invalid_cert.pem"), + + create_bad_client_certfile(NewClientCertFile, ClientOpts0), + + ClientOpts = [{active, false}, + {verify, verify_peer}, + {certfile, NewClientCertFile} | proplists:delete(certfile, ClientOpts0)], + ServerOpts = [{verify, verify_peer}, {fail_if_no_peer_cert, true}| ServerOpts0], + alert_passive(ServerOpts, ClientOpts, recv, + ServerNode, Hostname), + alert_passive(ServerOpts, ClientOpts, setopts, + ServerNode, Hostname). + tls13_client_tls11_server() -> [{doc,"Test that a TLS 1.3 client gets old server alert from TLS 1.0 server."}]. tls13_client_tls11_server(Config) when is_list(Config) -> @@ -359,3 +419,31 @@ check_session_id(Socket, not_empty) -> _ -> ok end. + +alert_passive(ServerOpts, ClientOpts, Function, + ServerNode, Hostname) -> + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + {ok, Socket} = ssl:connect(Hostname, Port, ClientOpts), + ct:sleep(500), + case Function of + recv -> + {error, {tls_alert, {unknown_ca,_}}} = ssl:recv(Socket, 0); + setopts -> + {error, {tls_alert, {unknown_ca,_}}} = ssl:setopts(Socket, [{active, once}]) + end. + +create_bad_client_certfile(NewClientCertFile, ClientOpts0) -> + KeyFile = proplists:get_value(keyfile, ClientOpts0), + [KeyEntry] = ssl_test_lib:pem_to_der(KeyFile), + Key = ssl_test_lib:public_key(public_key:pem_entry_decode(KeyEntry)), + ClientCertFile = proplists:get_value(certfile, ClientOpts0), + + [{'Certificate', ClientDerCert, _}] = ssl_test_lib:pem_to_der(ClientCertFile), + ClientOTPCert = public_key:pkix_decode_cert(ClientDerCert, otp), + ClientOTPTbsCert = ClientOTPCert#'OTPCertificate'.tbsCertificate, + NewClientDerCert = public_key:pkix_sign(ClientOTPTbsCert, Key), + ssl_test_lib:der_to_pem(NewClientCertFile, [{'Certificate', NewClientDerCert, not_encrypted}]). -- 2.43.0
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor