diff --git a/lib/ssl/src/ssl.erl b/lib/ssl/src/ssl.erl index 5ab8c07ce62..6f1a68ab80d 100644 --- a/lib/ssl/src/ssl.erl +++ b/lib/ssl/src/ssl.erl @@ -658,15 +658,24 @@ Options common to both client and server side. on hello extensions before continuing or aborting the handshake by calling `handshake_continue/3` or `handshake_cancel/1`. -- **`{keep_secrets, KeepSecrets}`** - Configures a TLS 1.3 connection for keylogging. +- **`{keep_secrets, KeepSecrets}`** - Configures a TLS connection for keylogging. - In order to retrieve keylog information on a TLS 1.3 connection, it must be + In order to retrieve keylog information on a TLS connection, it must be configured in advance to keep `client_random` and various handshake secrets. The `keep_secrets` functionality is disabled (`false`) by default. Added in OTP 23.2. + `keep_secrets` can also be set to a fun of arity one, in which case + secrets will be keept and the fun will be called with a list of keylog + items in the case that the connection fails. The + reason for this is that when the connection fails calling + connection_information/2 is not possible due to that the connection + has been closed. See [NSS keylog](using_ssl.md#nss-keylog). + + Added in OTP @OTP-19391@ + - **`{max_handshake_size, HandshakeSize}`** - Limit the acceptable handshake packet size. Used to limit the size of valid TLS handshake packets to avoid DoS @@ -702,7 +711,7 @@ Options common to both client and server side. {ciphers, cipher_suites()} | {signature_algs, signature_algs()} | {signature_algs_cert, [sign_scheme()]} | - {keep_secrets, KeepSecrets:: boolean()} | + {keep_secrets, KeepSecrets:: boolean() | function()} | {max_handshake_size, HandshakeSize::pos_integer()} | {versions, [protocol_version()]} | {log_level, Level::logger:level() | none | all} | @@ -2053,7 +2062,7 @@ TLS connection information that can be used for NSS key logging. -type security_info() :: [{client_random, binary()} | {server_random, binary()} | {master_secret, binary()} | - {keylog, term()}]. + {keylog, [io_lib:chars()]}]. -doc(#{title => <<"Info">>}). @@ -2690,9 +2699,8 @@ defined. Note that the values for `client_random`, `server_random`, `master_secret`, and `keylog` affect the security of connection. -In order to retrieve `keylog` and other secret information from a TLS 1.3 -connection, the `keep_secrets` option must be configured in advance and -set to `true`. +In order to retrieve `keylog` and other secret information from a TLS +connection, the `keep_secrets` option must be configured in advance. > #### Note {: .info } > @@ -3809,7 +3817,7 @@ opt_protocol_versions(UserOpts, Opts, Env) -> {_, LL} = get_opt_of(log_level, LogLevels, DefaultLevel, UserOpts, Opts), - Opts1 = set_opt_bool(keep_secrets, false, UserOpts, Opts), + Opts1 = opt_keep_secrets(UserOpts, Opts), {DistW, Dist} = get_opt_bool(erl_dist, false, UserOpts, Opts1), option_incompatible(PRC =:= dtls andalso Dist, [{protocol, PRC}, {erl_dist, Dist}]), @@ -4623,6 +4631,18 @@ opt_process(UserOpts, Opts0, _Env) -> %% Opts = Opts1#{receiver_spawn_opts => RSO, sender_spawn_opts => SSO}, set_opt_int(hibernate_after, 0, infinity, infinity, UserOpts, Opts2). +opt_keep_secrets(UserOpts, Opts) -> + case get_opt(keep_secrets, false, UserOpts, Opts) of + {new, Value} when is_boolean(Value) orelse is_function(Value) -> + Opts#{keep_secrets => Value}; + {old, _} -> + Opts; + {default, _} -> %% Keep default implicit + Opts; + {_, Value} -> + option_error(keep_secrets, Value) + end. + %%%% get_opt(Opt, Default, UserOpts, Opts) -> @@ -4693,13 +4713,6 @@ get_opt_file(Opt, Default, UserOpts, Opts) -> Res -> Res end. -set_opt_bool(Opt, Default, UserOpts, Opts) -> - case maps:get(Opt, UserOpts, Default) of - Default -> Opts; - Value when is_boolean(Value) -> Opts#{Opt => Value}; - Value -> option_error(Opt, Value) - end. - get_opt_map(Opt, Default, UserOpts, Opts) -> case get_opt(Opt, Default, UserOpts, Opts) of {new, Err} when not is_map(Err) -> option_error(Opt, Err); diff --git a/lib/ssl/src/ssl_gen_statem.erl b/lib/ssl/src/ssl_gen_statem.erl index cd6ba4fdc28..3c610304bab 100644 --- a/lib/ssl/src/ssl_gen_statem.erl +++ b/lib/ssl/src/ssl_gen_statem.erl @@ -967,16 +967,9 @@ read_application_data(Data, end end. -passive_receive(#state{static_env = #static_env{role = Role, - socket = Socket, - trackers = Trackers, - transport_cb = Transport, - protocol_cb = Connection}, - recv = #recv{from = RecvFrom}, - connection_env = #connection_env{socket_tls_closed = #alert{} = Alert}} = State, +passive_receive(#state{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), + handle_normal_shutdown(Alert, StateName, State), {stop, {shutdown, normal}, State}; passive_receive(#state{user_data_buffer = {Front,BufferSize,Rear}, %% Assert! Erl distribution uses active sockets @@ -1024,15 +1017,16 @@ handle_own_alert(Alert0, StateName, #state{static_env = #static_env{role = Role, protocol_cb = Connection}, ssl_options = #{log_level := LogLevel}} = State) -> + Alert = Alert0#alert{role = Role}, try %% Try to tell the other side - send_alert(Alert0, StateName, State) + send_alert(Alert, StateName, State) catch _:_ -> %% Can crash if we are in a uninitialized state ignore end, - try %% Try to tell the local user - Alert = Alert0#alert{role = Role}, - log_alert(LogLevel, Role, Connection:protocol_name(), StateName, Alert), - handle_normal_shutdown(Alert,StateName, State) + try + log_alert(LogLevel, Role, Connection:protocol_name(), StateName, Alert), + %% Try to tell the local user + handle_normal_shutdown(Alert, StateName, State) catch _:_ -> ok end, @@ -1047,6 +1041,7 @@ handle_normal_shutdown(Alert, StateName, #state{static_env = #static_env{role = recv = #recv{from = StartFrom} } = State) -> Pids = Connection:pids(State), + maybe_keep_secrets_callback(Alert, StateName, State), alert_user(Pids, Transport, Trackers, Socket, StartFrom, Alert, Role, StateName, Connection); handle_normal_shutdown(Alert, StateName, #state{static_env = #static_env{role = Role, @@ -1060,6 +1055,7 @@ handle_normal_shutdown(Alert, StateName, #state{static_env = #static_env{role = recv = #recv{from = RecvFrom} } = State) -> Pids = Connection:pids(State), + maybe_keep_secrets_callback(Alert, StateName, State), alert_user(Pids, Transport, Trackers, Socket, Type, Opts, Pid, RecvFrom, Alert, Role, StateName, Connection). handle_alert(#alert{level = ?FATAL} = Alert, StateName, State) -> @@ -1159,23 +1155,16 @@ handle_alert(Alert, StateName, State) -> handle_fatal_alert(Alert0, StateName, #state{static_env = #static_env{role = Role, - socket = Socket, host = Host, port = Port, - trackers = Trackers, - transport_cb = Transport, protocol_cb = Connection}, - connection_env = #connection_env{user_application = {_Mon, Pid}}, ssl_options = #{log_level := LogLevel}, - recv = #recv{from = From}, - session = Session, - socket_options = Opts} = State) -> + session = Session} = State) -> invalidate_session(Role, Host, Port, Session), Alert = Alert0#alert{role = opposite_role(Role)}, log_alert(LogLevel, Role, Connection:protocol_name(), StateName, Alert), - Pids = Connection:pids(State), - alert_user(Pids, Transport, Trackers, Socket, StateName, Opts, Pid, From, Alert, Role, StateName, Connection), + handle_normal_shutdown(Alert, StateName, State), {stop, {shutdown, normal}, State}. handle_trusted_certs_db(#state{ssl_options =#{cacerts := []} = Opts}) @@ -1939,12 +1928,15 @@ security_info(#state{connection_states = ConnectionStates, application_traffic_secret = AppTrafSecretRead, client_early_data_secret = ServerEarlyData }} = ReadState, - BaseSecurityInfo = [{client_random, ClientRand}, {server_random, ServerRand}, {master_secret, MasterSecret}], - + BaseSecurityInfo = [{client_random, ClientRand}, + {server_random, ServerRand}, {master_secret, MasterSecret}], KeepSecrets = maps:get(keep_secrets, Opts, false), - if KeepSecrets =/= true -> - BaseSecurityInfo; - true -> + case KeepSecrets of + false -> + %% Need to include {keep_secrets, false} for maybe_add_keylog + %% to be able to run in user process context + [{keep_secrets, false} | BaseSecurityInfo]; + Other when Other == true orelse is_function(Other) -> #{security_parameters := #security_parameters{ application_traffic_secret = AppTrafSecretWrite0, @@ -1976,7 +1968,7 @@ security_info(#state{connection_states = ConnectionStates, server_handshake_traffic_secret := ServerHSTrafficSecret} -> [{client_handshake_traffic_secret, ClientHSTrafficSecret}, {server_handshake_traffic_secret, ServerHSTrafficSecret}]; - _ -> + _ -> [] end ++ BaseSecurityInfo end. @@ -2133,16 +2125,40 @@ ssl_options_list([{ciphers = Key, Value}|T], Acc) -> ssl_options_list([{Key, Value}|T], Acc) -> ssl_options_list(T, [{Key, Value} | Acc]). -%% Maybe add NSS keylog info according to -%% https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/Key_Log_Format -maybe_add_keylog(Info) -> - maybe_add_keylog(lists:keyfind(protocol, 1, Info), Info). +keylog_1_3(Info) -> + {client_random, ClientRandomBin} = lists:keyfind(client_random, 1, Info), + {selected_cipher_suite, #{prf := Prf}} = lists:keyfind(selected_cipher_suite, 1, Info), + ClientRandom = binary:decode_unsigned(ClientRandomBin), + hs_keylog_1_3(ClientRandom, Prf, Info) ++ connection_keylog_1_3(ClientRandom, Prf, Info). + +keylog_pre_1_3(Info) -> + {client_random, ClientRandomBin} = lists:keyfind(client_random, 1, Info), + ClientRandom = binary:decode_unsigned(ClientRandomBin), + connection_keylog_pre_1_3(ClientRandom, Info). + +hs_keylog_1_3(ClientRandom, Prf, Info) -> + {client_handshake_traffic_secret, ClientHSecretBin} = + lists:keyfind(client_handshake_traffic_secret, 1, Info), + {server_handshake_traffic_secret, ServerHSecretBin} = + lists:keyfind(server_handshake_traffic_secret, 1, Info), + ClientHSecret = keylog_secret(ClientHSecretBin, Prf), + ServerHSecret = keylog_secret(ServerHSecretBin, Prf), + Keylog0 = [io_lib:format("CLIENT_HANDSHAKE_TRAFFIC_SECRET ~64.16.0B ", + [ClientRandom]) ++ ClientHSecret, + io_lib:format("SERVER_HANDSHAKE_TRAFFIC_SECRET ~64.16.0B ", + [ClientRandom]) ++ ServerHSecret], + case lists:keyfind(client_early_data_secret, 1, Info) of + {client_early_data_secret, EarlySecret} -> + ClientEarlySecret = keylog_secret(EarlySecret, Prf), + [io_lib:format("CLIENT_EARLY_TRAFFIC_SECRET ~64.16.0B ", + [ClientRandom]) ++ ClientEarlySecret | Keylog0]; + _ -> + Keylog0 + end. -maybe_add_keylog({_, 'tlsv1.3'}, Info) -> - try - {client_random, ClientRandomBin} = lists:keyfind(client_random, 1, Info), - %% after traffic key update current traffic secret - %% is stored in tls_sender process state +connection_keylog_1_3(ClientRandom, Prf, Info) -> + %% after traffic key update current traffic secret + %% is stored in tls_sender process state MaybeUpdateTrafficSecret = fun({Direction, {Sender, TrafficSecret0}}) -> TrafficSecret = @@ -2160,45 +2176,34 @@ maybe_add_keylog({_, 'tlsv1.3'}, Info) -> MaybeUpdateTrafficSecret(lists:keyfind(client_traffic_secret_0, 1, Info)), {server_traffic_secret_0, ServerTrafficSecret0Bin} = MaybeUpdateTrafficSecret(lists:keyfind(server_traffic_secret_0, 1, Info)), - {client_handshake_traffic_secret, ClientHSecretBin} = lists:keyfind(client_handshake_traffic_secret, 1, Info), - {server_handshake_traffic_secret, ServerHSecretBin} = lists:keyfind(server_handshake_traffic_secret, 1, Info), - {selected_cipher_suite, #{prf := Prf}} = lists:keyfind(selected_cipher_suite, 1, Info), - ClientRandom = binary:decode_unsigned(ClientRandomBin), - ClientTrafficSecret0 = keylog_secret(ClientTrafficSecret0Bin, Prf), - ServerTrafficSecret0 = keylog_secret(ServerTrafficSecret0Bin, Prf), - ClientHSecret = keylog_secret(ClientHSecretBin, Prf), - ServerHSecret = keylog_secret(ServerHSecretBin, Prf), - Keylog0 = [io_lib:format("CLIENT_HANDSHAKE_TRAFFIC_SECRET ~64.16.0B ", [ClientRandom]) ++ ClientHSecret, - io_lib:format("SERVER_HANDSHAKE_TRAFFIC_SECRET ~64.16.0B ", [ClientRandom]) ++ ServerHSecret, - io_lib:format("CLIENT_TRAFFIC_SECRET_0 ~64.16.0B ", [ClientRandom]) ++ ClientTrafficSecret0, - io_lib:format("SERVER_TRAFFIC_SECRET_0 ~64.16.0B ", [ClientRandom]) ++ ServerTrafficSecret0], - Keylog = case lists:keyfind(client_early_data_secret, 1, Info) of - {client_early_data_secret, EarlySecret} -> - ClientEarlySecret = keylog_secret(EarlySecret, Prf), - [io_lib:format("CLIENT_EARLY_TRAFFIC_SECRET ~64.16.0B ", [ClientRandom]) ++ ClientEarlySecret - | Keylog0]; - _ -> - Keylog0 - end, - Info ++ [{keylog,Keylog}] - catch - _Cxx:_Exx -> - Info - end; -maybe_add_keylog({_, _}, Info) -> - try - {client_random, ClientRandomBin} = lists:keyfind(client_random, 1, Info), - {master_secret, MasterSecretBin} = lists:keyfind(master_secret, 1, Info), - ClientRandom = binary:decode_unsigned(ClientRandomBin), - MasterSecret = binary:decode_unsigned(MasterSecretBin), - Keylog = [io_lib:format("CLIENT_RANDOM ~64.16.0B ~96.16.0B", [ClientRandom, MasterSecret])], - Info ++ [{keylog,Keylog}] - catch - _Cxx:_Exx -> + + ClientTrafficSecret = keylog_secret(ClientTrafficSecret0Bin, Prf), + ServerTrafficSecret = keylog_secret(ServerTrafficSecret0Bin, Prf), + + [io_lib:format("CLIENT_TRAFFIC_SECRET_0 ~64.16.0B ", [ClientRandom]) ++ ClientTrafficSecret, + io_lib:format("SERVER_TRAFFIC_SECRET_0 ~64.16.0B ", [ClientRandom]) ++ ServerTrafficSecret]. + +connection_keylog_pre_1_3(ClientRandom, Info) -> + {master_secret, MasterSecretBin} = lists:keyfind(master_secret, 1, Info), + MasterSecret = binary:decode_unsigned(MasterSecretBin), + [io_lib:format("CLIENT_RANDOM ~64.16.0B ~96.16.0B", [ClientRandom, MasterSecret])]. + + +%% Maybe add NSS keylog info according to +%% https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/Key_Log_Format +maybe_add_keylog(Info) -> + case lists:keyfind(keep_secrets, 1, Info) of + {keep_secrets, Value} when Value == true; + is_function(Value)-> + case lists:keyfind(protocol, 1, Info) of + {protocol, 'tlsv1.3'} -> + Info ++ [{keylog, keylog_1_3(Info)}]; + {protocol, _} -> + Info ++ [{keylog, keylog_pre_1_3(Info)}] + end; + {keep_secrets, false} -> Info - end; -maybe_add_keylog(_, Info) -> - Info. + end. keylog_secret(SecretBin, sha256) -> io_lib:format("~64.16.0B", [binary:decode_unsigned(SecretBin)]); @@ -2207,6 +2212,39 @@ keylog_secret(SecretBin, sha384) -> keylog_secret(SecretBin, sha512) -> io_lib:format("~128.16.0B", [binary:decode_unsigned(SecretBin)]). +maybe_keep_secrets_callback(#alert{level = ?FATAL}, StateName, + #state{ssl_options = #{keep_secrets := Fun}} = State) when is_function(Fun) -> + case alert_keylog(StateName, State) of + [] -> + ok; + KeyLog -> + Fun(KeyLog) + end; +maybe_keep_secrets_callback(_, _, _) -> + ok. + +alert_keylog(connection, #state{connection_env = + #connection_env{negotiated_version = TlsVersion}} = State) + when ?TLS_GTE(TlsVersion, ?TLS_1_3) -> + keylog_1_3(connection_info(State) ++ security_info(State)); +alert_keylog(connection, #state{connection_env = + #connection_env{negotiated_version = TlsVersion}} = State) + when ?TLS_LTE(TlsVersion, ?TLS_1_2) -> + keylog_pre_1_3(security_info(State)); +alert_keylog(start, _) -> %% TLS 1.3 + []; +alert_keylog(wait_sh, _) -> %% TLS 1.3 + []; +alert_keylog(_, #state{connection_env = + #connection_env{negotiated_version = TlsVersion}} = State) + when ?TLS_GTE(TlsVersion, ?TLS_1_3) -> + Info = connection_info(State) ++ security_info(State), + {client_random, ClientRandomBin} = lists:keyfind(client_random, 1, Info), + {selected_cipher_suite, #{prf := Prf}} = lists:keyfind(selected_cipher_suite, 1, Info), + ClientRandom = binary:decode_unsigned(ClientRandomBin), + hs_keylog_1_3(ClientRandom, Prf, Info); +alert_keylog(_,_) -> + []. %%%################################################################ %%%# diff --git a/lib/ssl/src/tls_gen_connection.erl b/lib/ssl/src/tls_gen_connection.erl index 5d95d09816d..fdf73f92328 100644 --- a/lib/ssl/src/tls_gen_connection.erl +++ b/lib/ssl/src/tls_gen_connection.erl @@ -910,10 +910,12 @@ handle_alerts([#alert{level = ?WARNING, description = ?CLOSE_NOTIFY} | _Alerts], recv = #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], +handle_alerts([#alert{level = ?FATAL} = Alert0 | _Alerts], {next_state, connection = StateName, #state{connection_env = CEnv, + static_env = #static_env{role = Role}, socket_options = #socket_options{active = false}, recv = #recv{from = From}} = State}) when From == undefined -> + Alert = Alert0#alert{role = ssl_gen_statem:opposite_role(Role)}, %% 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}) -> diff --git a/lib/ssl/src/tls_handshake_1_3.erl b/lib/ssl/src/tls_handshake_1_3.erl index 776e2a58087..c7c3831b2df 100644 --- a/lib/ssl/src/tls_handshake_1_3.erl +++ b/lib/ssl/src/tls_handshake_1_3.erl @@ -976,7 +976,8 @@ calculate_client_early_traffic_secret( -maybe_store_early_data_secret(#{keep_secrets := true}, EarlySecret, State) -> +maybe_store_early_data_secret(#{keep_secrets := Keep}, EarlySecret, State) when Keep == true; + is_function(Keep) -> #{security_parameters := SecParams0} = State, SecParams = SecParams0#security_parameters{client_early_data_secret = EarlySecret}, State#{security_parameters := SecParams}; @@ -1177,8 +1178,9 @@ overwrite_client_random(ConnectionState = #{security_parameters := SecurityParam maybe_store_handshake_traffic_secret(#state{connection_states = #{pending_read := PendingRead} = CS, - ssl_options = #{keep_secrets := true}} = State, - ClientHSTrafficSecret, ServerHSTrafficSecret) -> + ssl_options = #{keep_secrets := Keep}} = State, + ClientHSTrafficSecret, ServerHSTrafficSecret) when Keep == true; + is_function(Keep) -> PendingRead1 = store_handshake_traffic_secret(PendingRead, ClientHSTrafficSecret, ServerHSTrafficSecret), State#state{connection_states = CS#{pending_read => PendingRead1}}; maybe_store_handshake_traffic_secret(State, _, _) -> diff --git a/lib/ssl/src/tls_server_connection_1_3.erl b/lib/ssl/src/tls_server_connection_1_3.erl index 8801f00b678..cbf5d4b6f46 100644 --- a/lib/ssl/src/tls_server_connection_1_3.erl +++ b/lib/ssl/src/tls_server_connection_1_3.erl @@ -471,10 +471,10 @@ do_handle_client_hello(#client_hello{cipher_suites = ClientCiphers, Opts = State2#state.ssl_options, State3 = case maps:get(keep_secrets, Opts, false) of - true -> - tls_handshake_1_3:set_client_random(State2, Hello#client_hello.random); false -> - State2 + State2; + Other when Other == true orelse is_function(Other) -> + tls_handshake_1_3:set_client_random(State2, Hello#client_hello.random) end, State4 = tls_handshake_1_3:update_start_state(State3, diff --git a/lib/ssl/test/ssl_api_SUITE.erl b/lib/ssl/test/ssl_api_SUITE.erl index 169431ec163..4e15fe24183 100644 --- a/lib/ssl/test/ssl_api_SUITE.erl +++ b/lib/ssl/test/ssl_api_SUITE.erl @@ -3063,8 +3063,10 @@ options_debug(_Config) -> %% debug log_level keep_secrets ?OK(#{log_level := notice}, [], server, [keep_secrets]), ?OK(#{log_level := debug, keep_secrets := true}, [{log_level, debug}, {keep_secrets, true}], server), + ?OK(#{log_level := debug, keep_secrets := true}, + [{log_level, debug}, {keep_secrets, fun(_KeyLogItems) -> _KeyLogItems end}], server), ?OK(#{log_level := info}, - [{log_level, info}, {keep_secrets, false}], server, [keep_secrets]), + [{log_level, info}, {keep_secrets, false}], server, []), %% Errors ?ERR({log_level, foo}, [{log_level, foo}], server), @@ -4024,9 +4026,9 @@ keylog_connection_info_result(Socket, KeepSecrets) -> check_keylog_info('tlsv1.3', [{keylog, ["CLIENT_HANDSHAKE_TRAFFIC_SECRET"++_,_|_]=Keylog}], true) -> {ok, Keylog}; -check_keylog_info('tlsv1.3', []=Keylog, false) -> +check_keylog_info(_, []=Keylog, false) -> {ok, Keylog}; -check_keylog_info(_, [{keylog, ["CLIENT_RANDOM"++_]=Keylog}], _) -> +check_keylog_info(_, [{keylog, ["CLIENT_RANDOM"++_]=Keylog}], true) -> {ok, Keylog}; check_keylog_info(_, Unexpected, Keep) -> {unexpected, Keep, Unexpected}. diff --git a/lib/ssl/test/tls_1_3_version_SUITE.erl b/lib/ssl/test/tls_1_3_version_SUITE.erl index 7f841681744..cca93a07c91 100644 --- a/lib/ssl/test/tls_1_3_version_SUITE.erl +++ b/lib/ssl/test/tls_1_3_version_SUITE.erl @@ -79,7 +79,9 @@ client_cert_fail_alert_active/0, client_cert_fail_alert_active/1, client_cert_fail_alert_passive/0, - client_cert_fail_alert_passive/1 + client_cert_fail_alert_passive/1, + keylog_on_alert/0, + keylog_on_alert/1 ]). @@ -117,7 +119,8 @@ tls_1_3_1_2_tests() -> middle_box_client_tls_v2_session_reused, renegotiate_error, client_cert_fail_alert_active, - client_cert_fail_alert_passive + client_cert_fail_alert_passive, + keylog_on_alert ]. legacy_tests() -> [tls_client_tls10_server, @@ -533,7 +536,7 @@ client_cert_fail_alert_active(Config) when is_list(Config) -> ServerOpts0 = ssl_test_lib:ssl_options(extra_server, server_cert_opts, Config), PrivDir = proplists:get_value(priv_dir, Config), - NewClientCertFile = filename:join(PrivDir, "clinet_invalid_cert.pem"), + NewClientCertFile = filename:join(PrivDir, "client_invalid_cert.pem"), create_bad_client_certfile(NewClientCertFile, ClientOpts0), ClientOpts = [{active, true}, @@ -584,6 +587,51 @@ tls13_client_tls11_server(Config) when is_list(Config) -> ServerOpts = [{versions, ['tlsv1']} | ssl_test_lib:ssl_options(server_cert_opts, Config)], ssl_test_lib:basic_alert(ClientOpts, ServerOpts, Config, insufficient_security). +keylog_on_alert() -> + [{doc,"Test that keep_secrets callback, if specified, " + "is called with keylog info if alert is raised"}]. + +keylog_on_alert(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 = [{versions, ['tlsv1.3']}, {active, false}, {certfile, NewClientCertFile}| + proplists:delete(certfile, ClientOpts0)], + ServerOpts = [{versions, ['tlsv1.3']}, + {verify, verify_peer}, {fail_if_no_peer_cert, true} + | ServerOpts0], + + Me = self(), + Fun = fun(Keylog) -> + Me ! {alert_keylog, Keylog} + end, + alert_passive([{keep_secrets, Fun} | ServerOpts], ClientOpts, recv, + ServerNode, Hostname), + receive + {alert_keylog, SKeyLog} -> + case SKeyLog of + ["CLIENT_HANDSHAKE_TRAFFIC_SECRET"++_,_|_] -> + ok; + SOther -> + ct:fail({server_received, SOther}) + end + end, + + alert_passive(ServerOpts, [{keep_secrets, Fun} | ClientOpts], recv, + ServerNode, Hostname), + receive + {alert_keylog, CKeyLog} -> + case CKeyLog of + ["CLIENT_HANDSHAKE_TRAFFIC_SECRET"++_,_,_|_] -> + ok; + COther -> + ct:fail({client_received, COther}) + end + end. %%-------------------------------------------------------------------- %% Internal functions and callbacks ----------------------------------- @@ -645,15 +693,15 @@ create_bad_client_certfile(NewClientCertFile, ClientOpts) -> test_rsa_pcks1_cert(SHA, COpts, SOpts, Config) -> #{client_config := ClientOpts, - server_config := ServerOpts} = public_key:pkix_test_data(#{server_chain => #{root => root_key(SHA), - intermediates => intermediates(SHA, 1), - peer => peer_key(SHA)}, - client_chain => #{root => root_key(SHA), - intermediates => intermediates(SHA, 1), - peer => peer_key(SHA)}}), + server_config := ServerOpts} = + public_key:pkix_test_data(#{server_chain => #{root => root_key(SHA), + intermediates => intermediates(SHA, 1), + peer => peer_key(SHA)}, + client_chain => #{root => root_key(SHA), + intermediates => intermediates(SHA, 1), + peer => peer_key(SHA)}}), ssl_test_lib:basic_test(COpts ++ ClientOpts, SOpts ++ ServerOpts, Config). - root_key(SHA) -> root_key(SHA, undefined).