Skip to content

Commit

Permalink
ssl: Correct TLS-1.3 refactor so prf function works properly
Browse files Browse the repository at this point in the history
  • Loading branch information
IngelaAndin committed Dec 12, 2023
1 parent 7118194 commit 046d578
Show file tree
Hide file tree
Showing 8 changed files with 118 additions and 47 deletions.
20 changes: 13 additions & 7 deletions lib/ssl/src/tls_client_connection_1_3.erl
Original file line number Diff line number Diff line change
Expand Up @@ -463,9 +463,11 @@ wait_finished(internal,
State8 = tls_handshake_1_3:forget_master_secret(State7),
%% Configure traffic keys
State9 = ssl_record:step_encryption_state(State8),
{Record, State} = ssl_gen_statem:prepare_connection(State9,
tls_gen_connection),
tls_gen_connection:next_event(connection, Record, State,
{Record, #state{protocol_specific = PS} = State} = ssl_gen_statem:prepare_connection(State9,
tls_gen_connection),
ExporterSecret = tls_handshake_1_3:calculate_exporter_secret(State),
tls_gen_connection:next_event(connection, Record,
State#state{protocol_specific = PS#{exporter_secret => ExporterSecret}},
[{{timeout, handshake}, cancel}])
catch
{Ref, #alert{} = Alert} ->
Expand Down Expand Up @@ -505,6 +507,7 @@ handle_exlusive_1_3_hello_or_hello_retry_request(ServerHello, State0) ->

do_handle_exlusive_1_3_hello_or_hello_retry_request(
#server_hello{cipher_suite = SelectedCipherSuite,
random = Random,
session_id = SessionId,
extensions = Extensions},
#state{static_env = #static_env{host = Host,
Expand Down Expand Up @@ -575,7 +578,8 @@ do_handle_exlusive_1_3_hello_or_hello_retry_request(
#{cipher => SelectedCipherSuite,
key_share => ClientKeyShare,
session_id => SessionId,
group => SelectedGroup}),
group => SelectedGroup,
random => Random}),

%% Replace ClientHello1 with a special synthetic handshake message
State2 = tls_handshake_1_3:replace_ch1_with_message_hash(State1),
Expand Down Expand Up @@ -627,8 +631,9 @@ do_handle_exlusive_1_3_hello_or_hello_retry_request(
end.

handle_server_hello(#server_hello{cipher_suite = SelectedCipherSuite,
session_id = SessionId,
extensions = Extensions} = ServerHello,
random = Random,
session_id = SessionId,
extensions = Extensions} = ServerHello,
#state{key_share = ClientKeyShare,
ssl_options = #{ciphers := ClientCiphers,
supported_groups := ClientGroups0,
Expand Down Expand Up @@ -664,7 +669,8 @@ handle_server_hello(#server_hello{cipher_suite = SelectedCipherSuite,
key_share => ClientKeyShare,
session_id => SessionId,
group => SelectedGroup,
peer_public_key => ServerPublicKey}),
peer_public_key => ServerPublicKey,
random => Random}),

#state{connection_states = ConnectionStates} = State2,
#{security_parameters := SecParamsR} =
Expand Down
3 changes: 2 additions & 1 deletion lib/ssl/src/tls_dtls_connection.erl
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@

%% Help functions for tls|dtls_connection.erl
-export([handle_session/7,
handle_sni_extension/2]).
handle_sni_extension/2,
handle_call/4]).

%% General state handlingfor TLS-1.0 to TLS-1.2 and gen_handshake that wraps
%% handling of common state handling for handshake messages for error handling
Expand Down
27 changes: 27 additions & 0 deletions lib/ssl/src/tls_gen_connection_1_3.erl
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,33 @@ connection({call, From}, negotiated_protocol,
State) ->
ssl_gen_statem:hibernate_after(?FUNCTION_NAME, State,
[{reply, From, {ok, SelectedProtocol}}]);
connection({call, From}, {prf, _, Label, Context0, WantedLength},
#state{connection_states = ConnectionStates,
protocol_specific = PS} = State) ->
#{security_parameters := SecParams} =
ssl_record:current_connection_state(ConnectionStates, read),
#security_parameters{prf_algorithm = PRFAlgorithm,
client_random = ClientRandom,
server_random = ServerRandom} = SecParams,

