diff options
Diffstat (limited to 'lib/ssl/src/dtls_record.erl')
-rw-r--r-- | lib/ssl/src/dtls_record.erl | 340 |
1 files changed, 191 insertions, 149 deletions
diff --git a/lib/ssl/src/dtls_record.erl b/lib/ssl/src/dtls_record.erl index 8a6e2d315c..2b42ddf9b9 100644 --- a/lib/ssl/src/dtls_record.erl +++ b/lib/ssl/src/dtls_record.erl @@ -36,7 +36,9 @@ -export([decode_cipher_text/2]). %% Encoding --export([encode_plain_text/4, encode_tls_cipher_text/5, encode_change_cipher_spec/2]). +-export([encode_handshake/4, encode_alert_record/3, + encode_change_cipher_spec/3, encode_data/3]). +-export([encode_plain_text/5]). %% Protocol version handling -export([protocol_version/1, lowest_protocol_version/1, lowest_protocol_version/2, @@ -44,9 +46,9 @@ is_higher/2, supported_protocol_versions/0, is_acceptable_version/2]). -%% DTLS Epoch handling --export([init_connection_state_seq/2, current_connection_state_epoch/2, - set_connection_state_by_epoch/3, connection_state_by_epoch/3]). +-export([save_current_connection_state/2, next_epoch/2]). + +-export([init_connection_state_seq/2, current_connection_state_epoch/2]). -export_type([dtls_version/0, dtls_atom_version/0]). @@ -68,16 +70,64 @@ %%-------------------------------------------------------------------- init_connection_states(Role, BeastMitigation) -> ConnectionEnd = ssl_record:record_protocol_role(Role), - Current = initial_connection_state(ConnectionEnd, BeastMitigation), - Pending = ssl_record:empty_connection_state(ConnectionEnd, BeastMitigation), - #{write_msg_seq => 0, - prvious_read => undefined, + Initial = initial_connection_state(ConnectionEnd, BeastMitigation), + Current = Initial#{epoch := 0}, + InitialPending = ssl_record:empty_connection_state(ConnectionEnd, BeastMitigation), + Pending = InitialPending#{epoch => undefined}, + #{saved_read => Current, current_read => Current, pending_read => Pending, - prvious_write => undefined, + saved_write => Current, current_write => Current, pending_write => Pending}. - + +%%-------------------------------------------------------------------- +-spec save_current_connection_state(ssl_record:connection_states(), read | write) -> + ssl_record:connection_states(). +%% +%% Description: Returns the instance of the connection_state map +%% where the current read|write state has been copied to the save state. +%%-------------------------------------------------------------------- +save_current_connection_state(#{current_read := Current} = States, read) -> + States#{saved_read := Current}; + +save_current_connection_state(#{current_write := Current} = States, write) -> + States#{saved_write := Current}. + +next_epoch(#{pending_read := Pending, + current_read := #{epoch := Epoch}} = States, read) -> + States#{pending_read := Pending#{epoch := Epoch + 1}}; + +next_epoch(#{pending_write := Pending, + current_write := #{epoch := Epoch}} = States, write) -> + States#{pending_write := Pending#{epoch := Epoch + 1}}. + +get_connection_state_by_epoch(Epoch, #{current_write := #{epoch := Epoch} = Current}, + write) -> + Current; +get_connection_state_by_epoch(Epoch, #{saved_write := #{epoch := Epoch} = Saved}, + write) -> + Saved; +get_connection_state_by_epoch(Epoch, #{current_read := #{epoch := Epoch} = Current}, + read) -> + Current; +get_connection_state_by_epoch(Epoch, #{saved_read := #{epoch := Epoch} = Saved}, + read) -> + Saved. + +set_connection_state_by_epoch(WriteState, Epoch, #{current_write := #{epoch := Epoch}} = States, + write) -> + States#{current_write := WriteState}; +set_connection_state_by_epoch(WriteState, Epoch, #{saved_write := #{epoch := Epoch}} = States, + write) -> + States#{saved_write := WriteState}; +set_connection_state_by_epoch(ReadState, Epoch, #{current_read := #{epoch := Epoch}} = States, + read) -> + States#{current_read := ReadState}; +set_connection_state_by_epoch(ReadState, Epoch, #{saved_read := #{epoch := Epoch}} = States, + read) -> + States#{saved_read := ReadState}. + %%-------------------------------------------------------------------- -spec get_dtls_records(binary(), binary()) -> {[binary()], binary()} | #alert{}. %% @@ -140,98 +190,57 @@ get_dtls_records_aux(Data, Acc) -> ?ALERT_REC(?FATAL, ?UNEXPECTED_MESSAGE) end. -encode_plain_text(Type, Version, Data, - #{current_write := - #{epoch := Epoch, - sequence_number := Seq, - compression_state := CompS0, - security_parameters := - #security_parameters{ - cipher_type = ?AEAD, - compression_algorithm = CompAlg} - }= WriteState0} = ConnectionStates) -> - {Comp, CompS1} = ssl_record:compress(CompAlg, Data, CompS0), - WriteState1 = WriteState0#{compression_state => CompS1}, - AAD = calc_aad(Type, Version, Epoch, Seq), - {CipherFragment, WriteState} = ssl_record:cipher_aead(dtls_v1:corresponding_tls_version(Version), - Comp, WriteState1, AAD), - CipherText = encode_tls_cipher_text(Type, Version, Epoch, Seq, CipherFragment), - {CipherText, ConnectionStates#{current_write => WriteState#{sequence_number => Seq +1}}}; - -encode_plain_text(Type, Version, Data, - #{current_write := - #{epoch := Epoch, - sequence_number := Seq, - compression_state := CompS0, - security_parameters := - #security_parameters{compression_algorithm = CompAlg} - }= WriteState0} = ConnectionStates) -> - {Comp, CompS1} = ssl_record:compress(CompAlg, Data, CompS0), - WriteState1 = WriteState0#{compression_state => CompS1}, - MacHash = calc_mac_hash(WriteState1, Type, Version, Epoch, Seq, Comp), - {CipherFragment, WriteState} = ssl_record:cipher(dtls_v1:corresponding_tls_version(Version), - Comp, WriteState1, MacHash), - CipherText = encode_tls_cipher_text(Type, Version, Epoch, Seq, CipherFragment), - {CipherText, ConnectionStates#{current_write => WriteState#{sequence_number => Seq +1}}}. +%%-------------------------------------------------------------------- +-spec encode_handshake(iolist(), dtls_version(), integer(), ssl_record:connection_states()) -> + {iolist(), ssl_record:connection_states()}. +% +%% Description: Encodes a handshake message to send on the ssl-socket. +%%-------------------------------------------------------------------- +encode_handshake(Frag, Version, Epoch, ConnectionStates) -> + encode_plain_text(?HANDSHAKE, Version, Epoch, Frag, ConnectionStates). -decode_cipher_text(#ssl_tls{type = Type, version = Version, - epoch = Epoch, - sequence_number = Seq, - fragment = CipherFragment} = CipherText, - #{current_read := - #{compression_state := CompressionS0, - security_parameters := - #security_parameters{ - cipher_type = ?AEAD, - compression_algorithm = CompAlg} - } = ReadState0} = ConnnectionStates0) -> - AAD = calc_aad(Type, Version, Epoch, Seq), - case ssl_record:decipher_aead(dtls_v1:corresponding_tls_version(Version), - CipherFragment, ReadState0, AAD) of - {PlainFragment, ReadState1} -> - {Plain, CompressionS1} = ssl_record:uncompress(CompAlg, - PlainFragment, CompressionS0), - ConnnectionStates = ConnnectionStates0#{ - current_read => ReadState1#{ - compression_state => CompressionS1}}, - {CipherText#ssl_tls{fragment = Plain}, ConnnectionStates}; - #alert{} = Alert -> - Alert - end; -decode_cipher_text(#ssl_tls{type = Type, version = Version, - epoch = Epoch, - sequence_number = Seq, - fragment = CipherFragment} = CipherText, - #{current_read := - #{compression_state := CompressionS0, - security_parameters := - #security_parameters{ - compression_algorithm = CompAlg} - } = ReadState0}= ConnnectionStates0) -> - {PlainFragment, Mac, ReadState1} = ssl_record:decipher(dtls_v1:corresponding_tls_version(Version), - CipherFragment, ReadState0, true), - MacHash = calc_mac_hash(ReadState1, Type, Version, Epoch, Seq, PlainFragment), - case ssl_record:is_correct_mac(Mac, MacHash) of - true -> - {Plain, CompressionS1} = ssl_record:uncompress(CompAlg, - PlainFragment, CompressionS0), - ConnnectionStates = ConnnectionStates0#{ - current_read => ReadState1#{ - compression_state => CompressionS1}}, - {CipherText#ssl_tls{fragment = Plain}, ConnnectionStates}; - false -> - ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC) - end. +%%-------------------------------------------------------------------- +-spec encode_alert_record(#alert{}, dtls_version(), ssl_record:connection_states()) -> + {iolist(), ssl_record:connection_states()}. +%% +%% Description: Encodes an alert message to send on the ssl-socket. +%%-------------------------------------------------------------------- +encode_alert_record(#alert{level = Level, description = Description}, + Version, ConnectionStates) -> + #{epoch := Epoch} = ssl_record:current_connection_state(ConnectionStates, write), + encode_plain_text(?ALERT, Version, Epoch, <<?BYTE(Level), ?BYTE(Description)>>, + ConnectionStates). %%-------------------------------------------------------------------- --spec encode_change_cipher_spec(dtls_version(), ssl_record:connection_states()) -> +-spec encode_change_cipher_spec(dtls_version(), integer(), ssl_record:connection_states()) -> {iolist(), ssl_record:connection_states()}. %% %% Description: Encodes a change_cipher_spec-message to send on the ssl socket. %%-------------------------------------------------------------------- -encode_change_cipher_spec(Version, ConnectionStates) -> - encode_plain_text(?CHANGE_CIPHER_SPEC, Version, <<1:8>>, ConnectionStates). +encode_change_cipher_spec(Version, Epoch, ConnectionStates) -> + encode_plain_text(?CHANGE_CIPHER_SPEC, Version, Epoch, ?byte(?CHANGE_CIPHER_SPEC_PROTO), ConnectionStates). + +%%-------------------------------------------------------------------- +-spec encode_data(binary(), dtls_version(), ssl_record:connection_states()) -> + {iolist(),ssl_record:connection_states()}. +%% +%% Description: Encodes data to send on the ssl-socket. +%%-------------------------------------------------------------------- +encode_data(Data, Version, ConnectionStates) -> + #{epoch := Epoch} = ssl_record:current_connection_state(ConnectionStates, write), + encode_plain_text(?APPLICATION_DATA, Version, Epoch, Data, ConnectionStates). + +encode_plain_text(Type, Version, Epoch, Data, ConnectionStates) -> + Write0 = get_connection_state_by_epoch(Epoch, ConnectionStates, write), + {CipherFragment, Write1} = encode_plain_text(Type, Version, Data, Write0), + {CipherText, Write} = encode_dtls_cipher_text(Type, Version, CipherFragment, Write1), + {CipherText, set_connection_state_by_epoch(Write, Epoch, ConnectionStates, write)}. + + +decode_cipher_text(#ssl_tls{epoch = Epoch} = CipherText, ConnnectionStates0) -> + ReadState = get_connection_state_by_epoch(Epoch, ConnnectionStates0, read), + decode_cipher_text(CipherText, ReadState, ConnnectionStates0). %%-------------------------------------------------------------------- -spec protocol_version(dtls_atom_version() | dtls_version()) -> @@ -373,12 +382,11 @@ is_acceptable_version(Version, Versions) -> %% This is only valid for DTLS in the first client_hello %%-------------------------------------------------------------------- init_connection_state_seq({254, _}, - #{current_read := #{epoch := 0} = Read, - current_write := #{epoch := 0} = Write} = CS0) -> - Seq = maps:get(sequence_number, Read), - CS0#{current_write => Write#{sequence_number => Seq}}; -init_connection_state_seq(_, CS) -> - CS. + #{current_read := #{epoch := 0, sequence_number := Seq}, + current_write := #{epoch := 0} = Write} = ConnnectionStates0) -> + ConnnectionStates0#{current_write => Write#{sequence_number => Seq}}; +init_connection_state_seq(_, ConnnectionStates) -> + ConnnectionStates. %%-------------------------------------------------------- -spec current_connection_state_epoch(ssl_record:connection_states(), read | write) -> @@ -387,49 +395,12 @@ init_connection_state_seq(_, CS) -> %% Description: Returns the epoch the connection_state record %% that is currently defined as the current conection state. %%-------------------------------------------------------------------- -current_connection_state_epoch(#{current_read := Current}, +current_connection_state_epoch(#{current_read := #{epoch := Epoch}}, read) -> - maps:get(epoch, Current); -current_connection_state_epoch(#{current_write := Current}, + Epoch; +current_connection_state_epoch(#{current_write := #{epoch := Epoch}}, write) -> - maps:get(epoch, Current). - -%%-------------------------------------------------------------------- - --spec connection_state_by_epoch(ssl_record:connection_states(), integer(), read | write) -> - ssl_record:connection_state(). -%% -%% Description: Returns the instance of the connection_state record -%% that is defined by the Epoch. -%%-------------------------------------------------------------------- -connection_state_by_epoch(#{current_read := #{epoch := Epoch}} = CS, Epoch, read) -> - CS; -connection_state_by_epoch(#{pending_read := #{epoch := Epoch}} = CS, Epoch, read) -> - CS; -connection_state_by_epoch(#{current_write := #{epoch := Epoch}} = CS, Epoch, write) -> - CS; -connection_state_by_epoch(#{pending_write := #{epoch := Epoch}} = CS, Epoch, write) -> - CS. -%%-------------------------------------------------------------------- --spec set_connection_state_by_epoch(ssl_record:connection_states(), - ssl_record:connection_state(), read | write) - -> ssl_record:connection_states(). -%% -%% Description: Returns the instance of the connection_state record -%% that is defined by the Epoch. -%%-------------------------------------------------------------------- -set_connection_state_by_epoch(#{current_read := #{epoch := Epoch}} = ConnectionStates0, - NewCS = #{epoch := Epoch}, read) -> - ConnectionStates0#{current_read => NewCS}; -set_connection_state_by_epoch(#{pending_read := #{epoch := Epoch}} = ConnectionStates0, - NewCS = #{epoch := Epoch}, read) -> - ConnectionStates0#{pending_read => NewCS}; -set_connection_state_by_epoch(#{current_write := #{epoch := Epoch}} = ConnectionStates0, - NewCS = #{epoch := Epoch}, write) -> - ConnectionStates0#{current_write => NewCS}; -set_connection_state_by_epoch(#{pending_write := #{epoch := Epoch}} = ConnectionStates0, -NewCS = #{epoch := Epoch}, write) -> - ConnectionStates0#{pending_write => NewCS}. + Epoch. %%-------------------------------------------------------------------- %%% Internal functions @@ -437,8 +408,8 @@ NewCS = #{epoch := Epoch}, write) -> initial_connection_state(ConnectionEnd, BeastMitigation) -> #{security_parameters => ssl_record:initial_security_params(ConnectionEnd), - epoch => 0, - sequence_number => 1, + epoch => undefined, + sequence_number => 0, beast_mitigation => BeastMitigation, compression_state => undefined, cipher_state => undefined, @@ -458,14 +429,85 @@ highest_list_protocol_version(Ver, []) -> highest_list_protocol_version(Ver1, [Ver2 | Rest]) -> highest_list_protocol_version(highest_protocol_version(Ver1, Ver2), Rest). -encode_tls_cipher_text(Type, {MajVer, MinVer}, Epoch, Seq, Fragment) -> +encode_dtls_cipher_text(Type, {MajVer, MinVer}, Fragment, + #{epoch := Epoch, sequence_number := Seq} = WriteState) -> Length = erlang:iolist_size(Fragment), - [<<?BYTE(Type), ?BYTE(MajVer), ?BYTE(MinVer), ?UINT16(Epoch), - ?UINT48(Seq), ?UINT16(Length)>>, Fragment]. + {[<<?BYTE(Type), ?BYTE(MajVer), ?BYTE(MinVer), ?UINT16(Epoch), + ?UINT48(Seq), ?UINT16(Length)>>, Fragment], + WriteState#{sequence_number => Seq + 1}}. + +encode_plain_text(Type, Version, Data, #{compression_state := CompS0, + epoch := Epoch, + sequence_number := Seq, + security_parameters := + #security_parameters{ + cipher_type = ?AEAD, + compression_algorithm = CompAlg} + } = WriteState0) -> + {Comp, CompS1} = ssl_record:compress(CompAlg, Data, CompS0), + WriteState1 = WriteState0#{compression_state => CompS1}, + AAD = calc_aad(Type, Version, Epoch, Seq), + ssl_record:cipher_aead(dtls_v1:corresponding_tls_version(Version), Comp, WriteState1, AAD); +encode_plain_text(Type, Version, Data, #{compression_state := CompS0, + epoch := Epoch, + sequence_number := Seq, + security_parameters := + #security_parameters{compression_algorithm = CompAlg} + }= WriteState0) -> + {Comp, CompS1} = ssl_record:compress(CompAlg, Data, CompS0), + WriteState1 = WriteState0#{compression_state => CompS1}, + MacHash = calc_mac_hash(Type, Version, WriteState1, Epoch, Seq, Comp), + ssl_record:cipher(dtls_v1:corresponding_tls_version(Version), Comp, WriteState1, MacHash). + +decode_cipher_text(#ssl_tls{type = Type, version = Version, + epoch = Epoch, + sequence_number = Seq, + fragment = CipherFragment} = CipherText, + #{compression_state := CompressionS0, + security_parameters := + #security_parameters{ + cipher_type = ?AEAD, + compression_algorithm = CompAlg}} = ReadState0, + ConnnectionStates0) -> + AAD = calc_aad(Type, Version, Epoch, Seq), + case ssl_record:decipher_aead(dtls_v1:corresponding_tls_version(Version), + CipherFragment, ReadState0, AAD) of + {PlainFragment, ReadState1} -> + {Plain, CompressionS1} = ssl_record:uncompress(CompAlg, + PlainFragment, CompressionS0), + ReadState = ReadState1#{compression_state => CompressionS1}, + ConnnectionStates = set_connection_state_by_epoch(ReadState, Epoch, ConnnectionStates0, read), + {CipherText#ssl_tls{fragment = Plain}, ConnnectionStates}; + #alert{} = Alert -> + Alert + end; +decode_cipher_text(#ssl_tls{type = Type, version = Version, + epoch = Epoch, + sequence_number = Seq, + fragment = CipherFragment} = CipherText, + #{compression_state := CompressionS0, + security_parameters := + #security_parameters{ + compression_algorithm = CompAlg}} = ReadState0, + ConnnectionStates0) -> + {PlainFragment, Mac, ReadState1} = ssl_record:decipher(dtls_v1:corresponding_tls_version(Version), + CipherFragment, ReadState0, true), + MacHash = calc_mac_hash(Type, Version, ReadState1, Epoch, Seq, PlainFragment), + case ssl_record:is_correct_mac(Mac, MacHash) of + true -> + {Plain, CompressionS1} = ssl_record:uncompress(CompAlg, + PlainFragment, CompressionS0), + + ReadState = ReadState1#{compression_state => CompressionS1}, + ConnnectionStates = set_connection_state_by_epoch(ReadState, Epoch, ConnnectionStates0, read), + {CipherText#ssl_tls{fragment = Plain}, ConnnectionStates}; + false -> + ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC) + end. -calc_mac_hash(#{mac_secret := MacSecret, - security_parameters := #security_parameters{mac_algorithm = MacAlg}}, - Type, Version, Epoch, SeqNo, Fragment) -> +calc_mac_hash(Type, Version, #{mac_secret := MacSecret, + security_parameters := #security_parameters{mac_algorithm = MacAlg}}, + Epoch, SeqNo, Fragment) -> Length = erlang:iolist_size(Fragment), NewSeq = (Epoch bsl 48) + SeqNo, mac_hash(Version, MacAlg, MacSecret, NewSeq, Type, |