diff options
author | Péter Dimitrov <[email protected]> | 2019-01-30 17:03:00 +0100 |
---|---|---|
committer | Péter Dimitrov <[email protected]> | 2019-02-08 09:30:19 +0100 |
commit | c3e98b376063718cae7937784a8bada230ff2429 (patch) | |
tree | f8a0f33db071434b1161e1eeae0b7ef822a6c8bd /lib/ssl/src | |
parent | 20b76b6c535bf0279950ea9ef5d02c52a9f8b51c (diff) | |
download | otp-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.hrl | 1 | ||||
-rw-r--r-- | lib/ssl/src/ssl_connection.erl | 2 | ||||
-rw-r--r-- | lib/ssl/src/tls_connection.erl | 7 | ||||
-rw-r--r-- | lib/ssl/src/tls_connection_1_3.erl | 33 | ||||
-rw-r--r-- | lib/ssl/src/tls_handshake_1_3.erl | 165 | ||||
-rw-r--r-- | lib/ssl/src/tls_record.erl | 12 | ||||
-rw-r--r-- | lib/ssl/src/tls_record_1_3.erl | 17 |
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}, |