Context0Size = erlang:iolist_size(Context0),
Context = case Context0 of
[client_random, server_random | Rest] ->
erlang:iolist_to_binary([ClientRandom, ServerRandom, ?unit16(Context0), Context0]);
_ ->
erlang:iolist_to_binary([?unit16(Context0), Context0])
end,

ExporterMasterSecret = maps:get(exporter_secret, PS),

%% TLS-Exporter(label, context_value, key_length) =
%% HKDF-Expand-Label(Derive-Secret(ExporterSecret, label, ""),
%% "exporter", Hash(context_value), key_length)
ExporterSecret = tls_v1:derive_secret(ExporterMasterSecret, Label, <<>>, PRFAlgorithm),
HashContext = tls_v1:transcript_hash(Context, PRFAlgorithm),
Exporter = tls_v1:hkdf_expand_label(ExporterSecret, <<"exporter">>, HashContext,
WantedLength, PRFAlgorithm),
{next_state, ?FUNCTION_NAME, State, [{reply, From, {ok, Exporter}}]};
connection(Type, Event, State) ->
ssl_gen_statem:?FUNCTION_NAME(Type, Event, State).

Expand Down
38 changes: 29 additions & 9 deletions lib/ssl/src/tls_handshake_1_3.erl
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
-export([process_certificate_request/2,
process_certificate/2,
calculate_handshake_secrets/5,
calculate_exporter_secret/1,
verify_certificate_verify/2,
validate_finished/2,
maybe_calculate_resumption_master_secret/1,
Expand Down Expand Up @@ -1089,9 +1090,6 @@ calculate_traffic_secrets(#state{
ReadKey, ReadIV, undefined,
WriteKey, WriteIV, undefined).




%% X25519, X448
calculate_shared_secret(OthersKey, MyKey, Group)
when is_binary(OthersKey) andalso is_binary(MyKey) andalso
Expand All @@ -1117,8 +1115,8 @@ maybe_calculate_resumption_master_secret(#state{
ssl_options = #{session_tickets := SessionTickets},
connection_states = ConnectionStates,
handshake_env =
#handshake_env{
tls_handshake_history = HHistory}} = State)
#handshake_env{
tls_handshake_history = HHistory}} = State)
when SessionTickets =/= disabled ->
#{security_parameters := SecParamsR} =
ssl_record:pending_connection_state(ConnectionStates, read),
Expand All @@ -1128,6 +1126,18 @@ maybe_calculate_resumption_master_secret(#state{
RMS = tls_v1:resumption_master_secret(HKDFAlgo, MasterSecret, lists:reverse(Messages0)),
update_resumption_master_secret(State, RMS).

calculate_exporter_secret(#state{
static_env = #static_env{role = Role},
connection_states = ConnectionStates,
handshake_env =
#handshake_env{
tls_handshake_history = HHistory}}) ->
#{security_parameters := SecParamsR} =
ssl_record:pending_connection_state(ConnectionStates, read),
#security_parameters{prf_algorithm = HKDFAlgo,
master_secret = MasterSecret} = SecParamsR,
Messages = get_handshake_context(Role, HHistory),
tls_v1:exporter_master_secret(HKDFAlgo, MasterSecret, lists:reverse(Messages)).

