Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
home:Ledest:erlang:24
erlang
3771-Add-option-to-configure-encryption-seed-fo...
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File 3771-Add-option-to-configure-encryption-seed-for-TLS-1.3-.patch of Package erlang
From fc5fdd9ed5b4cd8db147460f76118fc315755fd7 Mon Sep 17 00:00:00 2001 From: Anders Kiel Hovgaard <anders.hovgaard@motorolasolutions.com> Date: Fri, 6 May 2022 13:39:20 +0200 Subject: [PATCH 1/2] Add option to configure encryption seed for TLS 1.3 stateless tickets This enables TLS 1.3 stateless session tickets to work across multiple server instances by allowing the user to configure the same encryption seed in each instance, through a newly added `stateless_tickets_seed` ssl server option. --- lib/ssl/doc/src/ssl.xml | 20 +++++++ lib/ssl/src/ssl.erl | 19 ++++++ lib/ssl/src/ssl_internal.hrl | 3 +- lib/ssl/src/tls_server_session_ticket.erl | 32 +++++++---- lib/ssl/src/tls_socket.erl | 12 ++-- lib/ssl/test/ssl_api_SUITE.erl | 47 +++++++++++++++ lib/ssl/test/ssl_session_ticket_SUITE.erl | 70 ++++++++++++++++++++++- 7 files changed, 185 insertions(+), 18 deletions(-) diff --git a/lib/ssl/doc/src/ssl.xml b/lib/ssl/doc/src/ssl.xml index c0b79d9f2b..7fb1ef73a9 100644 --- a/lib/ssl/doc/src/ssl.xml +++ b/lib/ssl/doc/src/ssl.xml @@ -1448,6 +1448,26 @@ fun(srp, Username :: binary(), UserState :: term()) -> </desc> </datatype> + <datatype> + <name name="stateless_tickets_seed"/> + <desc> + <p>Configures the seed used for the encryption of stateless session tickets. + Allowed values are any randomly generated <c>binary()</c>. If this option is not + configured, an encryption seed will be randomly generated.</p> + + <warning><p>Reusing the ticket encryption seed between multiple server + instances enables stateless session tickets to work across multiple server + instances, but it breaks anti-replay protection across instances.</p> + + <p>Inaccurate time synchronization between server instances can also + affect session ticket freshness checks, potentially causing false negatives as + well as false positives.</p></warning> + + <note><p>This option is supported by TLS 1.3 and above and only with stateless + session tickets.</p></note> + </desc> + </datatype> + <datatype> <name name="anti_replay"/> <desc> diff --git a/lib/ssl/src/ssl.erl b/lib/ssl/src/ssl.erl index c16c076afd..35bfe4844c 100644 --- a/lib/ssl/src/ssl.erl +++ b/lib/ssl/src/ssl.erl @@ -469,6 +469,7 @@ {honor_ecc_order, honor_ecc_order()} | {client_renegotiation, client_renegotiation()}| {session_tickets, server_session_tickets()} | + {stateless_tickets_seed, stateless_tickets_seed()} | {anti_replay, anti_replay()} | {cookie, cookie()} | {early_data, server_early_data()}. @@ -489,6 +490,7 @@ -type honor_cipher_order() :: boolean(). -type honor_ecc_order() :: boolean(). -type client_renegotiation() :: boolean(). +-type stateless_tickets_seed() :: binary(). -type cookie() :: boolean(). %% ------------------------------------------------------------------------------------------------------- -type prf_random() :: client_random | server_random. % exported @@ -1819,6 +1821,16 @@ handle_option(session_tickets = Option, Value0, #{versions := Versions} = Option assert_option_dependency(Option, versions, Versions, ['tlsv1.3']), Value = validate_option(Option, Value0, Role), OptionsMap#{Option => Value}; +handle_option(stateless_tickets_seed = Option, unbound, OptionsMap, #{rules := Rules}) -> + Value = validate_option(Option, default_value(Option, Rules)), + OptionsMap#{Option => Value}; +handle_option(stateless_tickets_seed = Option, Value0, + #{session_tickets := SessionTickets, versions := Versions} = OptionsMap, #{role := Role}) -> + assert_role(server_only, Role, Option, Value0), + assert_option_dependency(Option, versions, Versions, ['tlsv1.3']), + assert_option_dependency(Option, session_tickets, [SessionTickets], [stateless]), + Value = validate_option(Option, Value0), + OptionsMap#{Option => Value}; handle_option(signature_algs = Option, unbound, #{versions := [HighestVersion | _] = Versions} = OptionsMap, #{role := Role}) -> Value = handle_hashsigns_option( @@ -2391,6 +2403,13 @@ validate_option(session_tickets, Value, client) -> {options, role, {session_tickets, {Value, {client, [disabled, manual, auto]}}}}}); +validate_option(stateless_tickets_seed, undefined, _) -> + undefined; +validate_option(stateless_tickets_seed, Seed, _) + when is_binary(Seed) -> + Seed; +validate_option(stateless_tickets_seed = Option, Value, _) -> + throw({error, {options, type, {Option, {Value, not_binary}}}}); validate_option(sni_fun, undefined, _) -> undefined; validate_option(sni_fun, Fun, _) diff --git a/lib/ssl/src/ssl_internal.hrl b/lib/ssl/src/ssl_internal.hrl index 93d7c2456e..381611fa2c 100644 --- a/lib/ssl/src/ssl_internal.hrl +++ b/lib/ssl/src/ssl_internal.hrl @@ -184,7 +184,8 @@ secure_renegotiate => {true, [versions]}, keep_secrets => {false, [versions]}, server_name_indication => {undefined, [versions]}, - session_tickets => {disabled, [versions]}, + session_tickets => {disabled, [versions]}, + stateless_tickets_seed => {undefined, [versions, session_tickets]}, signature_algs => {undefined, [versions]}, signature_algs_cert => {undefined, [versions]}, sni_fun => {undefined, [versions, diff --git a/lib/ssl/src/tls_server_session_ticket.erl b/lib/ssl/src/tls_server_session_ticket.erl index 257c4c0812..08f3b19111 100644 --- a/lib/ssl/src/tls_server_session_ticket.erl +++ b/lib/ssl/src/tls_server_session_ticket.erl @@ -31,7 +31,7 @@ -include("ssl_cipher.hrl"). %% API --export([start_link/6, +-export([start_link/7, new/3, use/4 ]). @@ -54,12 +54,16 @@ %%%=================================================================== %%% API %%%=================================================================== --spec start_link(term(), atom(), integer(), integer(), integer(), tuple()) -> {ok, Pid :: pid()} | +-spec start_link(term(), Mode, integer(), integer(), integer(), tuple(), Seed) -> + {ok, Pid :: pid()} | {error, Error :: {already_started, pid()}} | {error, Error :: term()} | - ignore. -start_link(Listener, Mode, Lifetime, TicketStoreSize, MaxEarlyDataSize, AntiReplay) -> - gen_server:start_link(?MODULE, [Listener, Mode, Lifetime, TicketStoreSize, MaxEarlyDataSize, AntiReplay], []). + ignore + when Mode :: stateless | stateful, + Seed :: undefined | binary(). +start_link(Listener, Mode, Lifetime, TicketStoreSize, MaxEarlyDataSize, AntiReplay, Seed) -> + gen_server:start_link(?MODULE, [Listener, Mode, Lifetime, TicketStoreSize, + MaxEarlyDataSize, AntiReplay, Seed], []). new(Pid, Prf, MasterSecret) -> gen_server:call(Pid, {new_session_ticket, Prf, MasterSecret}, infinity). @@ -148,20 +152,18 @@ format_status(_Opt, Status) -> %%% Internal functions %%%=================================================================== -inital_state([stateless, Lifetime, _, MaxEarlyDataSize, undefined]) -> +inital_state([stateless, Lifetime, _, MaxEarlyDataSize, undefined, Seed]) -> #state{nonce = 0, - stateless = #{seed => {crypto:strong_rand_bytes(16), - crypto:strong_rand_bytes(32)}, + stateless = #{seed => stateless_seed(Seed), window => undefined}, lifetime = Lifetime, max_early_data_size = MaxEarlyDataSize }; -inital_state([stateless, Lifetime, _, MaxEarlyDataSize, {Window, K, M}]) -> +inital_state([stateless, Lifetime, _, MaxEarlyDataSize, {Window, K, M}, Seed]) -> erlang:send_after(Window * 1000, self(), rotate_bloom_filters), #state{nonce = 0, stateless = #{bloom_filter => tls_bloom_filter:new(K, M), - seed => {crypto:strong_rand_bytes(16), - crypto:strong_rand_bytes(32)}, + seed => stateless_seed(Seed), window => Window}, lifetime = Lifetime, max_early_data_size = MaxEarlyDataSize @@ -443,3 +445,11 @@ stateless_anti_replay(Index, PSK, Binder, end; stateless_anti_replay(Index, PSK, _, State) -> {{ok, {Index, PSK}}, State}. + +-spec stateless_seed(Seed :: undefined | binary()) -> + {IV :: binary(), Shard :: binary()}. +stateless_seed(undefined) -> + {crypto:strong_rand_bytes(16), crypto:strong_rand_bytes(32)}; +stateless_seed(Seed) -> + <<IV:16/binary, Shard:32/binary, _/binary>> = crypto:hash(sha512, Seed), + {IV, Shard}. diff --git a/lib/ssl/src/tls_socket.erl b/lib/ssl/src/tls_socket.erl index 779043f1de..d9d90ac426 100644 --- a/lib/ssl/src/tls_socket.erl +++ b/lib/ssl/src/tls_socket.erl @@ -261,20 +261,24 @@ session_tickets_tracker(_,_, _, _, #{erl_dist := false, session_tickets_tracker(ListenSocket, Lifetime, TicketStoreSize, MaxEarlyDataSize, #{erl_dist := false, session_tickets := Mode, - anti_replay := AntiReplay}) -> + anti_replay := AntiReplay, + stateless_tickets_seed := Seed}) -> tls_server_session_ticket_sup:start_child([ListenSocket, Mode, Lifetime, - TicketStoreSize, MaxEarlyDataSize, AntiReplay]); + TicketStoreSize, MaxEarlyDataSize, + AntiReplay, Seed]); session_tickets_tracker(ListenSocket, Lifetime, TicketStoreSize, MaxEarlyDataSize, #{erl_dist := true, session_tickets := Mode, - anti_replay := AntiReplay}) -> + anti_replay := AntiReplay, + stateless_tickets_seed := Seed}) -> SupName = tls_server_session_ticket_sup:sup_name(dist), Children = supervisor:count_children(SupName), Workers = proplists:get_value(workers, Children), case Workers of 0 -> tls_server_session_ticket_sup:start_child([ListenSocket, Mode, Lifetime, - TicketStoreSize, MaxEarlyDataSize, AntiReplay]); + TicketStoreSize, MaxEarlyDataSize, + AntiReplay, Seed]); 1 -> [{_,Child,_, _}] = supervisor:which_children(SupName), {ok, Child} diff --git a/lib/ssl/test/ssl_api_SUITE.erl b/lib/ssl/test/ssl_api_SUITE.erl index dac6a30bee..a6264eed00 100644 --- a/lib/ssl/test/ssl_api_SUITE.erl +++ b/lib/ssl/test/ssl_api_SUITE.erl @@ -175,6 +175,8 @@ server_options_negative_version_gap/1, server_options_negative_dependency_role/0, server_options_negative_dependency_role/1, + server_options_negative_stateless_tickets_seed/0, + server_options_negative_stateless_tickets_seed/1, invalid_options_tls13/0, invalid_options_tls13/1, ssl_not_started/0, @@ -354,6 +356,7 @@ tls13_group() -> server_options_negative_early_data, server_options_negative_version_gap, server_options_negative_dependency_role, + server_options_negative_stateless_tickets_seed, invalid_options_tls13, cookie ]. @@ -2473,6 +2476,50 @@ server_options_negative_dependency_role(Config) when is_list(Config) -> {options,role, {session_tickets,{manual,{server,[disabled,stateful,stateless]}}}}). +%%-------------------------------------------------------------------- +server_options_negative_stateless_tickets_seed() -> + [{doc, "Test server option stateless_tickets_seed"}]. +server_options_negative_stateless_tickets_seed(Config) -> + Seed = crypto:strong_rand_bytes(32), + start_server_negative(Config, [{versions, ['tlsv1.2']}, + {stateless_tickets_seed, Seed}], + {options, dependency, + {stateless_tickets_seed, {versions, ['tlsv1.3']}}}), + + start_server_negative(Config, [{versions, ['tlsv1.2', 'tlsv1.3']}, + {stateless_tickets_seed, Seed}], + {options, dependency, + {stateless_tickets_seed, + {session_tickets, [stateless]}}}), + + start_server_negative(Config, [{versions, ['tlsv1.2', 'tlsv1.3']}, + {session_tickets, disabled}, + {stateless_tickets_seed, Seed}], + {options, dependency, + {stateless_tickets_seed, + {session_tickets, [stateless]}}}), + + start_server_negative(Config, [{versions, ['tlsv1.2', 'tlsv1.3']}, + {session_tickets, stateful}, + {stateless_tickets_seed, Seed}], + {options, dependency, + {stateless_tickets_seed, + {session_tickets, [stateless]}}}), + + InvalidSeed1 = 12345, + start_server_negative(Config, [{versions, ['tlsv1.2', 'tlsv1.3']}, + {session_tickets, stateless}, + {stateless_tickets_seed, InvalidSeed1}], + {options, type, {stateless_tickets_seed, + {InvalidSeed1, not_binary}}}), + + InvalidSeed2 = 'some-random-char-list', + start_server_negative(Config, [{versions, ['tlsv1.2', 'tlsv1.3']}, + {session_tickets, stateless}, + {stateless_tickets_seed, InvalidSeed2}], + {options, type, {stateless_tickets_seed, + {InvalidSeed2, not_binary}}}). + %%-------------------------------------------------------------------- honor_server_cipher_order_tls13() -> [{doc,"Test API honor server cipher order in TLS 1.3."}]. diff --git a/lib/ssl/test/ssl_session_ticket_SUITE.erl b/lib/ssl/test/ssl_session_ticket_SUITE.erl index 1947d41138..6000117f6f 100644 --- a/lib/ssl/test/ssl_session_ticket_SUITE.erl +++ b/lib/ssl/test/ssl_session_ticket_SUITE.erl @@ -78,7 +78,9 @@ early_data_basic/0, early_data_basic/1, early_data_basic_auth/0, - early_data_basic_auth/1]). + early_data_basic_auth/1, + stateless_multiple_servers/0, + stateless_multiple_servers/1]). -include("tls_handshake.hrl"). @@ -104,7 +106,8 @@ groups() -> [ticketage_smaller_than_windowsize_anti_replay, ticketage_bigger_than_windowsize_anti_replay, ticketage_out_of_lifetime_anti_replay, ticket_reuse_anti_replay, - ticket_reuse_anti_replay_server_restart]}, + ticket_reuse_anti_replay_server_restart, + stateless_multiple_servers]}, {mixed, [], mixed_tests()}]. session_tests() -> @@ -1227,6 +1230,69 @@ early_data_basic_auth(Config) when is_list(Config) -> ssl_test_lib:close(Server0), ssl_test_lib:close(Client1). +stateless_multiple_servers() -> + [{doc, "Test session resumption with session tickets, resuming on different server"}]. +stateless_multiple_servers(Config) when is_list(Config) -> + ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), + ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Seed = crypto:strong_rand_bytes(64), + + %% Configure session tickets + ClientOpts = [{session_tickets, auto}, + {versions, ['tlsv1.2','tlsv1.3']} | ClientOpts0], + ServerOpts = [{session_tickets, stateless}, + {stateless_tickets_seed, Seed}, + {versions, ['tlsv1.2','tlsv1.3']} | ServerOpts0], + + Server0 = + ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, + verify_active_session_resumption, + [false]}}, + {options, ServerOpts}]), + Port0 = ssl_test_lib:inet_port(Server0), + + Server1 = + ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, %% Short handshake + verify_active_session_resumption, + [true]}}, + {options, ServerOpts}]), + Port1 = ssl_test_lib:inet_port(Server1), + + %% Store ticket from first connection to server 0 + Client0 = + ssl_test_lib:start_client([{node, ClientNode}, + {port, Port0}, {host, Hostname}, + {mfa, {ssl_test_lib, %% Full handshake + verify_active_session_resumption, + [false]}}, + {from, self()}, {options, ClientOpts}]), + ssl_test_lib:check_result(Server0, ok, Client0, ok), + + %% Wait for session ticket + ct:sleep(100), + + ssl_test_lib:close(Client0), + + %% Use ticket when connecting to server 1 + Client1 = + ssl_test_lib:start_client([{node, ClientNode}, + {port, Port1}, {host, Hostname}, + {mfa, {ssl_test_lib, %% Short handshake + verify_active_session_resumption, + [true]}}, + {from, self()}, {options, ClientOpts}]), + ssl_test_lib:check_result(Server1, ok, Client1, ok), + + process_flag(trap_exit, false), + ssl_test_lib:close(Server0), + ssl_test_lib:close(Server1), + ssl_test_lib:close(Client1). %%-------------------------------------------------------------------- %% Internal functions ------------------------------------------------ -- 2.35.3
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