File 2961-ssl-Fix-session-ticket-handling.patch of Package erlang
From febdc983b953924dc88d64e0b437fa0405b86de2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?P=C3=A9ter=20Dimitrov?= <peterdmv@erlang.org>
Date: Tue, 28 Jul 2020 17:14:53 +0200
Subject: [PATCH] ssl: Fix session ticket handling
Fix handling of stateful session tickets in statless servers and
statless session tickets in stateful servers.
Implement proper handling of faulty session tickets.
---
 lib/ssl/src/ssl_cipher.erl                |  12 +-
 lib/ssl/src/tls_handshake_1_3.erl         |  49 +++---
 lib/ssl/test/ssl_session_ticket_SUITE.erl | 188 +++++++++++++++++++++-
 3 files changed, 226 insertions(+), 23 deletions(-)
diff --git a/lib/ssl/src/ssl_cipher.erl b/lib/ssl/src/ssl_cipher.erl
index 434bdb68bb..ded01f23f6 100644
--- a/lib/ssl/src/ssl_cipher.erl
+++ b/lib/ssl/src/ssl_cipher.erl
@@ -1428,9 +1428,15 @@ decrypt_ticket_data(CipherFragment, Shard, IV) ->
     Size = byte_size(Shard),
     AAD = additional_data(erlang:iolist_size(CipherFragment) - Size),
     Len = byte_size(CipherFragment) - Size - 16,
-    <<Encrypted:Len/binary,CipherTag:16/binary,OTP:Size/binary>> = CipherFragment,
-    Key = crypto:exor(OTP, Shard),
-    crypto:crypto_one_time_aead(aes_256_gcm, Key, IV, Encrypted, AAD, CipherTag, false).
+    case CipherFragment of
+        <<Encrypted:Len/binary,CipherTag:16/binary,OTP:Size/binary>> ->
+            Key = crypto:exor(OTP, Shard),
+            crypto:crypto_one_time_aead(aes_256_gcm, Key, IV,
+                                        Encrypted, AAD, CipherTag,
+                                        false);
+        _ ->
+            error
+    end.
 
 
 additional_data(Length) ->
diff --git a/lib/ssl/src/tls_handshake_1_3.erl b/lib/ssl/src/tls_handshake_1_3.erl
index 2e8726d7ab..52af90ae33 100644
--- a/lib/ssl/src/tls_handshake_1_3.erl
+++ b/lib/ssl/src/tls_handshake_1_3.erl
@@ -2471,25 +2471,38 @@ process_user_tickets(UseTicket) ->
 process_user_tickets([], Acc, _) ->
     lists:reverse(Acc);
 process_user_tickets([H|T], Acc, N) ->
-    #{hkdf := HKDF,
-      sni := _SNI,
-      psk := PSK,
-      timestamp := Timestamp,
-      ticket := NewSessionTicket} = erlang:binary_to_term(H),
-    #new_session_ticket{
-       ticket_lifetime = _LifeTime,
-       ticket_age_add = AgeAdd,
-       ticket_nonce = Nonce,
-       ticket = Ticket,
-       extensions = _Extensions
-      } = NewSessionTicket,
-    TicketAge =  erlang:system_time(seconds) - Timestamp,
-    ObfuscatedTicketAge = obfuscate_ticket_age(TicketAge, AgeAdd),
-    Identity = #psk_identity{
-                  identity = Ticket,
-                  obfuscated_ticket_age = ObfuscatedTicketAge},
-    process_user_tickets(T, [{undefined, N, Identity, PSK, Nonce, HKDF}|Acc], N + 1).
+    case process_ticket(H, N) of
+        error ->
+            process_user_tickets(T, Acc, N + 1);
+        TicketData ->
+            process_user_tickets(T, [TicketData|Acc], N + 1)
+    end.
 
+process_ticket(Bin, N) when is_binary(Bin) ->
+    try erlang:binary_to_term(Bin, [safe]) of
+        #{hkdf := HKDF,
+          sni := _SNI,  %% TODO: Handle SNI?
+          psk := PSK,
+          timestamp := Timestamp,
+          ticket := NewSessionTicket} ->
+            #new_session_ticket{
+               ticket_lifetime = _LifeTime,
+               ticket_age_add = AgeAdd,
+               ticket_nonce = Nonce,
+               ticket = Ticket,
+               extensions = _Extensions
+              } = NewSessionTicket,
+            TicketAge =  erlang:system_time(seconds) - Timestamp,
+            ObfuscatedTicketAge = obfuscate_ticket_age(TicketAge, AgeAdd),
+            Identity = #psk_identity{
+                          identity = Ticket,
+                          obfuscated_ticket_age = ObfuscatedTicketAge},
+            {undefined, N, Identity, PSK, Nonce, HKDF};
+        _Else ->
+            error
+    catch error:badarg ->
+            error
+    end.
 
 %% The "obfuscated_ticket_age"
 %% field of each PskIdentity contains an obfuscated version of the
