diff options
Diffstat (limited to 'lib/ssl/src/ssl_record.erl')
| -rw-r--r-- | lib/ssl/src/ssl_record.erl | 618 |
1 files changed, 189 insertions, 429 deletions
diff --git a/lib/ssl/src/ssl_record.erl b/lib/ssl/src/ssl_record.erl index 173b9611c6..018c8befe0 100644 --- a/lib/ssl/src/ssl_record.erl +++ b/lib/ssl/src/ssl_record.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2012. All Rights Reserved. +%% Copyright Ericsson AB 2013-2013. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -15,54 +15,40 @@ %% under the License. %% %% %CopyrightEnd% -%% -%% %%---------------------------------------------------------------------- -%% Purpose: Help functions for handling the SSL-Record protocol -%% +%% Purpose: Handle TLS/SSL/DTLS record protocol. Note epoch is only +%% used by DTLS but handled here so we can avoid code duplication. %%---------------------------------------------------------------------- -module(ssl_record). -include("ssl_record.hrl"). -include("ssl_internal.hrl"). --include("ssl_alert.hrl"). --include("ssl_handshake.hrl"). -include("ssl_cipher.hrl"). +-include("ssl_alert.hrl"). %% Connection state handling --export([init_connection_states/1, - current_connection_state/2, pending_connection_state/2, - update_security_params/3, +-export([init_connection_states/1, + current_connection_state/2, pending_connection_state/2, + activate_pending_connection_state/2, + set_security_params/3, set_mac_secret/4, - set_master_secret/2, - activate_pending_connection_state/2, + set_master_secret/2, set_pending_cipher_state/4, set_renegotiation_flag/2, set_client_verify_data/3, set_server_verify_data/3]). -%% Handling of incoming data --export([get_tls_records/2]). - %% Encoding records -export([encode_handshake/3, encode_alert_record/3, encode_change_cipher_spec/2, encode_data/3]). -%% Decoding --export([decode_cipher_text/2]). - -%% Misc. --export([protocol_version/1, lowest_protocol_version/2, - highest_protocol_version/1, supported_protocol_versions/0, - is_acceptable_version/1]). - --export([compressions/0]). +%% Compression +-export([compress/3, uncompress/3, compressions/0]). --compile(inline). - --define(INITIAL_BYTES, 5). +%% Payload encryption/decryption +-export([cipher/4, decipher/3, is_correct_mac/2]). %%==================================================================== %% Internal application API @@ -72,7 +58,7 @@ -spec init_connection_states(client | server) -> #connection_states{}. %% %% Description: Creates a connection_states record with appropriate -%% values for the initial SSL connection setup. +%% values for the initial SSL connection setup. %%-------------------------------------------------------------------- init_connection_states(Role) -> ConnectionEnd = record_protocol_role(Role), @@ -90,7 +76,7 @@ init_connection_states(Role) -> %% %% Description: Returns the instance of the connection_state record %% that is currently defined as the current conection state. -%%-------------------------------------------------------------------- +%%-------------------------------------------------------------------- current_connection_state(#connection_states{current_read = Current}, read) -> Current; @@ -100,38 +86,77 @@ current_connection_state(#connection_states{current_write = Current}, %%-------------------------------------------------------------------- -spec pending_connection_state(#connection_states{}, read | write) -> - #connection_state{}. + term(). %% %% Description: Returns the instance of the connection_state record %% that is currently defined as the pending conection state. -%%-------------------------------------------------------------------- -pending_connection_state(#connection_states{pending_read = Pending}, +%%-------------------------------------------------------------------- +pending_connection_state(#connection_states{pending_read = Pending}, read) -> Pending; pending_connection_state(#connection_states{pending_write = Pending}, write) -> Pending. + %%-------------------------------------------------------------------- --spec update_security_params(#security_parameters{}, #security_parameters{}, +-spec activate_pending_connection_state(#connection_states{}, read | write) -> + #connection_states{}. +%% +%% Description: Creates a new instance of the connection_states record +%% where the pending state of <Type> has been activated. +%%-------------------------------------------------------------------- +activate_pending_connection_state(States = + #connection_states{current_read = Current, + pending_read = Pending}, + read) -> + NewCurrent = Pending#connection_state{epoch = dtls_next_epoch(Current), + sequence_number = 0}, + SecParams = Pending#connection_state.security_parameters, + ConnectionEnd = SecParams#security_parameters.connection_end, + EmptyPending = empty_connection_state(ConnectionEnd), + SecureRenegotation = NewCurrent#connection_state.secure_renegotiation, + NewPending = EmptyPending#connection_state{secure_renegotiation = SecureRenegotation}, + States#connection_states{current_read = NewCurrent, + pending_read = NewPending + }; + +activate_pending_connection_state(States = + #connection_states{current_write = Current, + pending_write = Pending}, + write) -> + NewCurrent = Pending#connection_state{epoch = dtls_next_epoch(Current), + sequence_number = 0}, + SecParams = Pending#connection_state.security_parameters, + ConnectionEnd = SecParams#security_parameters.connection_end, + EmptyPending = empty_connection_state(ConnectionEnd), + SecureRenegotation = NewCurrent#connection_state.secure_renegotiation, + NewPending = EmptyPending#connection_state{secure_renegotiation = SecureRenegotation}, + States#connection_states{current_write = NewCurrent, + pending_write = NewPending + }. + + +%%-------------------------------------------------------------------- +-spec set_security_params(#security_parameters{}, #security_parameters{}, #connection_states{}) -> #connection_states{}. %% %% Description: Creates a new instance of the connection_states record %% where the pending states gets its security parameters updated. -%%-------------------------------------------------------------------- -update_security_params(ReadParams, WriteParams, States = +%%-------------------------------------------------------------------- +set_security_params(ReadParams, WriteParams, States = #connection_states{pending_read = Read, - pending_write = Write}) -> + pending_write = Write}) -> States#connection_states{pending_read = - Read#connection_state{security_parameters = + Read#connection_state{security_parameters = ReadParams}, - pending_write = - Write#connection_state{security_parameters = + pending_write = + Write#connection_state{security_parameters = WriteParams} }. %%-------------------------------------------------------------------- --spec set_mac_secret(binary(), binary(), client | server, - #connection_states{}) -> #connection_states{}. +-spec set_mac_secret(binary(), binary(), client | server, + #connection_states{}) -> #connection_states{}. %% %% Description: update the mac_secret field in pending connection states %%-------------------------------------------------------------------- @@ -156,7 +181,7 @@ set_mac_secret(ReadMacSecret, WriteMacSecret, %%-------------------------------------------------------------------- set_master_secret(MasterSecret, States = #connection_states{pending_read = Read, - pending_write = Write}) -> + pending_write = Write}) -> ReadSecPar = Read#connection_state.security_parameters, Read1 = Read#connection_state{ security_parameters = ReadSecPar#security_parameters{ @@ -176,15 +201,15 @@ set_renegotiation_flag(Flag, #connection_states{ current_read = CurrentRead0, current_write = CurrentWrite0, pending_read = PendingRead0, - pending_write = PendingWrite0} + pending_write = PendingWrite0} = ConnectionStates) -> CurrentRead = CurrentRead0#connection_state{secure_renegotiation = Flag}, CurrentWrite = CurrentWrite0#connection_state{secure_renegotiation = Flag}, PendingRead = PendingRead0#connection_state{secure_renegotiation = Flag}, PendingWrite = PendingWrite0#connection_state{secure_renegotiation = Flag}, - ConnectionStates#connection_states{current_read = CurrentRead, - current_write = CurrentWrite, - pending_read = PendingRead, + ConnectionStates#connection_states{current_read = CurrentRead, + current_write = CurrentWrite, + pending_read = PendingRead, pending_write = PendingWrite}. %%-------------------------------------------------------------------- @@ -192,27 +217,27 @@ set_renegotiation_flag(Flag, #connection_states{ binary(), #connection_states{})-> #connection_states{}. %% -%% Description: Set verify data in connection states. +%% Description: Set verify data in connection states. %%-------------------------------------------------------------------- -set_client_verify_data(current_read, Data, +set_client_verify_data(current_read, Data, #connection_states{current_read = CurrentRead0, - pending_write = PendingWrite0} + pending_write = PendingWrite0} = ConnectionStates) -> CurrentRead = CurrentRead0#connection_state{client_verify_data = Data}, PendingWrite = PendingWrite0#connection_state{client_verify_data = Data}, ConnectionStates#connection_states{current_read = CurrentRead, pending_write = PendingWrite}; -set_client_verify_data(current_write, Data, +set_client_verify_data(current_write, Data, #connection_states{pending_read = PendingRead0, - current_write = CurrentWrite0} + current_write = CurrentWrite0} = ConnectionStates) -> PendingRead = PendingRead0#connection_state{client_verify_data = Data}, CurrentWrite = CurrentWrite0#connection_state{client_verify_data = Data}, ConnectionStates#connection_states{pending_read = PendingRead, current_write = CurrentWrite}; -set_client_verify_data(current_both, Data, +set_client_verify_data(current_both, Data, #connection_states{current_read = CurrentRead0, - current_write = CurrentWrite0} + current_write = CurrentWrite0} = ConnectionStates) -> CurrentRead = CurrentRead0#connection_state{client_verify_data = Data}, CurrentWrite = CurrentWrite0#connection_state{client_verify_data = Data}, @@ -225,66 +250,32 @@ set_client_verify_data(current_both, Data, %% %% Description: Set verify data in pending connection states. %%-------------------------------------------------------------------- -set_server_verify_data(current_write, Data, +set_server_verify_data(current_write, Data, #connection_states{pending_read = PendingRead0, - current_write = CurrentWrite0} + current_write = CurrentWrite0} = ConnectionStates) -> PendingRead = PendingRead0#connection_state{server_verify_data = Data}, CurrentWrite = CurrentWrite0#connection_state{server_verify_data = Data}, ConnectionStates#connection_states{pending_read = PendingRead, current_write = CurrentWrite}; -set_server_verify_data(current_read, Data, +set_server_verify_data(current_read, Data, #connection_states{current_read = CurrentRead0, - pending_write = PendingWrite0} + pending_write = PendingWrite0} = ConnectionStates) -> CurrentRead = CurrentRead0#connection_state{server_verify_data = Data}, PendingWrite = PendingWrite0#connection_state{server_verify_data = Data}, ConnectionStates#connection_states{current_read = CurrentRead, pending_write = PendingWrite}; -set_server_verify_data(current_both, Data, +set_server_verify_data(current_both, Data, #connection_states{current_read = CurrentRead0, - current_write = CurrentWrite0} + current_write = CurrentWrite0} = ConnectionStates) -> CurrentRead = CurrentRead0#connection_state{server_verify_data = Data}, CurrentWrite = CurrentWrite0#connection_state{server_verify_data = Data}, ConnectionStates#connection_states{current_read = CurrentRead, current_write = CurrentWrite}. - -%%-------------------------------------------------------------------- --spec activate_pending_connection_state(#connection_states{}, read | write) -> - #connection_states{}. -%% -%% Description: Creates a new instance of the connection_states record -%% where the pending state of <Type> has been activated. -%%-------------------------------------------------------------------- -activate_pending_connection_state(States = - #connection_states{pending_read = Pending}, - read) -> - NewCurrent = Pending#connection_state{sequence_number = 0}, - SecParams = Pending#connection_state.security_parameters, - ConnectionEnd = SecParams#security_parameters.connection_end, - EmptyPending = empty_connection_state(ConnectionEnd), - SecureRenegotation = NewCurrent#connection_state.secure_renegotiation, - NewPending = EmptyPending#connection_state{secure_renegotiation = SecureRenegotation}, - States#connection_states{current_read = NewCurrent, - pending_read = NewPending - }; - -activate_pending_connection_state(States = - #connection_states{pending_write = Pending}, - write) -> - NewCurrent = Pending#connection_state{sequence_number = 0}, - SecParams = Pending#connection_state.security_parameters, - ConnectionEnd = SecParams#security_parameters.connection_end, - EmptyPending = empty_connection_state(ConnectionEnd), - SecureRenegotation = NewCurrent#connection_state.secure_renegotiation, - NewPending = EmptyPending#connection_state{secure_renegotiation = SecureRenegotation}, - States#connection_states{current_write = NewCurrent, - pending_write = NewPending - }. - %%-------------------------------------------------------------------- -spec set_pending_cipher_state(#connection_states{}, #cipher_state{}, #cipher_state{}, client | server) -> @@ -306,227 +297,6 @@ set_pending_cipher_state(#connection_states{pending_read = Read, pending_read = Read#connection_state{cipher_state = ServerState}, pending_write = Write#connection_state{cipher_state = ClientState}}. -%%-------------------------------------------------------------------- --spec get_tls_records(binary(), binary()) -> {[binary()], binary()} | #alert{}. -%% -%% Description: Given old buffer and new data from TCP, packs up a records -%% and returns it as a list of tls_compressed binaries also returns leftover -%% data -%%-------------------------------------------------------------------- -get_tls_records(Data, <<>>) -> - get_tls_records_aux(Data, []); -get_tls_records(Data, Buffer) -> - get_tls_records_aux(list_to_binary([Buffer, Data]), []). - -get_tls_records_aux(<<?BYTE(?APPLICATION_DATA),?BYTE(MajVer),?BYTE(MinVer), - ?UINT16(Length), Data:Length/binary, Rest/binary>>, - Acc) -> - get_tls_records_aux(Rest, [#ssl_tls{type = ?APPLICATION_DATA, - version = {MajVer, MinVer}, - fragment = Data} | Acc]); -get_tls_records_aux(<<?BYTE(?HANDSHAKE),?BYTE(MajVer),?BYTE(MinVer), - ?UINT16(Length), - Data:Length/binary, Rest/binary>>, Acc) -> - get_tls_records_aux(Rest, [#ssl_tls{type = ?HANDSHAKE, - version = {MajVer, MinVer}, - fragment = Data} | Acc]); -get_tls_records_aux(<<?BYTE(?ALERT),?BYTE(MajVer),?BYTE(MinVer), - ?UINT16(Length), Data:Length/binary, - Rest/binary>>, Acc) -> - get_tls_records_aux(Rest, [#ssl_tls{type = ?ALERT, - version = {MajVer, MinVer}, - fragment = Data} | Acc]); -get_tls_records_aux(<<?BYTE(?CHANGE_CIPHER_SPEC),?BYTE(MajVer),?BYTE(MinVer), - ?UINT16(Length), Data:Length/binary, Rest/binary>>, - Acc) -> - get_tls_records_aux(Rest, [#ssl_tls{type = ?CHANGE_CIPHER_SPEC, - version = {MajVer, MinVer}, - fragment = Data} | Acc]); -%% Matches an ssl v2 client hello message. -%% The server must be able to receive such messages, from clients that -%% are willing to use ssl v3 or higher, but have ssl v2 compatibility. -get_tls_records_aux(<<1:1, Length0:15, Data0:Length0/binary, Rest/binary>>, - Acc) -> - case Data0 of - <<?BYTE(?CLIENT_HELLO), ?BYTE(MajVer), ?BYTE(MinVer), _/binary>> -> - Length = Length0-1, - <<?BYTE(_), Data1:Length/binary>> = Data0, - Data = <<?BYTE(?CLIENT_HELLO), ?UINT24(Length), Data1/binary>>, - get_tls_records_aux(Rest, [#ssl_tls{type = ?HANDSHAKE, - version = {MajVer, MinVer}, - fragment = Data} | Acc]); - _ -> - ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE) - - end; - -get_tls_records_aux(<<0:1, _CT:7, ?BYTE(_MajVer), ?BYTE(_MinVer), - ?UINT16(Length), _/binary>>, - _Acc) when Length > ?MAX_CIPHER_TEXT_LENGTH -> - ?ALERT_REC(?FATAL, ?RECORD_OVERFLOW); - -get_tls_records_aux(<<1:1, Length0:15, _/binary>>,_Acc) - when Length0 > ?MAX_CIPHER_TEXT_LENGTH -> - ?ALERT_REC(?FATAL, ?RECORD_OVERFLOW); - -get_tls_records_aux(Data, Acc) -> - case size(Data) =< ?MAX_CIPHER_TEXT_LENGTH + ?INITIAL_BYTES of - true -> - {lists:reverse(Acc), Data}; - false -> - ?ALERT_REC(?FATAL, ?UNEXPECTED_MESSAGE) - end. -%%-------------------------------------------------------------------- --spec protocol_version(tls_atom_version() | tls_version()) -> - tls_version() | tls_atom_version(). -%% -%% Description: Creates a protocol version record from a version atom -%% or vice versa. -%%-------------------------------------------------------------------- -protocol_version('tlsv1.2') -> - {3, 3}; -protocol_version('tlsv1.1') -> - {3, 2}; -protocol_version(tlsv1) -> - {3, 1}; -protocol_version(sslv3) -> - {3, 0}; -protocol_version(sslv2) -> %% Backwards compatibility - {2, 0}; -protocol_version({3, 3}) -> - 'tlsv1.2'; -protocol_version({3, 2}) -> - 'tlsv1.1'; -protocol_version({3, 1}) -> - tlsv1; -protocol_version({3, 0}) -> - sslv3. -%%-------------------------------------------------------------------- --spec lowest_protocol_version(tls_version(), tls_version()) -> tls_version(). -%% -%% Description: Lowes protocol version of two given versions -%%-------------------------------------------------------------------- -lowest_protocol_version(Version = {M, N}, {M, O}) when N < O -> - Version; -lowest_protocol_version({M, _}, - Version = {M, _}) -> - Version; -lowest_protocol_version(Version = {M,_}, - {N, _}) when M < N -> - Version; -lowest_protocol_version(_,Version) -> - Version. -%%-------------------------------------------------------------------- --spec highest_protocol_version([tls_version()]) -> tls_version(). -%% -%% Description: Highest protocol version present in a list -%%-------------------------------------------------------------------- -highest_protocol_version([]) -> - highest_protocol_version(); -highest_protocol_version(Versions) -> - [Ver | Vers] = Versions, - highest_protocol_version(Ver, Vers). - -highest_protocol_version(Version, []) -> - Version; -highest_protocol_version(Version = {N, M}, [{N, O} | Rest]) when M > O -> - highest_protocol_version(Version, Rest); -highest_protocol_version({M, _}, [Version = {M, _} | Rest]) -> - highest_protocol_version(Version, Rest); -highest_protocol_version(Version = {M,_}, [{N,_} | Rest]) when M > N -> - highest_protocol_version(Version, Rest); -highest_protocol_version(_, [Version | Rest]) -> - highest_protocol_version(Version, Rest). - -%%-------------------------------------------------------------------- --spec supported_protocol_versions() -> [tls_version()]. -%% -%% Description: Protocol versions supported -%%-------------------------------------------------------------------- -supported_protocol_versions() -> - Fun = fun(Version) -> - protocol_version(Version) - end, - case application:get_env(ssl, protocol_version) of - undefined -> - lists:map(Fun, supported_protocol_versions([])); - {ok, []} -> - lists:map(Fun, supported_protocol_versions([])); - {ok, Vsns} when is_list(Vsns) -> - Versions = lists:filter(fun is_acceptable_version/1, lists:map(Fun, Vsns)), - supported_protocol_versions(Versions); - {ok, Vsn} -> - Versions = lists:filter(fun is_acceptable_version/1, [Fun(Vsn)]), - supported_protocol_versions(Versions) - end. - -supported_protocol_versions([]) -> - Vsns = case sufficient_tlsv1_2_crypto_support() of - true -> - ?ALL_SUPPORTED_VERSIONS; - false -> - ?MIN_SUPPORTED_VERSIONS - end, - application:set_env(ssl, protocol_version, Vsns), - Vsns; - -supported_protocol_versions([_|_] = Vsns) -> - Vsns. - -%%-------------------------------------------------------------------- --spec is_acceptable_version(tls_version()) -> boolean(). -%% -%% Description: ssl version 2 is not acceptable security risks are too big. -%%-------------------------------------------------------------------- -is_acceptable_version({N,_}) - when N >= ?LOWEST_MAJOR_SUPPORTED_VERSION -> - true; -is_acceptable_version(_) -> - false. - -%%-------------------------------------------------------------------- --spec compressions() -> [binary()]. -%% -%% Description: return a list of compressions supported (currently none) -%%-------------------------------------------------------------------- -compressions() -> - [?byte(?NULL)]. - -%%-------------------------------------------------------------------- --spec decode_cipher_text(#ssl_tls{}, #connection_states{}) -> - {#ssl_tls{}, #connection_states{}}| #alert{}. -%% -%% Description: Decode cipher text -%%-------------------------------------------------------------------- -decode_cipher_text(CipherText, ConnnectionStates0) -> - ReadState0 = ConnnectionStates0#connection_states.current_read, - #connection_state{compression_state = CompressionS0, - security_parameters = SecParams} = ReadState0, - CompressAlg = SecParams#security_parameters.compression_algorithm, - case decipher(CipherText, ReadState0) of - {Compressed, ReadState1} -> - {Plain, CompressionS1} = uncompress(CompressAlg, - Compressed, CompressionS0), - ConnnectionStates = ConnnectionStates0#connection_states{ - current_read = ReadState1#connection_state{ - compression_state = CompressionS1}}, - {Plain, ConnnectionStates}; - #alert{} = Alert -> - Alert - end. -%%-------------------------------------------------------------------- --spec encode_data(binary(), tls_version(), #connection_states{}) -> - {iolist(), #connection_states{}}. -%% -%% Description: Encodes data to send on the ssl-socket. -%%-------------------------------------------------------------------- -encode_data(Frag, Version, - #connection_states{current_write = #connection_state{ - security_parameters = - #security_parameters{bulk_cipher_algorithm = BCA}}} = - ConnectionStates) -> - Data = split_bin(Frag, ?MAX_PLAIN_TEXT_LENGTH, Version, BCA), - encode_iolist(?APPLICATION_DATA, Data, Version, ConnectionStates). %%-------------------------------------------------------------------- -spec encode_handshake(iolist(), tls_version(), #connection_states{}) -> @@ -558,31 +328,71 @@ encode_change_cipher_spec(Version, ConnectionStates) -> encode_plain_text(?CHANGE_CIPHER_SPEC, Version, <<1:8>>, ConnectionStates). %%-------------------------------------------------------------------- -%%% Internal functions +-spec encode_data(binary(), tls_version(), #connection_states{}) -> + {iolist(), #connection_states{}}. +%% +%% Description: Encodes data to send on the ssl-socket. %%-------------------------------------------------------------------- -encode_iolist(Type, Data, Version, ConnectionStates0) -> - {ConnectionStates, EncodedMsg} = - lists:foldl(fun(Text, {CS0, Encoded}) -> - {Enc, CS1} = encode_plain_text(Type, Version, Text, CS0), - {CS1, [Enc | Encoded]} - end, {ConnectionStates0, []}, Data), - {lists:reverse(EncodedMsg), ConnectionStates}. +encode_data(Frag, Version, + #connection_states{current_write = #connection_state{ + security_parameters = + #security_parameters{bulk_cipher_algorithm = BCA}}} = + ConnectionStates) -> + Data = split_bin(Frag, ?MAX_PLAIN_TEXT_LENGTH, Version, BCA), + encode_iolist(?APPLICATION_DATA, Data, Version, ConnectionStates). -highest_protocol_version() -> - highest_protocol_version(supported_protocol_versions()). +uncompress(?NULL, Data, CS) -> + {Data, CS}. -initial_connection_state(ConnectionEnd) -> - #connection_state{security_parameters = - initial_security_params(ConnectionEnd), - sequence_number = 0 - }. +compress(?NULL, Data, CS) -> + {Data, CS}. -initial_security_params(ConnectionEnd) -> - SecParams = #security_parameters{connection_end = ConnectionEnd, - compression_algorithm = ?NULL}, - ssl_cipher:security_parameters(highest_protocol_version(), ?TLS_NULL_WITH_NULL_NULL, - SecParams). +%%-------------------------------------------------------------------- +-spec compressions() -> [binary()]. +%% +%% Description: return a list of compressions supported (currently none) +%%-------------------------------------------------------------------- +compressions() -> + [?byte(?NULL)]. + +%%-------------------------------------------------------------------- +-spec cipher(tls_version(), iolist(), #connection_state{}, MacHash::binary()) -> + {CipherFragment::binary(), #connection_state{}}. +%% +%% Description: Payload encryption +%%-------------------------------------------------------------------- +cipher(Version, Fragment, + #connection_state{cipher_state = CipherS0, + security_parameters= + #security_parameters{bulk_cipher_algorithm = + BulkCipherAlgo} + } = WriteState0, MacHash) -> + {CipherFragment, CipherS1} = + ssl_cipher:cipher(BulkCipherAlgo, CipherS0, MacHash, Fragment, Version), + {CipherFragment, WriteState0#connection_state{cipher_state = CipherS1}}. +%%-------------------------------------------------------------------- +-spec decipher(tls_version(), binary(), #connection_state{}) -> {binary(), binary(), #connection_state{}}. +%% +%% Description: Payload decryption +%%-------------------------------------------------------------------- +decipher(Version, CipherFragment, + #connection_state{security_parameters = + #security_parameters{bulk_cipher_algorithm = + BulkCipherAlgo, + hash_size = HashSz}, + cipher_state = CipherS0 + } = ReadState) -> + case ssl_cipher:decipher(BulkCipherAlgo, HashSz, CipherS0, CipherFragment, Version) of + {PlainFragment, Mac, CipherS1} -> + CS1 = ReadState#connection_state{cipher_state = CipherS1}, + {PlainFragment, Mac, CS1}; + #alert{} = Alert -> + Alert + end. +%%-------------------------------------------------------------------- +%%% Internal functions +%%-------------------------------------------------------------------- empty_connection_state(ConnectionEnd) -> SecParams = empty_security_params(ConnectionEnd), #connection_state{security_parameters = SecParams}. @@ -599,14 +409,52 @@ random() -> Random_28_bytes = crypto:rand_bytes(28), <<?UINT32(Secs_since_1970), Random_28_bytes/binary>>. +dtls_next_epoch(#connection_state{epoch = undefined}) -> %% SSL/TLS + undefined; +dtls_next_epoch(#connection_state{epoch = Epoch}) -> %% DTLS + Epoch + 1. + +is_correct_mac(Mac, Mac) -> + true; +is_correct_mac(_M,_H) -> + false. + record_protocol_role(client) -> ?CLIENT; record_protocol_role(server) -> ?SERVER. -%% 1/n-1 splitting countermeasure Rizzo/Duong-Beast, RC4 chiphers are not vulnerable to this attack. -split_bin(<<FirstByte:8, Rest/binary>>, ChunkSize, Version, BCA) when BCA =/= ?RC4 andalso ({3, 1} == Version orelse - {3, 0} == Version) -> +initial_connection_state(ConnectionEnd) -> + #connection_state{security_parameters = + initial_security_params(ConnectionEnd), + sequence_number = 0 + }. + +initial_security_params(ConnectionEnd) -> + SecParams = #security_parameters{connection_end = ConnectionEnd, + compression_algorithm = ?NULL}, + ssl_cipher:security_parameters(?TLS_NULL_WITH_NULL_NULL, SecParams). + + +encode_plain_text(Type, Version, Data, ConnectionStates) -> + RecordCB = protocol_module(Version), + RecordCB:encode_plain_text(Type, Version, Data, ConnectionStates). + +encode_iolist(Type, Data, Version, ConnectionStates0) -> + RecordCB = protocol_module(Version), + {ConnectionStates, EncodedMsg} = + lists:foldl(fun(Text, {CS0, Encoded}) -> + {Enc, CS1} = + RecordCB:encode_plain_text(Type, Version, Text, CS0), + {CS1, [Enc | Encoded]} + end, {ConnectionStates0, []}, Data), + {lists:reverse(EncodedMsg), ConnectionStates}. + +%% 1/n-1 splitting countermeasure Rizzo/Duong-Beast, RC4 chiphers are +%% not vulnerable to this attack. +split_bin(<<FirstByte:8, Rest/binary>>, ChunkSize, Version, BCA) when + BCA =/= ?RC4 andalso ({3, 1} == Version orelse + {3, 0} == Version) -> do_split_bin(Rest, ChunkSize, [[FirstByte]]); split_bin(Bin, ChunkSize, _, _) -> do_split_bin(Bin, ChunkSize, []). @@ -621,95 +469,7 @@ do_split_bin(Bin, ChunkSize, Acc) -> lists:reverse(Acc, [Bin]) end. -encode_plain_text(Type, Version, Data, ConnectionStates) -> - #connection_states{current_write=#connection_state{ - compression_state=CompS0, - security_parameters= - #security_parameters{compression_algorithm=CompAlg} - }=CS0} = ConnectionStates, - {Comp, CompS1} = compress(CompAlg, Data, CompS0), - CS1 = CS0#connection_state{compression_state = CompS1}, - {CipherText, CS2} = cipher(Type, Version, Comp, CS1), - CTBin = encode_tls_cipher_text(Type, Version, CipherText), - {CTBin, ConnectionStates#connection_states{current_write = CS2}}. - -encode_tls_cipher_text(Type, {MajVer, MinVer}, Fragment) -> - Length = erlang:iolist_size(Fragment), - [<<?BYTE(Type), ?BYTE(MajVer), ?BYTE(MinVer), ?UINT16(Length)>>, Fragment]. - -cipher(Type, Version, Fragment, CS0) -> - Length = erlang:iolist_size(Fragment), - {MacHash, CS1=#connection_state{cipher_state = CipherS0, - security_parameters= - #security_parameters{bulk_cipher_algorithm = - BCA} - }} = - hash_and_bump_seqno(CS0, Type, Version, Length, Fragment), - {Ciphered, CipherS1} = ssl_cipher:cipher(BCA, CipherS0, MacHash, Fragment, Version), - CS2 = CS1#connection_state{cipher_state=CipherS1}, - {Ciphered, CS2}. - -decipher(TLS=#ssl_tls{type=Type, version=Version, fragment=Fragment}, CS0) -> - SP = CS0#connection_state.security_parameters, - BCA = SP#security_parameters.bulk_cipher_algorithm, - HashSz = SP#security_parameters.hash_size, - CipherS0 = CS0#connection_state.cipher_state, - case ssl_cipher:decipher(BCA, HashSz, CipherS0, Fragment, Version) of - {T, Mac, CipherS1} -> - CS1 = CS0#connection_state{cipher_state = CipherS1}, - TLength = size(T), - {MacHash, CS2} = hash_and_bump_seqno(CS1, Type, Version, TLength, T), - case is_correct_mac(Mac, MacHash) of - true -> - {TLS#ssl_tls{fragment = T}, CS2}; - false -> - ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC) - end; - #alert{} = Alert -> - Alert - end. - -uncompress(?NULL, Data = #ssl_tls{type = _Type, - version = _Version, - fragment = _Fragment}, CS) -> - {Data, CS}. - -compress(?NULL, Data, CS) -> - {Data, CS}. - -hash_and_bump_seqno(#connection_state{sequence_number = SeqNo, - mac_secret = MacSecret, - security_parameters = - SecPars} = CS0, - Type, Version, Length, Fragment) -> - Hash = mac_hash(Version, - SecPars#security_parameters.mac_algorithm, - MacSecret, SeqNo, Type, - Length, Fragment), - {Hash, CS0#connection_state{sequence_number = SeqNo+1}}. - -is_correct_mac(Mac, Mac) -> - true; -is_correct_mac(_M,_H) -> - false. - -mac_hash({_,_}, ?NULL, _MacSecret, _SeqNo, _Type, - _Length, _Fragment) -> - <<>>; -mac_hash({3, 0}, MacAlg, MacSecret, SeqNo, Type, Length, Fragment) -> - ssl_ssl3:mac_hash(MacAlg, MacSecret, SeqNo, Type, Length, Fragment); -mac_hash({3, N} = Version, MacAlg, MacSecret, SeqNo, Type, Length, Fragment) - when N =:= 1; N =:= 2; N =:= 3 -> - ssl_tls1:mac_hash(MacAlg, MacSecret, SeqNo, Type, Version, - Length, Fragment). - -sufficient_tlsv1_2_crypto_support() -> - Data = "Sampl", - Data2 = "e #1", - Key = <<0,1,2,3,16,17,18,19,32,33,34,35,48,49,50,51,4,5,6,7,20,21,22,23,36,37,38,39, - 52,53,54,55,8,9,10,11,24,25,26,27,40,41,42,43,56,57,58,59>>, - try - crypto:sha256_mac(Key, lists:flatten([Data, Data2])), - true - catch _:_ -> false - end. +protocol_module({3, _}) -> + tls_record; +protocol_module({254, _}) -> + dtls_record. |
