Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
home:Ledest:erlang:23
erlang
1005-ssl-Fix-stateless-ticket-in_window-compari...
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File 1005-ssl-Fix-stateless-ticket-in_window-comparison.patch of Package erlang
From 612fe8f0fab5f6883469d7bc43febf9120cf60dd Mon Sep 17 00:00:00 2001 From: Sindri Ingimundarson <sindri.ingimundarson@motorolasolutions.com> Date: Tue, 24 May 2022 15:53:55 +0200 Subject: [PATCH] ssl: Fix stateless ticket in_window comparison If a stateless ticket is older than the WindowSize of the bloom filter then it will be rejected. By subtracting the ReportedAge from the RealAge we have the DeltaAge, which will become too large during a replay. Previously the absolute age of the ticket was being used. --- lib/ssl/doc/src/ssl.xml | 2 +- lib/ssl/doc/src/using_ssl.xml | 2 +- lib/ssl/src/tls_server_session_ticket.erl | 39 ++++++++++++++++++- lib/ssl/test/ssl_session_ticket_SUITE.erl | 6 +-- .../test/tls_server_session_ticket_SUITE.erl | 34 ++++++++++++++-- 5 files changed, 72 insertions(+), 11 deletions(-) diff --git a/lib/ssl/doc/src/ssl.xml b/lib/ssl/doc/src/ssl.xml index ef74c287e6..c3c26160f6 100644 --- a/lib/ssl/doc/src/ssl.xml +++ b/lib/ssl/doc/src/ssl.xml @@ -1456,7 +1456,7 @@ fun(srp, Username :: binary(), UserState :: term()) -> <p>Allowed values are the pre-defined <c>'10k'</c>, <c>'100k'</c> or a custom 3-tuple that defines the properties of the bloom filters: <c>{WindowSize, HashFunctions, Bits}</c>. <c>WindowSize</c> is the number of seconds after the current Bloom filter is rotated - and also the window size used for freshness checks. <c>HashFunctions</c> is the number + and also the window size used for freshness checks of ClientHello. <c>HashFunctions</c> is the number hash functions and <c>Bits</c> is the number of bits in the bit vector. <c>'10k'</c> and <c>'100k'</c> are simple defaults with the following properties:</p> <list type="bulleted"> diff --git a/lib/ssl/doc/src/using_ssl.xml b/lib/ssl/doc/src/using_ssl.xml index 318e367245..148c7d8dfc 100644 --- a/lib/ssl/doc/src/using_ssl.xml +++ b/lib/ssl/doc/src/using_ssl.xml @@ -776,7 +776,7 @@ ssl:connect("localhost", 9999, [{verify, verify_peer}, less than ticket lifetime.</p></item> <item><p>Actual ticket age shall be less than the ticket lifetime (stateless session tickets contain the servers timestamp when the ticket was issued).</p></item> - <item><p>Ticket shall be used within specified time window (freshness checks).</p></item> + <item><p>ClientHello created with the ticket shall be sent relatively recently (freshness checks).</p></item> <item><p>If all above checks passed both <em>current</em> and <em>old</em> Bloom filters are checked to detect if binder was already seen. Being a probabilistic data structure, false positives can occur and they trigger a full handshake.</p></item> diff --git a/lib/ssl/src/tls_server_session_ticket.erl b/lib/ssl/src/tls_server_session_ticket.erl index f6b91404fb..257c4c0812 100644 --- a/lib/ssl/src/tls_server_session_ticket.erl +++ b/lib/ssl/src/tls_server_session_ticket.erl @@ -382,15 +382,50 @@ stateless_usable_ticket(#stateless_ticket{hash = Prf, stateless_living_ticket(0, _, _, _, _) -> true; +%% If `anti_replay` is not enabled, then a ticket is considered to be living +%% if it has not exceeded its lifetime. +%% +%% If `anti_replay` is enabled, we must additionally perform a freshness check +%% as is outlined in section 8.3 Freshness Checks - RFC 8446 stateless_living_ticket(ObfAge, TicketAgeAdd, Lifetime, Timestamp, Window) -> - ReportedAge = ObfAge - TicketAgeAdd, + %% RealAge is the server's view of the age of the ticket in seconds. RealAge = erlang:system_time(second) - Timestamp, + + %% ReportedAge is the client's view of the age of the ticket in milliseconds. + ReportedAge = ObfAge - TicketAgeAdd, + + %% DeltaAge is the difference of the client's view of the age of the ticket + %% and the server's view of the age of the ticket in seconds. + DeltaAge = abs(RealAge - (ReportedAge / 1000)), + + %% We ensure that both the client's view of the age of the ticket and the + %% server's view of the age of the ticket do not exceed the lifetime specified. (ReportedAge =< Lifetime * 1000) andalso (RealAge =< Lifetime) - andalso (in_window(RealAge, Window)). + andalso (in_window(DeltaAge, Window)). in_window(_, undefined) -> true; +%% RFC 8446 - section 8.2 Client Hello Recording +%% describes an anti-replay implementation that can use bounded memory +%% by storing a unique value from a ClientHello (in our case the PSK binder) +%% withing a given time window. +%% +%% In order implement this, when a ClientHello is received, the server +%% must ensure that a ClientHello has been sent relatively recently. +%% We do this by ensuring that the client and server view of the age +%% of the ticket is not larger than our recording window. +%% +%% In the case of an attempted replay attack, there are 2 possible +%% outcomes: +%% - A ClientHello is replayed within the recording window +%% * The ticket looks valid, `in_window` returns true +%% so we proceed to check the unique value +%% * The unique value (PSK Binder) is stored in the bloom filter +%% and we reject the ticket. +%% +%% - A ClientHello is replayed outside the recording window +%% * We reject the ticket as `in_window` returns false. in_window(Age, Window) when is_integer(Window) -> Age =< Window. diff --git a/lib/ssl/test/ssl_session_ticket_SUITE.erl b/lib/ssl/test/ssl_session_ticket_SUITE.erl index 21804324e7..1947d41138 100644 --- a/lib/ssl/test/ssl_session_ticket_SUITE.erl +++ b/lib/ssl/test/ssl_session_ticket_SUITE.erl @@ -243,7 +243,7 @@ ticketage_smaller_than_windowsize_anti_replay(Config) when is_list(Config) -> ticketage_bigger_than_windowsize_anti_replay() -> [{doc, "Session resumption with stateless tickets and anti_replay enabled." "Fresh ClientHellos." - "Ticket age bigger than windowsize. 0-RTT is expected to fail." + "Ticket age bigger than windowsize. 0-RTT is expected to succeed." "(Erlang client - Erlang server)"}]. ticketage_bigger_than_windowsize_anti_replay(Config) when is_list(Config) -> WindowSize = 3, @@ -252,10 +252,10 @@ ticketage_bigger_than_windowsize_anti_replay(Config) when is_list(Config) -> ssl_test_lib:check_result(Server0, ok, Client0, ok), Client1 = anti_replay_helper_connect(Server0, Client0, Port0, ClientNode, Hostname, ClientOpts, - {seconds, WindowSize + 2}, false), + {seconds, WindowSize + 2}, true), Client2 = anti_replay_helper_connect(Server0, Client0, Port0, ClientNode, Hostname, ClientOpts, - {seconds, 2*WindowSize + 2}, false), + {seconds, 2*WindowSize + 2}, true), process_flag(trap_exit, false), [ssl_test_lib:close(A) || A <- [Server0, Client0, Client1, Client2]]. diff --git a/lib/ssl/test/tls_server_session_ticket_SUITE.erl b/lib/ssl/test/tls_server_session_ticket_SUITE.erl index 6e2eeb4996..bccc3c2a58 100644 --- a/lib/ssl/test/tls_server_session_ticket_SUITE.erl +++ b/lib/ssl/test/tls_server_session_ticket_SUITE.erl @@ -37,14 +37,17 @@ main_test/0, main_test/1, misc_test/0, - misc_test/1]). + misc_test/1, + valid_ticket_older_than_windowsize_test/0, + valid_ticket_older_than_windowsize_test/1]). --define(LIFETIME, 1). % tickets expire after 1s +-define(LIFETIME, 3). % tickets expire after 3s -define(TICKET_STORE_SIZE, 1). -define(MASTER_SECRET, "master_secret"). -define(PRF, sha). -define(VERSION, {3,4}). -define(PSK, <<15,168,18,43,216,33,227,142,114,190,70,183,137,57,64,64,66,152,115,94>>). +-define(WINDOW_SIZE, 1). %%-------------------------------------------------------------------- %% Common Test interface functions ----------------------------------- @@ -55,12 +58,12 @@ all() -> groups() -> [{stateful, [], [main_test, expired_ticket_test, invalid_ticket_test]}, {stateless, [], [expired_ticket_test, invalid_ticket_test, main_test]}, - {stateless_antireplay, [], [main_test, misc_test]} + {stateless_antireplay, [], [main_test, misc_test, valid_ticket_older_than_windowsize_test]} ]. init_per_group(stateless_antireplay, Config) -> check_environment([{server_session_tickets, stateless}, - {anti_replay, {10, 20, 30}}] + {anti_replay, {?WINDOW_SIZE, 20, 30}}] ++ Config); init_per_group(Group = stateless, Config) -> check_environment([{server_session_tickets, Group} | Config]); @@ -147,6 +150,29 @@ expired_ticket_test(Config) when is_list(Config) -> [iolist_to_binary(HandshakeHist)]), true = is_process_alive(Pid). +valid_ticket_older_than_windowsize_test() -> + [{doc, "Verify valid ticket handling of tickets older than WindowSize"}]. + +valid_ticket_older_than_windowsize_test(Config) when is_list(Config) -> + Pid = ?config(server_pid, Config), + % Fill in GB tree store for stateful setup (Stateless tests also fail without this) + tls_server_session_ticket:new(Pid, ?PRF, ?MASTER_SECRET), + % Reach ticket store size limit - force GB tree pruning + SessionTicket = #new_session_ticket{} = + tls_server_session_ticket:new(Pid, ?PRF, ?MASTER_SECRET), + TicketRecvTime = erlang:system_time(millisecond), + %% Sleep more than the window length (which is in seconds) + ct:sleep({seconds, 2 * ?WINDOW_SIZE}), + {HandshakeHist, OferredPsks} = get_handshake_hist(SessionTicket, TicketRecvTime, ?PSK), + AcceptResponse = {ok, {0, ?PSK}}, + AcceptResponse = tls_server_session_ticket:use(Pid, OferredPsks, ?PRF, + [iolist_to_binary(HandshakeHist)]), + % check replay attempt result + ExpReplyResult = get_replay_expected_result(Config, AcceptResponse), + ExpReplyResult = tls_server_session_ticket:use(Pid, OferredPsks, ?PRF, + [iolist_to_binary(HandshakeHist)]), + true = is_process_alive(Pid). + misc_test() -> [{doc, "Miscellaneous functionality"}]. misc_test(Config) when is_list(Config) -> -- 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