aboutsummaryrefslogtreecommitdiffstats
path: root/lib/ssl/src
diff options
context:
space:
mode:
authorPéter Dimitrov <[email protected]>2019-01-30 17:03:00 +0100
committerPéter Dimitrov <[email protected]>2019-02-08 09:30:19 +0100
commitc3e98b376063718cae7937784a8bada230ff2429 (patch)
treef8a0f33db071434b1161e1eeae0b7ef822a6c8bd /lib/ssl/src
parent20b76b6c535bf0279950ea9ef5d02c52a9f8b51c (diff)
downloadotp-c3e98b376063718cae7937784a8bada230ff2429.tar.gz
otp-c3e98b376063718cae7937784a8bada230ff2429.tar.bz2
otp-c3e98b376063718cae7937784a8bada230ff2429.zip
ssl: Improve TLS 1.3 statem
- Store FinishedKey in cipher_state. - Implement state 'wait_finished'. - Calculate traffic secrets in 'wait_finished' after Finished received from client and go to state 'Connection'. - Drop 'change_cipher_spec' messages (middlebox compatibility mode). - Extend tests of 1-RTT. Change-Id: Id69619ec5da053ffaaef75378678a27afeef6916
Diffstat (limited to 'lib/ssl/src')
-rw-r--r--lib/ssl/src/ssl_cipher.hrl1
-rw-r--r--lib/ssl/src/ssl_connection.erl2
-rw-r--r--lib/ssl/src/tls_connection.erl7
-rw-r--r--lib/ssl/src/tls_connection_1_3.erl33
-rw-r--r--lib/ssl/src/tls_handshake_1_3.erl165
-rw-r--r--lib/ssl/src/tls_record.erl12
-rw-r--r--lib/ssl/src/tls_record_1_3.erl17
7 files changed, 184 insertions, 53 deletions
diff --git a/lib/ssl/src/ssl_cipher.hrl b/lib/ssl/src/ssl_cipher.hrl
index 5891f3a7cc..00822ad9de 100644
--- a/lib/ssl/src/ssl_cipher.hrl
+++ b/lib/ssl/src/ssl_cipher.hrl
@@ -47,6 +47,7 @@
-record(cipher_state, {
iv,
key,
+ finished_key,
state,
nonce,
tag_len
diff --git a/lib/ssl/src/ssl_connection.erl b/lib/ssl/src/ssl_connection.erl
index cd8baf0434..57f7027ba6 100644
--- a/lib/ssl/src/ssl_connection.erl
+++ b/lib/ssl/src/ssl_connection.erl
@@ -39,7 +39,7 @@
%% Setup
--export([connect/8, handshake/7, handshake/2, handshake/3,
+-export([connect/8, handshake/7, handshake/2, handshake/3, handle_common_event/5,
handshake_continue/3, handshake_cancel/1,
socket_control/4, socket_control/5, start_or_recv_cancel_timer/2]).
diff --git a/lib/ssl/src/tls_connection.erl b/lib/ssl/src/tls_connection.erl
index 159250e6d7..e7388f9573 100644
--- a/lib/ssl/src/tls_connection.erl
+++ b/lib/ssl/src/tls_connection.erl
@@ -50,7 +50,8 @@
handle_protocol_record/3]).
%% Handshake handling
--export([renegotiation/2, renegotiate/2, send_handshake/2,
+-export([renegotiation/2, renegotiate/2, send_handshake/2,
+ send_handshake_flight/1,
queue_handshake/2, queue_change_cipher/2,
reinit/1, reinit_handshake_data/1, select_sni_extension/1,
empty_connection_state/2]).
@@ -225,8 +226,8 @@ decode_cipher_texts(Version, Type,
{#ssl_tls{type = ?APPLICATION_DATA, fragment = Plain}, ConnectionStates} ->
decode_cipher_texts(Version, Type, CipherTexts,
ConnectionStates, Check, <<Acc/binary, Plain/binary>>);
- {#ssl_tls{type = Type, fragment = Plain}, ConnectionStates} ->
- {#ssl_tls{type = Type, fragment = Plain}, ConnectionStates, CipherTexts};
+ {#ssl_tls{type = Type0, fragment = Plain}, ConnectionStates} ->
+ {#ssl_tls{type = Type0, fragment = Plain}, ConnectionStates, CipherTexts};
#alert{} = Alert ->
{Alert, ConnectionStates0, CipherTexts}
end;
diff --git a/lib/ssl/src/tls_connection_1_3.erl b/lib/ssl/src/tls_connection_1_3.erl
index 48b3ff0d97..621e86f4b5 100644
--- a/lib/ssl/src/tls_connection_1_3.erl
+++ b/lib/ssl/src/tls_connection_1_3.erl
@@ -109,7 +109,8 @@
%% gen_statem helper functions
-export([start/4,
- negotiated/4
+ negotiated/4,
+ wait_finished/4
]).
start(internal,
@@ -135,20 +136,38 @@ start(internal,
end.
-%% TODO: remove suppression when function implemented!
--dialyzer([{nowarn_function, [negotiated/4]}, no_match]).
negotiated(internal, Map, State0, _Module) ->
case tls_handshake_1_3:do_negotiated(Map, State0) of
#alert{} = Alert ->
ssl_connection:handle_own_alert(Alert, {3,4}, negotiated, State0);
- M ->
- %% TODO: implement update_state
- %% State = update_state(State0, M),
- {next_state, wait_flight2, State0, [{next_event, internal, M}]}
+ State ->
+ {next_state, wait_finished, State, []}
end.
+wait_finished(internal,
+ #change_cipher_spec{} = ChangeCipherSpec, State0, _Module) ->
+ case tls_handshake_1_3:do_wait_finished(ChangeCipherSpec, State0) of
+ #alert{} = Alert ->
+ ssl_connection:handle_own_alert(Alert, {3,4}, wait_finished, State0);
+ State1 ->
+ {Record, State} = tls_connection:next_record(State1),
+ tls_connection:next_event(?FUNCTION_NAME, Record, State)
+ end;
+wait_finished(internal,
+ #finished{} = Finished, State0, Module) ->
+ case tls_handshake_1_3:do_wait_finished(Finished, State0) of
+ #alert{} = Alert ->
+ ssl_connection:handle_own_alert(Alert, {3,4}, finished, State0);
+ State1 ->
+ {Record, State} = ssl_connection:prepare_connection(State1, Module),
+ tls_connection:next_event(connection, Record, State)
+ end;
+wait_finished(Type, Msg, State, Connection) ->
+ ssl_connection:handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection).
+
+
update_state(#state{connection_states = ConnectionStates0,
session = Session} = State,
#{cipher := Cipher,
diff --git a/lib/ssl/src/tls_handshake_1_3.erl b/lib/ssl/src/tls_handshake_1_3.erl
index f92c54dc53..e009524f98 100644
--- a/lib/ssl/src/tls_handshake_1_3.erl
+++ b/lib/ssl/src/tls_handshake_1_3.erl
@@ -45,7 +45,8 @@
encrypted_extensions/0,
server_hello/4]).
--export([do_negotiated/2]).
+-export([do_negotiated/2,
+ do_wait_finished/2]).
%%====================================================================
%% Create handshake messages
@@ -148,12 +149,11 @@ finished(#state{connection_states = ConnectionStates,
handshake_env =
#handshake_env{
tls_handshake_history = {Messages, _}}}) ->
- #{security_parameters := SecParamsR} =
+ #{security_parameters := SecParamsR,
+ cipher_state := #cipher_state{finished_key = FinishedKey}} =
ssl_record:current_connection_state(ConnectionStates, write),
- #security_parameters{prf_algorithm = HKDFAlgo,
- master_secret = SHTS} = SecParamsR,
+ #security_parameters{prf_algorithm = HKDFAlgo} = SecParamsR,
- FinishedKey = tls_v1:finished_key(SHTS, HKDFAlgo),
VerifyData = tls_v1:finished_verify_data(FinishedKey, HKDFAlgo, Messages),
#finished{
@@ -468,12 +468,8 @@ do_negotiated(#{client_share := ClientKey,
{State1, _} = tls_connection:send_handshake(ServerHello, State0),
- {HandshakeSecret, ReadKey, ReadIV, WriteKey, WriteIV} =
- calculate_security_parameters(ClientKey, SelectedGroup, KeyShare, State1),
-
State2 =
- update_pending_connection_states(State1, HandshakeSecret,
- ReadKey, ReadIV, WriteKey, WriteIV),
+ calculate_handshake_secrets(ClientKey, SelectedGroup, KeyShare, State1),
State3 = ssl_record:step_encryption_state(State2),
@@ -498,33 +494,88 @@ do_negotiated(#{client_share := ClientKey,
%% Create Finished
Finished = finished(State6),
- %% Encode Certificate, CertifricateVerify
- {_State7, _} = tls_connection:send_handshake(Finished, State6),
+ %% Encode Finished
+ State7 = tls_connection:queue_handshake(Finished, State6),
+
+ %% Send first flight
+ {State8, _} = tls_connection:send_handshake_flight(State7),
+
+ State8
+
+ catch
+ {Ref, {state_not_implemented, State}} ->
+ %% TODO
+ ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, {state_not_implemented, State})
+ end.
- %% Send finished
- %% Next record/Next event
+do_wait_finished(#change_cipher_spec{},
+ #state{connection_states = _ConnectionStates0,
+ session = #session{session_id = _SessionId,
+ own_certificate = _OwnCert},
+ ssl_options = #ssl_options{} = _SslOpts,
+ key_share = _KeyShare,
+ handshake_env = #handshake_env{tls_handshake_history = _HHistory0},
+ private_key = _CertPrivateKey,
+ static_env = #static_env{
+ cert_db = _CertDbHandle,
+ cert_db_ref = _CertDbRef,
+ socket = _Socket,
+ transport_cb = _Transport}
+ } = State0) ->
+ %% {Ref,Maybe} = maybe(),
- Maybe(not_implemented(negotiated))
+ try
+ State0
catch
- {Ref, {state_not_implemented, State}} ->
+ {_Ref, {state_not_implemented, State}} ->
+ ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, {state_not_implemented, State})
+ end;
+do_wait_finished(#finished{},
+ #state{connection_states = _ConnectionStates0,
+ session = #session{session_id = _SessionId,
+ own_certificate = _OwnCert},
+ ssl_options = #ssl_options{} = _SslOpts,
+ key_share = _KeyShare,
+ handshake_env = #handshake_env{tls_handshake_history = _HHistory0},
+ private_key = _CertPrivateKey,
+ static_env = #static_env{
+ cert_db = _CertDbHandle,
+ cert_db_ref = _CertDbRef,
+ socket = _Socket,
+ transport_cb = _Transport}
+ } = State0) ->
+
+ %% {Ref,Maybe} = maybe(),
+
+ try
+ %% TODO: validate client Finished
+
+ State1 = calculate_traffic_secrets(State0),
+
+ %% Configure traffic keys
+ ssl_record:step_encryption_state(State1)
+
+
+ catch
+ {_Ref, {state_not_implemented, State}} ->
%% TODO
?ALERT_REC(?FATAL, ?INTERNAL_ERROR, {state_not_implemented, State})
end.
%% TODO: Remove this function!
-not_implemented(State) ->
- {error, {state_not_implemented, State}}.
+%% not_implemented(State) ->
+%% {error, {state_not_implemented, State}}.
-calculate_security_parameters(ClientKey, SelectedGroup, KeyShare,
+calculate_handshake_secrets(ClientKey, SelectedGroup, KeyShare,
#state{connection_states = ConnectionStates,
handshake_env =
#handshake_env{
- tls_handshake_history = HHistory}}) ->
+ tls_handshake_history = HHistory}} = State0) ->
#{security_parameters := SecParamsR} =
ssl_record:pending_connection_state(ConnectionStates, read),
#security_parameters{prf_algorithm = HKDFAlgo,
@@ -550,23 +601,46 @@ calculate_security_parameters(ClientKey, SelectedGroup, KeyShare,
{ReadKey, ReadIV} = tls_v1:calculate_traffic_keys(HKDFAlgo, Cipher, ClientHSTrafficSecret),
{WriteKey, WriteIV} = tls_v1:calculate_traffic_keys(HKDFAlgo, Cipher, ServerHSTrafficSecret),
- %% TODO: store all relevant secrets in state!
- {ServerHSTrafficSecret, ReadKey, ReadIV, WriteKey, WriteIV}.
+ %% Calculate Finished Keys
+ ReadFinishedKey = tls_v1:finished_key(ClientHSTrafficSecret, HKDFAlgo),
+ WriteFinishedKey = tls_v1:finished_key(ServerHSTrafficSecret, HKDFAlgo),
+
+ update_pending_connection_states(State0, HandshakeSecret,
+ ReadKey, ReadIV, ReadFinishedKey,
+ WriteKey, WriteIV, WriteFinishedKey).
+
+calculate_traffic_secrets(#state{connection_states = ConnectionStates,
+ handshake_env =
+ #handshake_env{
+ tls_handshake_history = HHistory}} = State0) ->
+ #{security_parameters := SecParamsR} =
+ ssl_record:pending_connection_state(ConnectionStates, read),
+ #security_parameters{prf_algorithm = HKDFAlgo,
+ cipher_suite = CipherSuite,
+ master_secret = HandshakeSecret} = SecParamsR,
+
+ MasterSecret =
+ tls_v1:key_schedule(master_secret, HKDFAlgo, HandshakeSecret),
- %% %% Update pending connection state
- %% PendingRead0 = ssl_record:pending_connection_state(ConnectionStates, read),
- %% PendingWrite0 = ssl_record:pending_connection_state(ConnectionStates, write),
+ {Messages0, _} = HHistory,
- %% PendingRead = update_conn_state(PendingRead0, HandshakeSecret, ReadKey, ReadIV),
- %% PendingWrite = update_conn_state(PendingWrite0, HandshakeSecret, WriteKey, WriteIV),
+ %% Drop Client Finish
+ [_|Messages] = Messages0,
+
+ %% Calculate [sender]_application_traffic_secret_0
+ ClientAppTrafficSecret0 =
+ tls_v1:client_application_traffic_secret_0(HKDFAlgo, MasterSecret, lists:reverse(Messages)),
+ ServerAppTrafficSecret0 =
+ tls_v1:server_application_traffic_secret_0(HKDFAlgo, MasterSecret, lists:reverse(Messages)),
+
+ %% Calculate traffic keys
+ #{cipher := Cipher} = ssl_cipher_format:suite_definition(CipherSuite),
+ {ReadKey, ReadIV} = tls_v1:calculate_traffic_keys(HKDFAlgo, Cipher, ClientAppTrafficSecret0),
+ {WriteKey, WriteIV} = tls_v1:calculate_traffic_keys(HKDFAlgo, Cipher, ServerAppTrafficSecret0),
- %% %% Update pending and copy to current (activate)
- %% %% All subsequent handshake messages are encrypted
- %% %% ([sender]_handshake_traffic_secret)
- %% #{current_read => PendingRead,
- %% current_write => PendingWrite,
- %% pending_read => PendingRead,
- %% pending_write => PendingWrite}.
+ update_pending_connection_states(State0, MasterSecret,
+ ReadKey, ReadIV, undefined,
+ WriteKey, WriteIV, undefined).
get_server_private_key(#key_share_server_hello{server_share = ServerShare}) ->
@@ -602,24 +676,31 @@ calculate_shared_secret(OthersKey, MyKey = #'ECPrivateKey'{}, _Group)
update_pending_connection_states(#state{connection_states =
CS = #{pending_read := PendingRead0,
pending_write := PendingWrite0}} = State,
- HandshakeSecret, ReadKey, ReadIV, WriteKey, WriteIV) ->
- PendingRead = update_connection_state(PendingRead0, HandshakeSecret, ReadKey, ReadIV),
- PendingWrite = update_connection_state(PendingWrite0, HandshakeSecret, WriteKey, WriteIV),
+ HandshakeSecret,
+ ReadKey, ReadIV, ReadFinishedKey,
+ WriteKey, WriteIV, WriteFinishedKey) ->
+ PendingRead = update_connection_state(PendingRead0, HandshakeSecret,
+ ReadKey, ReadIV, ReadFinishedKey),
+ PendingWrite = update_connection_state(PendingWrite0, HandshakeSecret,
+ WriteKey, WriteIV, WriteFinishedKey),
State#state{connection_states = CS#{pending_read => PendingRead,
- pending_write => PendingWrite}}.
+ pending_write => PendingWrite}}.
update_connection_state(ConnectionState = #{security_parameters := SecurityParameters0},
- HandshakeSecret, Key, IV) ->
+ HandshakeSecret, Key, IV, FinishedKey) ->
%% Store secret
SecurityParameters = SecurityParameters0#security_parameters{
master_secret = HandshakeSecret},
ConnectionState#{security_parameters => SecurityParameters,
- cipher_state => cipher_init(Key, IV)}.
+ cipher_state => cipher_init(Key, IV, FinishedKey)}.
-cipher_init(Key, IV) ->
- #cipher_state{key = Key, iv = IV, tag_len = 16}.
+cipher_init(Key, IV, FinishedKey) ->
+ #cipher_state{key = Key,
+ iv = IV,
+ finished_key = FinishedKey,
+ tag_len = 16}.
%% If there is no overlap between the received
diff --git a/lib/ssl/src/tls_record.erl b/lib/ssl/src/tls_record.erl
index ad2bfb7a5c..d7ea12ec3f 100644
--- a/lib/ssl/src/tls_record.erl
+++ b/lib/ssl/src/tls_record.erl
@@ -398,6 +398,18 @@ initial_connection_state(ConnectionEnd, BeastMitigation) ->
server_verify_data => undefined
}.
+%% TLS 1.3
+get_tls_records_aux({3,4} = Version, <<?BYTE(Type),?BYTE(3),?BYTE(3),
+ ?UINT16(Length), Data:Length/binary,
+ Rest/binary>> = RawTLSRecord,
+ Acc, SslOpts) when Type == ?APPLICATION_DATA;
+ Type == ?HANDSHAKE;
+ Type == ?ALERT;
+ Type == ?CHANGE_CIPHER_SPEC ->
+ ssl_logger:debug(SslOpts#ssl_options.log_level, inbound, 'tls_record', [RawTLSRecord]),
+ get_tls_records_aux(Version, Rest, [#ssl_tls{type = Type,
+ version = {3,3}, %% Use legacy version
+ fragment = Data} | Acc], SslOpts);
get_tls_records_aux({MajVer, MinVer} = Version, <<?BYTE(Type),?BYTE(MajVer),?BYTE(MinVer),
?UINT16(Length), Data:Length/binary, Rest/binary>> = RawTLSRecord,
Acc, SslOpts) when Type == ?APPLICATION_DATA;
diff --git a/lib/ssl/src/tls_record_1_3.erl b/lib/ssl/src/tls_record_1_3.erl
index 1681babed9..05acc08392 100644
--- a/lib/ssl/src/tls_record_1_3.erl
+++ b/lib/ssl/src/tls_record_1_3.erl
@@ -123,6 +123,23 @@ decode_cipher_text(#ssl_tls{type = ?OPAQUE_TYPE,
ReadState0#{sequence_number => Seq + 1}},
{decode_inner_plaintext(PlainFragment), ConnectionStates}
end;
+
+%% RFC8446 - TLS 1.3
+%% D.4. Middlebox Compatibility Mode
+%% - If not offering early data, the client sends a dummy
+%% change_cipher_spec record (see the third paragraph of Section 5)
+%% immediately before its second flight. This may either be before
+%% its second ClientHello or before its encrypted handshake flight.
+%% If offering early data, the record is placed immediately after the
+%% first ClientHello.
+decode_cipher_text(#ssl_tls{type = ?CHANGE_CIPHER_SPEC,
+ version = ?LEGACY_VERSION,
+ fragment = <<1>>},
+ ConnectionStates0) ->
+ {#ssl_tls{type = ?CHANGE_CIPHER_SPEC,
+ version = {3,4}, %% Internally use real version
+ fragment = <<1>>}, ConnectionStates0};
+
decode_cipher_text(#ssl_tls{type = Type,
version = ?LEGACY_VERSION,
fragment = CipherFragment},