diff --git a/lib/ssl/test/ssl_session_ticket_SUITE.erl b/lib/ssl/test/ssl_session_ticket_SUITE.erl
index a33940a9f0..218c4a1564 100644
--- a/lib/ssl/test/ssl_session_ticket_SUITE.erl
+++ b/lib/ssl/test/ssl_session_ticket_SUITE.erl
@@ -35,6 +35,18 @@
          basic/1,
          basic_anti_replay/0,
          basic_anti_replay/1,
+         basic_stateful_stateless/0,
+         basic_stateful_stateless/1,
+         basic_stateless_stateful/0,
+         basic_stateless_stateful/1,
+         basic_stateful_stateless_anti_replay/0,
+         basic_stateful_stateless_anti_replay/1,
+         basic_stateless_stateful_anti_replay/0,
+         basic_stateless_stateful_anti_replay/1,
+         basic_stateful_stateless_faulty_ticket/0,
+         basic_stateful_stateless_faulty_ticket/1,
+         basic_stateless_stateful_faulty_ticket/0,
+         basic_stateless_stateful_faulty_ticket/1,
          hello_retry_request/0,
          hello_retry_request/1,
          multiple_tickets/0,
@@ -58,9 +70,12 @@ all() ->
     ].
 
 groups() ->
-    [{'tlsv1.3', [], [{group, stateful}, {group, stateless}]},
+    [{'tlsv1.3', [], [{group, stateful},
+                      {group, stateless},
+                      {group, mixed}]},
      {stateful, [], session_tests()},
-     {stateless, [], session_tests() ++ [basic_anti_replay]}].
+     {stateless, [], session_tests() ++ [basic_anti_replay]},
+     {mixed, [], mixed_tests()}].
 
 session_tests() ->
     [basic,
@@ -68,6 +83,16 @@ session_tests() ->
      multiple_tickets,
      multiple_tickets_2hash].
 
+mixed_tests() ->
+    [
+     basic_stateful_stateless,
+     basic_stateless_stateful,
+     basic_stateful_stateless_anti_replay,
+     basic_stateless_stateful_anti_replay,
+     basic_stateful_stateless_faulty_ticket,
+     basic_stateless_stateful_faulty_ticket
+    ].
+
 init_per_suite(Config0) ->
     catch crypto:stop(),
     try crypto:start() of
@@ -215,6 +240,104 @@ basic_anti_replay(Config) when is_list(Config) ->
     ssl_test_lib:close(Server0),
     ssl_test_lib:close(Client1).
 