forget_master_secret(#state{connection_states =
#{pending_read := PendingRead,
Expand Down Expand Up @@ -1232,22 +1242,28 @@ update_start_state(State, Map) ->
SelectedSignAlg = maps:get(sign_alg, Map, undefined),
PeerPublicKey = maps:get(peer_public_key, Map, undefined),
ALPNProtocol = maps:get(alpn, Map, undefined),
Random = maps:get(random, Map),
update_start_state(State, Cipher, KeyShare, SessionId,
Group, SelectedSignAlg, PeerPublicKey,
ALPNProtocol).
ALPNProtocol, Random).
%%
update_start_state(#state{connection_states = ConnectionStates0,
handshake_env = #handshake_env{} = HsEnv,
static_env = #static_env{role = Role},
connection_env = CEnv,
session = Session} = State,
Cipher, KeyShare, SessionId,
Group, SelectedSignAlg, PeerPublicKey, ALPNProtocol) ->
Group, SelectedSignAlg, PeerPublicKey, ALPNProtocol, Random) ->
#{security_parameters := SecParamsR0} = PendingRead =
maps:get(pending_read, ConnectionStates0),
#{security_parameters := SecParamsW0} = PendingWrite =
maps:get(pending_write, ConnectionStates0),
SecParamsR = ssl_cipher:security_parameters_1_3(SecParamsR0, Cipher),
SecParamsW = ssl_cipher:security_parameters_1_3(SecParamsW0, Cipher),
SecParamsR1 = ssl_cipher:security_parameters_1_3(SecParamsR0, Cipher),
SecParamsW1 = ssl_cipher:security_parameters_1_3(SecParamsW0, Cipher),

SecParamsR = update_random(Role, SecParamsR1, Random),
SecParamsW = update_random(Role, SecParamsW1, Random),

ConnectionStates =
ConnectionStates0#{pending_read => PendingRead#{security_parameters => SecParamsR},
pending_write => PendingWrite#{security_parameters => SecParamsW}},
Expand All @@ -1261,6 +1277,10 @@ update_start_state(#state{connection_states = ConnectionStates0,
cipher_suite = Cipher},
connection_env = CEnv#connection_env{negotiated_version = ?TLS_1_3}}.

update_random(server, SParams, Random) ->
SParams#security_parameters{client_random = Random};
update_random(client, SParams, Random) ->
SParams#security_parameters{server_random = Random}.

update_resumption_master_secret(#state{connection_states = ConnectionStates0} = State,
ResumptionMasterSecret) ->
Expand Down
27 changes: 15 additions & 12 deletions lib/ssl/src/tls_server_connection_1_3.erl
Original file line number Diff line number Diff line change
Expand Up @@ -313,14 +313,15 @@ wait_finished(internal,
State1 = tls_handshake_1_3:calculate_traffic_secrets(State0),
State2 = tls_handshake_1_3:maybe_calculate_resumption_master_secret(State1),
State3 = tls_handshake_1_3:forget_master_secret(State2),

%% Configure traffic keys
State4 = ssl_record:step_encryption_state(State3),

State5 = maybe_send_session_ticket(State4),

{Record, State} = ssl_gen_statem:prepare_connection(State5, tls_gen_connection),
tls_gen_connection:next_event(connection, Record, State,
{Record, #state{protocol_specific = PS} = State} = ssl_gen_statem:prepare_connection(State5, tls_gen_connection),
ExporterSecret = tls_handshake_1_3:calculate_exporter_secret(State),
tls_gen_connection:next_event(connection, Record,
State#state{protocol_specific = PS#{exporter_secret => ExporterSecret}},
[{{timeout, handshake}, cancel}])
catch
{Ref, #alert{} = Alert} ->
Expand Down Expand Up @@ -387,14 +388,15 @@ handle_client_hello(ClientHello, State0) ->
end.

do_handle_client_hello(#client_hello{cipher_suites = ClientCiphers,
session_id = SessionId,
extensions = Extensions} = Hello,
#state{ssl_options = #{ciphers := ServerCiphers,
signature_algs := ServerSignAlgs,
supported_groups := ServerGroups0,
alpn_preferred_protocols := ALPNPreferredProtocols,
honor_cipher_order := HonorCipherOrder,
early_data := EarlyDataEnabled} = Opts} = State0) ->
random = Random,
session_id = SessionId,
extensions = Extensions} = Hello,
#state{ssl_options = #{ciphers := ServerCiphers,
signature_algs := ServerSignAlgs,
supported_groups := ServerGroups0,
alpn_preferred_protocols := ALPNPreferredProtocols,
honor_cipher_order := HonorCipherOrder,
early_data := EarlyDataEnabled} = Opts} = State0) ->
SNI = maps:get(sni, Extensions, undefined),
EarlyDataIndication = maps:get(early_data, Extensions, undefined),
{Ref,Maybe} = tls_gen_connection_1_3:do_maybe(),
Expand Down Expand Up @@ -480,7 +482,8 @@ do_handle_client_hello(#client_hello{cipher_suites = ClientCiphers,
group => Group,
sign_alg => SelectedSignAlg,
peer_public_key => ClientPubKey,
alpn => ALPNProtocol}),
alpn => ALPNProtocol,
random => Random}),