+basic_stateful_stateless() ->
+    [{doc,"Test session resumption with session tickets (erlang client - erlang server)"}].
+basic_stateful_stateless(Config) when is_list(Config) ->
+    do_test_mixed(Config,
+                  [{session_tickets, auto},
+                   {log_level, debug},
+                   {versions, ['tlsv1.2','tlsv1.3']}],
+                  [{session_tickets, stateful},
+                   {log_level, debug},
+                   {versions, ['tlsv1.2','tlsv1.3']}],
+                  [{session_tickets, stateless},
+                   {log_level, debug},
+                   {versions, ['tlsv1.2','tlsv1.3']}]).
+
+basic_stateless_stateful() ->
+    [{doc,"Test session resumption with session tickets (erlang client - erlang server)"}].
+basic_stateless_stateful(Config) when is_list(Config) ->
+    do_test_mixed(Config,
+                  [{session_tickets, auto},
+                   {log_level, debug},
+                   {versions, ['tlsv1.2','tlsv1.3']}],
+                  [{session_tickets, stateless},
+                   {log_level, debug},
+                   {versions, ['tlsv1.2','tlsv1.3']}],
+                  [{session_tickets, stateful},
+                   {log_level, debug},
+                   {versions, ['tlsv1.2','tlsv1.3']}]).
+
+basic_stateful_stateless_anti_replay() ->
+    [{doc,"Test session resumption with session tickets (erlang client - erlang server)"}].
+basic_stateful_stateless_anti_replay(Config) when is_list(Config) ->
+    do_test_mixed(Config,
+                  [{session_tickets, auto},
+                   {log_level, debug},
+                   {versions, ['tlsv1.2','tlsv1.3']}],
+                  [{session_tickets, stateful},
+                   {log_level, debug},
+                   {versions, ['tlsv1.2','tlsv1.3']}],
+                  [{session_tickets, stateless},
+                   {log_level, debug},
+                   {anti_replay, '10k'},
+                   {versions, ['tlsv1.2','tlsv1.3']}]).
+
+basic_stateless_stateful_anti_replay() ->
+    [{doc,"Test session resumption with session tickets (erlang client - erlang server)"}].
+basic_stateless_stateful_anti_replay(Config) when is_list(Config) ->
+    do_test_mixed(Config,
+                  [{session_tickets, auto},
+                   {log_level, debug},
+                   {versions, ['tlsv1.2','tlsv1.3']}],
+                  [{session_tickets, stateless},
+                   {log_level, debug},
+                   {anti_replay, '10k'},
+                   {versions, ['tlsv1.2','tlsv1.3']}],
+                  [{session_tickets, stateful},
+                   {log_level, debug},
+                   {versions, ['tlsv1.2','tlsv1.3']}]).
+
+basic_stateful_stateless_faulty_ticket() ->
+    [{doc,"Test session resumption with session tickets (erlang client - erlang server)"}].
+basic_stateful_stateless_faulty_ticket(Config) when is_list(Config) ->
+    do_test_mixed(Config,
+                  [{session_tickets, auto},
+                   {log_level, debug},
+                   {versions, ['tlsv1.2','tlsv1.3']}],
+                  [{session_tickets, manual},
+                   {use_ticket, [<<131,100,0,12,"faultyticket">>,
+                                 <<"faulty ticket">>]},
+                   {log_level, debug},
+                   {versions, ['tlsv1.2','tlsv1.3']}],
+                  [{session_tickets, stateless},
+                   {log_level, debug},
+                   {anti_replay, '10k'},
+                   {versions, ['tlsv1.2','tlsv1.3']}],
+                  [{session_tickets, stateful},
+                   {log_level, debug},
+                   {versions, ['tlsv1.2','tlsv1.3']}]).
+
+basic_stateless_stateful_faulty_ticket() ->
+    [{doc,"Test session resumption with session tickets (erlang client - erlang server)"}].
+basic_stateless_stateful_faulty_ticket(Config) when is_list(Config) ->
+    do_test_mixed(Config,
+                  [{session_tickets, auto},
+                   {log_level, debug},
+                   {versions, ['tlsv1.2','tlsv1.3']}],
+                  [{session_tickets, manual},
+                   {use_ticket, [<<"faulty ticket">>,
+                                 <<131,100,0,12,"faultyticket">>]},
+                   {log_level, debug},
+                   {versions, ['tlsv1.2','tlsv1.3']}],
+                  [{session_tickets, stateless},
+                   {log_level, debug},
+                   {anti_replay, '10k'},
+                   {versions, ['tlsv1.2','tlsv1.3']}],
+                  [{session_tickets, stateful},
+                   {log_level, debug},
+                   {versions, ['tlsv1.2','tlsv1.3']}]).
+
 hello_retry_request() ->
     [{doc,"Test session resumption with session tickets and hello_retry_request (erlang client - erlang server)"}].
 hello_retry_request(Config) when is_list(Config) ->
@@ -462,3 +585,64 @@ multiple_tickets_2hash(Config) when is_list(Config) ->
 %%--------------------------------------------------------------------
 %% Internal functions ------------------------------------------------
 %%--------------------------------------------------------------------
+
+do_test_mixed(Config, COpts, SOpts1, SOpts2) when is_list(Config) ->
+    do_test_mixed(Config, COpts, COpts, SOpts1, SOpts2).
+%%
+do_test_mixed(Config, COpts1, COpts2, SOpts1, SOpts2) 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),
+
+    %% Configure session tickets
+    ClientOpts1 = COpts1 ++ ClientOpts0,
+    ServerOpts1 = SOpts1 ++ ServerOpts0,
+
+    Server0 =
+	ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+				   {from, self()},
+				   {mfa, {ssl_test_lib,
+                                          verify_active_session_resumption,
+                                          [false]}},
+				   {options, ServerOpts1}]),
+    Port0 = ssl_test_lib:inet_port(Server0),
+
+    %% Store ticket from first connection
+    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, ClientOpts1}]),
+    ssl_test_lib:check_result(Server0, ok, Client0, ok),
+
+    %% Wait for session ticket
+    ct:sleep(100),
+
+    ssl_test_lib:close(Client0),
+    ssl_test_lib:close(Server0),
+
+    ClientOpts2 = COpts2 ++ ClientOpts0,
+    ServerOpts2 = SOpts2 ++ ServerOpts0,
+
+    Server1 =
+	ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+				   {from, self()},
+				   {mfa, {ssl_test_lib,
+                                          verify_active_session_resumption,
+                                          [false]}},
+				   {options, ServerOpts2}]),
+    Port1 = ssl_test_lib:inet_port(Server1),
+
+    %% Use ticket
+    Client1 = ssl_test_lib:start_client([{node, ClientNode},
+                                         {port, Port1}, {host, Hostname},
+                                         {mfa, {ssl_test_lib,  %% Short handshake
+                                                verify_active_session_resumption,
+                                                [false]}},
+                                         {from, self()}, {options, ClientOpts2}]),
+    ssl_test_lib:check_result(Server1, ok, Client1, ok),
+
+    process_flag(trap_exit, false),
+    ssl_test_lib:close(Server1),
+    ssl_test_lib:close(Client1).
-- 
2.26.2