%% 4.1.4. Hello Retry Request
%%
Expand Down
17 changes: 13 additions & 4 deletions lib/ssl/src/tls_v1.erl
Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,13 @@

-export([derive_secret/4,
hkdf_expand_label/5,
hkdf_expand_label/6,
hkdf_extract/3,
hkdf_expand/4,
key_length/1,
key_schedule/3,
key_schedule/4,
create_info/3,
create_info/4,
external_binder_key/2,
resumption_binder_key/2,
client_early_traffic_secret/3,
Expand Down Expand Up @@ -121,18 +122,26 @@ derive_secret(Secret, Label, Messages, Algo) ->
Context::binary(), Length::integer(),
Algo::ssl:hash()) -> KeyingMaterial::binary().
hkdf_expand_label(Secret, Label0, Context, Length, Algo) ->
HkdfLabel = create_info(Label0, Context, Length),
HkdfLabel = create_info(Label0, Context, Length, <<"tls13 ">>),
hkdf_expand(Secret, HkdfLabel, Length, Algo).

-spec hkdf_expand_label(Secret::binary(), Label0::binary(),
Context::binary(), Length::integer(),
Algo::ssl:hash(), Prefix::binary()) -> KeyingMaterial::binary().
hkdf_expand_label(Secret, Label0, Context, Length, Algo , Prefix) ->
HkdfLabel = create_info(Label0, Context, Length, Prefix),
hkdf_expand(Secret, HkdfLabel, Length, Algo).


%% Create info parameter for HKDF-Expand:
%% HKDF-Expand(PRK, info, L) -> OKM
create_info(Label0, Context0, Length) ->
create_info(Label0, Context0, Length, Prefix) ->
%% struct {
%% uint16 length = Length;
%% opaque label<7..255> = "tls13 " + Label;
%% opaque context<0..255> = Context;
%% } HkdfLabel;
Label1 = << <<"tls13 ">>/binary, Label0/binary>>,
Label1 = <<Prefix/binary, Label0/binary>>,
LabelLen = byte_size(Label1),
Label = <<?BYTE(LabelLen), Label1/binary>>,
ContextLen = byte_size(Context0),
Expand Down
10 changes: 6 additions & 4 deletions lib/ssl/test/ssl_api_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -3665,10 +3665,12 @@ check_srp_in_connection_information(Socket, Username, server) ->

%% In TLS 1.3 the master_secret field is used to store multiple secrets from the key schedule and it is a tuple.
%% client_random and server_random are not used in the TLS 1.3 key schedule.
check_connection_info('tlsv1.3', [{client_random, ClientRand}, {master_secret, {master_secret, MasterSecret}}]) ->
is_binary(ClientRand) andalso is_binary(MasterSecret);
check_connection_info('tlsv1.3', [{server_random, ServerRand}, {master_secret, {master_secret, MasterSecret}}]) ->
is_binary(ServerRand) andalso is_binary(MasterSecret);
check_connection_info('tlsv1.3', [{client_random, ClientRand}, {serer_random, ServerRand},
{master_secret, {master_secret, MasterSecret}}]) ->
is_binary(ClientRand) andalso is_binary(ServerRand) andalso is_binary(MasterSecret);
check_connection_info('tlsv1.3', [{client_random, ClientRand},{server_random, ServerRand},
{master_secret, {master_secret, MasterSecret}}]) ->
is_binary(ClientRand) andalso is_binary(ServerRand) andalso is_binary(MasterSecret);
check_connection_info(_, [{client_random, ClientRand}, {server_random, ServerRand}, {master_secret, MasterSecret}]) ->
is_binary(ClientRand) andalso is_binary(ServerRand) andalso is_binary(MasterSecret);
check_connection_info(_, _) ->
Expand Down
Loading

0 comments on commit 046d578

Please sign in to comment.