From 754b87eb181552d67c61c9a80c31ce52e4b39f19 Mon Sep 17 00:00:00 2001 From: Ingela Anderton Andin Date: Thu, 5 Sep 2013 10:43:31 +0200 Subject: ssl: Refactor TLS/DTLS record handling --- lib/ssl/src/Makefile | 1 + lib/ssl/src/dtls_connection.erl | 6 + lib/ssl/src/dtls_handshake.erl | 18 +- lib/ssl/src/dtls_record.erl | 349 +++++++++++++++------------ lib/ssl/src/dtls_record.hrl | 2 - lib/ssl/src/dtls_v1.erl | 5 +- lib/ssl/src/ssl.app.src | 42 ++-- lib/ssl/src/ssl_cipher.erl | 14 +- lib/ssl/src/ssl_cipher.hrl | 1 + lib/ssl/src/ssl_handshake.erl | 61 ++--- lib/ssl/src/ssl_internal.hrl | 1 - lib/ssl/src/ssl_record.erl | 396 +++++++++++++++++++++++++++++-- lib/ssl/src/ssl_record.hrl | 2 + lib/ssl/src/tls_connection.erl | 56 ++--- lib/ssl/src/tls_handshake.erl | 6 +- lib/ssl/src/tls_record.erl | 511 ++++++---------------------------------- lib/ssl/src/tls_record.hrl | 2 +- 17 files changed, 775 insertions(+), 698 deletions(-) (limited to 'lib/ssl') diff --git a/lib/ssl/src/Makefile b/lib/ssl/src/Makefile index a5af451244..6744e2f256 100644 --- a/lib/ssl/src/Makefile +++ b/lib/ssl/src/Makefile @@ -69,6 +69,7 @@ MODULES= \ ssl_v2 \ ssl_v3 \ tls_v1 \ + dtls_v1 \ ssl_tls_dist_proxy INTERNAL_HRL_FILES = \ diff --git a/lib/ssl/src/dtls_connection.erl b/lib/ssl/src/dtls_connection.erl index f6a1dc699e..fda488501c 100644 --- a/lib/ssl/src/dtls_connection.erl +++ b/lib/ssl/src/dtls_connection.erl @@ -333,3 +333,9 @@ %% timestamp() -> %% {Mega, Sec, Micro} = erlang:now(), %% Mega * 1000000 * 1000 + Sec * 1000 + (Micro div 1000). + +%% encode_handshake_rec(HandshakeRec, Version, MsgSeq, BinMsgs0, CS0) -> +%% {_, Fragments} = ssl_handshake:encode_handshake(HandshakeRec, Version, MsgSeq, 1400), +%% lists:foldl(fun(F, {Bin, C0}) -> +%% {B, C1} = ssl_record:encode_handshake(F, Version, C0), +%% {[B|Bin], C1} end, {BinMsgs0, CS0}, Fragments). diff --git a/lib/ssl/src/dtls_handshake.erl b/lib/ssl/src/dtls_handshake.erl index 8a481af76d..26e8ce7503 100644 --- a/lib/ssl/src/dtls_handshake.erl +++ b/lib/ssl/src/dtls_handshake.erl @@ -54,18 +54,18 @@ client_hello(Host, Port, Cookie, ConnectionStates, #client_hello{session_id = Id, client_version = Version, cipher_suites = ssl_handshake:cipher_suites(CipherSuites, Renegotiation), - compression_methods = tls_record:compressions(), + compression_methods = ssl_record:compressions(), random = SecParams#security_parameters.client_random, cookie = Cookie, extensions = Extensions }. hello(Address, Port, - #ssl_tls{epoch = Epoch, record_seq = Seq, + #ssl_tls{epoch = _Epoch, record_seq = _Seq, version = Version} = Record) -> {[{Hello, _}], _, _} = - ssl_handshake:get_dtls_handshake(Record, - ssl_handshake:dtls_handshake_new_flight(undefined)), + get_dtls_handshake(Record, + dtls_handshake_new_flight(undefined)), #client_hello{client_version = {Major, Minor}, random = Random, session_id = SessionId, @@ -81,11 +81,9 @@ hello(Address, Port, accept; _ -> %% generate HelloVerifyRequest - {RequestFragment, _} = ssl_handshake:encode_handshake( - ssl_handshake:hello_verify_request(Cookie), - Version, 0, 1400), - HelloVerifyRequest = - ssl_record:encode_tls_cipher_text(?HANDSHAKE, Version, Epoch, Seq, RequestFragment), + HelloVerifyRequest = encode_handshake(#hello_verify_request{protocol_version = Version, + cookie = Cookie}, + Version, 0, 1400), {reply, HelloVerifyRequest} end. @@ -415,7 +413,7 @@ decode_handshake(_Version, ?CLIENT_HELLO, < - #connection_state{}. -%% -%% Description: Copy the read sequence number to the write sequence number -%% This is only valid for DTLS in the first client_hello -%%-------------------------------------------------------------------- -init_connection_state_seq({254, _}, - #connection_states{ - current_read = Read = #connection_state{epoch = 0}, - current_write = Write = #connection_state{epoch = 0}} = CS0) -> - CS0#connection_states{current_write = - Write#connection_state{ - sequence_number = Read#connection_state.sequence_number}}; -init_connection_state_seq(_, CS) -> - CS. +-export([init_connection_state_seq/2, current_connection_state_epoch/2, + set_connection_state_by_epoch/3, connection_state_by_epoch/3]). -%%-------------------------------------------------------- --spec current_connection_state_epoch(#connection_states{}, read | write) -> - integer(). -%% -%% Description: Returns the epoch the connection_state record -%% that is currently defined as the current conection state. -%%-------------------------------------------------------------------- -current_connection_state_epoch(#connection_states{current_read = Current}, - read) -> - Current#connection_state.epoch; -current_connection_state_epoch(#connection_states{current_write = Current}, - write) -> - Current#connection_state.epoch. +-compile(inline). -%%-------------------------------------------------------------------- +%%==================================================================== +%% Internal application API +%%==================================================================== --spec connection_state_by_epoch(#connection_states{}, integer(), read | write) -> - #connection_state{}. -%% -%% Description: Returns the instance of the connection_state record -%% that is defined by the Epoch. -%%-------------------------------------------------------------------- -connection_state_by_epoch(#connection_states{current_read = CS}, Epoch, read) - when CS#connection_state.epoch == Epoch -> - CS; -connection_state_by_epoch(#connection_states{pending_read = CS}, Epoch, read) - when CS#connection_state.epoch == Epoch -> - CS; -connection_state_by_epoch(#connection_states{current_write = CS}, Epoch, write) - when CS#connection_state.epoch == Epoch -> - CS; -connection_state_by_epoch(#connection_states{pending_write = CS}, Epoch, write) - when CS#connection_state.epoch == Epoch -> - CS. %%-------------------------------------------------------------------- --spec set_connection_state_by_epoch(#connection_states{}, - #connection_state{}, read | write) -> ok. +-spec get_dtls_records(binary(), binary()) -> {[binary()], binary()} | #alert{}. %% -%% Description: Returns the instance of the connection_state record -%% that is defined by the Epoch. +%% Description: Given old buffer and new data from UDP/SCTP, packs up a records +%% and returns it as a list of tls_compressed binaries also returns leftover +%% data %%-------------------------------------------------------------------- -set_connection_state_by_epoch(ConnectionStates0 = - #connection_states{current_read = CS}, - NewCS = #connection_state{epoch = Epoch}, read) - when CS#connection_state.epoch == Epoch -> - ConnectionStates0#connection_states{current_read = NewCS}; +get_dtls_records(Data, <<>>) -> + get_dtls_records_aux(Data, []); +get_dtls_records(Data, Buffer) -> + get_dtls_records_aux(list_to_binary([Buffer, Data]), []). -set_connection_state_by_epoch(ConnectionStates0 = - #connection_states{pending_read = CS}, - NewCS = #connection_state{epoch = Epoch}, read) - when CS#connection_state.epoch == Epoch -> - ConnectionStates0#connection_states{pending_read = NewCS}; +get_dtls_records_aux(<>, + Acc) -> + get_dtls_records_aux(Rest, [#ssl_tls{type = ?APPLICATION_DATA, + version = {MajVer, MinVer}, + epoch = Epoch, record_seq = SequenceNumber, + fragment = Data} | Acc]); +get_dtls_records_aux(<>, Acc) when MajVer >= 128 -> + get_dtls_records_aux(Rest, [#ssl_tls{type = ?HANDSHAKE, + version = {MajVer, MinVer}, + epoch = Epoch, record_seq = SequenceNumber, + fragment = Data} | Acc]); +get_dtls_records_aux(<>, Acc) -> + get_dtls_records_aux(Rest, [#ssl_tls{type = ?ALERT, + version = {MajVer, MinVer}, + epoch = Epoch, record_seq = SequenceNumber, + fragment = Data} | Acc]); +get_dtls_records_aux(<>, + Acc) -> + get_dtls_records_aux(Rest, [#ssl_tls{type = ?CHANGE_CIPHER_SPEC, + version = {MajVer, MinVer}, + epoch = Epoch, record_seq = SequenceNumber, + fragment = Data} | Acc]); -set_connection_state_by_epoch(ConnectionStates0 = - #connection_states{current_write = CS}, - NewCS = #connection_state{epoch = Epoch}, write) - when CS#connection_state.epoch == Epoch -> - ConnectionStates0#connection_states{current_write = NewCS}; +get_dtls_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); -set_connection_state_by_epoch(ConnectionStates0 = - #connection_states{pending_write = CS}, - NewCS = #connection_state{epoch = Epoch}, write) - when CS#connection_state.epoch == Epoch -> - ConnectionStates0#connection_states{pending_write = NewCS}. +get_dtls_records_aux(<<1:1, Length0:15, _/binary>>,_Acc) + when Length0 > ?MAX_CIPHER_TEXT_LENGTH -> + ?ALERT_REC(?FATAL, ?RECORD_OVERFLOW); + +get_dtls_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. + +encode_plain_text(Type, Version, Data, + #connection_state{ + compression_state = CompS0, + epoch = Epoch, + sequence_number = Seq, + security_parameters= + #security_parameters{compression_algorithm = CompAlg} + }= CS0) -> + {Comp, CompS1} = ssl_record: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, Epoch, Seq, CipherText), + {CTBin, CS2}. + +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} = ssl_record:uncompress(CompressAlg, + Compressed, CompressionS0), + ConnnectionStates = ConnnectionStates0#connection_states{ + current_read = ReadState1#connection_state{ + compression_state = CompressionS1}}, + {Plain, ConnnectionStates}; + #alert{} = Alert -> + Alert + end. %%-------------------------------------------------------------------- -spec protocol_version(tls_atom_version() | tls_version()) -> @@ -161,67 +191,6 @@ highest_protocol_version(Version = {M,_}, [{N,_} | Rest]) when M < N -> highest_protocol_version(_, [Version | Rest]) -> highest_protocol_version(Version, Rest). -%%-------------------------------------------------------------------- --spec get_dtls_records(binary(), binary()) -> {[binary()], binary()} | #alert{}. -%% -%% Description: Given old buffer and new data from UDP/SCTP, packs up a records -%% and returns it as a list of tls_compressed binaries also returns leftover -%% data -%%-------------------------------------------------------------------- -get_dtls_records(Data, <<>>) -> - get_dtls_records_aux(Data, []); -get_dtls_records(Data, Buffer) -> - get_dtls_records_aux(list_to_binary([Buffer, Data]), []). - -get_dtls_records_aux(<>, - Acc) -> - get_dtls_records_aux(Rest, [#ssl_tls{type = ?APPLICATION_DATA, - version = {MajVer, MinVer}, - epoch = Epoch, record_seq = SequenceNumber, - fragment = Data} | Acc]); -get_dtls_records_aux(<>, Acc) when MajVer >= 128 -> - get_dtls_records_aux(Rest, [#ssl_tls{type = ?HANDSHAKE, - version = {MajVer, MinVer}, - epoch = Epoch, record_seq = SequenceNumber, - fragment = Data} | Acc]); -get_dtls_records_aux(<>, Acc) -> - get_dtls_records_aux(Rest, [#ssl_tls{type = ?ALERT, - version = {MajVer, MinVer}, - epoch = Epoch, record_seq = SequenceNumber, - fragment = Data} | Acc]); -get_dtls_records_aux(<>, - Acc) -> - get_dtls_records_aux(Rest, [#ssl_tls{type = ?CHANGE_CIPHER_SPEC, - version = {MajVer, MinVer}, - epoch = Epoch, record_seq = SequenceNumber, - fragment = Data} | Acc]); - -get_dtls_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_dtls_records_aux(<<1:1, Length0:15, _/binary>>,_Acc) - when Length0 > ?MAX_CIPHER_TEXT_LENGTH -> - ?ALERT_REC(?FATAL, ?RECORD_OVERFLOW); - -get_dtls_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 supported_protocol_versions() -> [tls_version()]. @@ -264,13 +233,103 @@ is_acceptable_version(Version, Versions) -> lists:member(Version, Versions). +%%-------------------------------------------------------------------- +-spec init_connection_state_seq(tls_version(), #connection_states{}) -> + #connection_state{}. +%% +%% Description: Copy the read sequence number to the write sequence number +%% This is only valid for DTLS in the first client_hello +%%-------------------------------------------------------------------- +init_connection_state_seq({254, _}, + #connection_states{ + current_read = Read = #connection_state{epoch = 0}, + current_write = Write = #connection_state{epoch = 0}} = CS0) -> + CS0#connection_states{current_write = + Write#connection_state{ + sequence_number = Read#connection_state.sequence_number}}; +init_connection_state_seq(_, CS) -> + CS. + +%%-------------------------------------------------------- +-spec current_connection_state_epoch(#connection_states{}, read | write) -> + integer(). +%% +%% Description: Returns the epoch the connection_state record +%% that is currently defined as the current conection state. +%%-------------------------------------------------------------------- +current_connection_state_epoch(#connection_states{current_read = Current}, + read) -> + Current#connection_state.epoch; +current_connection_state_epoch(#connection_states{current_write = Current}, + write) -> + Current#connection_state.epoch. + +%%-------------------------------------------------------------------- + +-spec connection_state_by_epoch(#connection_states{}, integer(), read | write) -> + #connection_state{}. +%% +%% Description: Returns the instance of the connection_state record +%% that is defined by the Epoch. +%%-------------------------------------------------------------------- +connection_state_by_epoch(#connection_states{current_read = CS}, Epoch, read) + when CS#connection_state.epoch == Epoch -> + CS; +connection_state_by_epoch(#connection_states{pending_read = CS}, Epoch, read) + when CS#connection_state.epoch == Epoch -> + CS; +connection_state_by_epoch(#connection_states{current_write = CS}, Epoch, write) + when CS#connection_state.epoch == Epoch -> + CS; +connection_state_by_epoch(#connection_states{pending_write = CS}, Epoch, write) + when CS#connection_state.epoch == Epoch -> + CS. +%%-------------------------------------------------------------------- +-spec set_connection_state_by_epoch(#connection_states{}, + #connection_state{}, read | write) -> ok. +%% +%% Description: Returns the instance of the connection_state record +%% that is defined by the Epoch. +%%-------------------------------------------------------------------- +set_connection_state_by_epoch(ConnectionStates0 = + #connection_states{current_read = CS}, + NewCS = #connection_state{epoch = Epoch}, read) + when CS#connection_state.epoch == Epoch -> + ConnectionStates0#connection_states{current_read = NewCS}; + +set_connection_state_by_epoch(ConnectionStates0 = + #connection_states{pending_read = CS}, + NewCS = #connection_state{epoch = Epoch}, read) + when CS#connection_state.epoch == Epoch -> + ConnectionStates0#connection_states{pending_read = NewCS}; + +set_connection_state_by_epoch(ConnectionStates0 = + #connection_states{current_write = CS}, + NewCS = #connection_state{epoch = Epoch}, write) + when CS#connection_state.epoch == Epoch -> + ConnectionStates0#connection_states{current_write = NewCS}; + +set_connection_state_by_epoch(ConnectionStates0 = + #connection_states{pending_write = CS}, + NewCS = #connection_state{epoch = Epoch}, write) + when CS#connection_state.epoch == Epoch -> + ConnectionStates0#connection_states{pending_write = NewCS}. + +%%-------------------------------------------------------------------- +%%% Internal functions +%%-------------------------------------------------------------------- +encode_tls_cipher_text(Type, {MajVer, MinVer}, Epoch, Seq, Fragment) -> + Length = erlang:iolist_size(Fragment), + [<>, 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} - }} = + 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}, @@ -299,10 +358,10 @@ decipher(TLS=#ssl_tls{type=Type, version=Version={254, _}, end. hash_with_seqno(#connection_state{mac_secret = MacSecret, - security_parameters = - SecPars}, - Type, Version = {254, _}, - Epoch, SeqNo, Length, Fragment) -> + security_parameters = + SecPars}, + Type, Version = {254, _}, + Epoch, SeqNo, Length, Fragment) -> mac_hash(Version, SecPars#security_parameters.mac_algorithm, MacSecret, (Epoch bsl 48) + SeqNo, Type, diff --git a/lib/ssl/src/dtls_record.hrl b/lib/ssl/src/dtls_record.hrl index b72c14c2d7..e935d84bdf 100644 --- a/lib/ssl/src/dtls_record.hrl +++ b/lib/ssl/src/dtls_record.hrl @@ -28,8 +28,6 @@ -include("ssl_record.hrl"). %% Common TLS and DTLS records and Constantes --define(INITIAL_BYTES, 5). - %% Used to handle tls_plain_text, tls_compressed and tls_cipher_text -record(ssl_tls, { diff --git a/lib/ssl/src/dtls_v1.erl b/lib/ssl/src/dtls_v1.erl index a9fcf575af..c12e12e424 100644 --- a/lib/ssl/src/dtls_v1.erl +++ b/lib/ssl/src/dtls_v1.erl @@ -20,7 +20,7 @@ -include("ssl_cipher.hrl"). --export([suites/1, mac_hash/7, ecc_curves/1]). +-export([suites/1, mac_hash/7, ecc_curves/1, corresponding_tls_version/1]). -spec suites(Minor:: 253|255) -> [cipher_suite()]. @@ -34,6 +34,9 @@ mac_hash(Version, MacAlg, MacSecret, SeqNo, Type, Length, Fragment) -> ecc_curves({_Major, Minor}) -> tls_v1:ecc_curves(corresponding_minor_tls_version(Minor)). +corresponding_tls_version({254, Minor}) -> + {3, corresponding_minor_tls_version(Minor)}. + corresponding_minor_tls_version(255) -> 2; corresponding_minor_tls_version(253) -> diff --git a/lib/ssl/src/ssl.app.src b/lib/ssl/src/ssl.app.src index 677f5fdd07..44798f8c12 100644 --- a/lib/ssl/src/ssl.app.src +++ b/lib/ssl/src/ssl.app.src @@ -3,40 +3,44 @@ {vsn, "%VSN%"}, {modules, [ %% TLS/SSL - tls, tls_connection, tls_handshake, tls_record, + tls_v1, + ssl_v3, + ssl_v2, %% DTLS - dtls_record, - dtls_handshake, dtls_connection, - dtls, - %% Backwards compatibility + dtls_handshake, + dtls_record, + dtls_v1, + %% API + tls, %% Future API module + dtls, %% Future API module ssl, + ssl_session_cache_api, %% Both TLS/SSL and DTLS - ssl_app, - ssl_sup, + ssl_handshake, + ssl_record, + ssl_cipher, + ssl_srp_primes, + ssl_alert, + ssl_socket, + %%ssl_connection, + %% Erlang Distribution over SSL/TLS inet_tls_dist, ssl_tls_dist_proxy, ssl_dist_sup, - tls_v1, - ssl_v3, - ssl_v2, + %% SSL/TLS session handling ssl_session, - ssl_session_cache_api, ssl_session_cache, - ssl_socket, - ssl_record, ssl_manager, - ssl_handshake, - ssl_connection_sup, - %%ssl_connection, - ssl_cipher, - ssl_srp_primes, ssl_pkix_db, ssl_certificate, - ssl_alert + %% App structure + ssl_app, + ssl_sup, + ssl_connection_sup ]}, {registered, [ssl_sup, ssl_manager]}, {applications, [crypto, public_key, kernel, stdlib]}, diff --git a/lib/ssl/src/ssl_cipher.erl b/lib/ssl/src/ssl_cipher.erl index 3fe4df99c9..6513042e98 100644 --- a/lib/ssl/src/ssl_cipher.erl +++ b/lib/ssl/src/ssl_cipher.erl @@ -32,7 +32,7 @@ -include("ssl_alert.hrl"). -include_lib("public_key/include/public_key.hrl"). --export([security_parameters/3, suite_definition/1, +-export([security_parameters/2, security_parameters/3, suite_definition/1, decipher/5, cipher/5, suite/1, suites/1, anonymous_suites/0, psk_suites/1, srp_suites/0, openssl_suite/1, openssl_suite_name/1, filter/2, filter_suites/1, @@ -41,7 +41,17 @@ -compile(inline). %%-------------------------------------------------------------------- --spec security_parameters(tls_version(), cipher_suite(), #security_parameters{}) -> +-spec security_parameters(cipher_suite(), #security_parameters{}) -> + #security_parameters{}. +%% Only security_parameters/2 should call security_parameters/3 with undefined as +%% first argument. +%%-------------------------------------------------------------------- + +security_parameters(?TLS_NULL_WITH_NULL_NULL = CipherSuite, SecParams) -> + security_parameters(undefined, CipherSuite, SecParams). + +%%-------------------------------------------------------------------- +-spec security_parameters(tls_version() | undefined, cipher_suite(), #security_parameters{}) -> #security_parameters{}. %% %% Description: Returns a security parameters record where the diff --git a/lib/ssl/src/ssl_cipher.hrl b/lib/ssl/src/ssl_cipher.hrl index 8417564d41..62a5269def 100644 --- a/lib/ssl/src/ssl_cipher.hrl +++ b/lib/ssl/src/ssl_cipher.hrl @@ -29,6 +29,7 @@ -type cipher() :: null |rc4_128 | idea_cbc | des40_cbc | des_cbc | '3des_ede_cbc' | aes_128_cbc | aes_256_cbc. -type hash() :: null | sha | md5 | sha224 | sha256 | sha384 | sha512. +-type key_algo() :: null | rsa | dhe_rsa | dhe_dss | ecdhe_ecdsa| ecdh_ecdsa | ecdh_rsa| srp_rsa| srp_dss | psk | dhe_psk | rsa_psk | dh_anon | ecdh_anon | srp_anon. -type erl_cipher_suite() :: {key_algo(), cipher(), hash()}. -type int_cipher_suite() :: {key_algo(), cipher(), hash(), hash() | default_prf}. -type cipher_suite() :: binary(). diff --git a/lib/ssl/src/ssl_handshake.erl b/lib/ssl/src/ssl_handshake.erl index 189e5e7051..29a8996bd6 100644 --- a/lib/ssl/src/ssl_handshake.erl +++ b/lib/ssl/src/ssl_handshake.erl @@ -17,7 +17,8 @@ %% %CopyrightEnd% %---------------------------------------------------------------------- -%% Purpose: Help funtions for handling the SSL-handshake protocol +%% Purpose: Help funtions for handling the SSL-handshake protocol (common +%% to SSL/TLS and DTLS %%---------------------------------------------------------------------- -module(ssl_handshake). @@ -501,7 +502,7 @@ select_cert_hashsign(undefined, ?'id-dsa', _) -> master_secret(RecordCB, Version, #session{master_secret = Mastersecret}, ConnectionStates, Role) -> ConnectionState = - RecordCB:pending_connection_state(ConnectionStates, read), + ssl_record:pending_connection_state(ConnectionStates, read), SecParams = ConnectionState#connection_state.security_parameters, try master_secret(RecordCB, Version, Mastersecret, SecParams, ConnectionStates, Role) @@ -515,7 +516,7 @@ master_secret(RecordCB, Version, #session{master_secret = Mastersecret}, master_secret(RecordCB, Version, PremasterSecret, ConnectionStates, Role) -> ConnectionState = - RecordCB:pending_connection_state(ConnectionStates, read), + ssl_record:pending_connection_state(ConnectionStates, read), SecParams = ConnectionState#connection_state.security_parameters, #security_parameters{prf_algorithm = PrfAlgo, client_random = ClientRandom, @@ -969,16 +970,16 @@ select_version(RecordCB, ClientVersion, Versions) -> renegotiation_info(_, client, _, false) -> #renegotiation_info{renegotiated_connection = undefined}; -renegotiation_info(RecordCB, server, ConnectionStates, false) -> - CS = RecordCB:current_connection_state(ConnectionStates, read), +renegotiation_info(_RecordCB, server, ConnectionStates, false) -> + CS = ssl_record:current_connection_state(ConnectionStates, read), case CS#connection_state.secure_renegotiation of true -> #renegotiation_info{renegotiated_connection = ?byte(0)}; false -> #renegotiation_info{renegotiated_connection = undefined} end; -renegotiation_info(RecordCB, client, ConnectionStates, true) -> - CS = RecordCB:current_connection_state(ConnectionStates, read), +renegotiation_info(_RecordCB, client, ConnectionStates, true) -> + CS = ssl_record:current_connection_state(ConnectionStates, read), case CS#connection_state.secure_renegotiation of true -> Data = CS#connection_state.client_verify_data, @@ -987,8 +988,8 @@ renegotiation_info(RecordCB, client, ConnectionStates, true) -> #renegotiation_info{renegotiated_connection = undefined} end; -renegotiation_info(RecordCB, server, ConnectionStates, true) -> - CS = RecordCB:current_connection_state(ConnectionStates, read), +renegotiation_info(_RecordCB, server, ConnectionStates, true) -> + CS = ssl_record:current_connection_state(ConnectionStates, read), case CS#connection_state.secure_renegotiation of true -> CData = CS#connection_state.client_verify_data, @@ -998,24 +999,24 @@ renegotiation_info(RecordCB, server, ConnectionStates, true) -> #renegotiation_info{renegotiated_connection = undefined} end. -handle_renegotiation_info(RecordCB, _, #renegotiation_info{renegotiated_connection = ?byte(0)}, +handle_renegotiation_info(_RecordCB, _, #renegotiation_info{renegotiated_connection = ?byte(0)}, ConnectionStates, false, _, _) -> - {ok, RecordCB:set_renegotiation_flag(true, ConnectionStates)}; + {ok, ssl_record:set_renegotiation_flag(true, ConnectionStates)}; -handle_renegotiation_info(RecordCB, server, undefined, ConnectionStates, _, _, CipherSuites) -> +handle_renegotiation_info(_RecordCB, server, undefined, ConnectionStates, _, _, CipherSuites) -> case is_member(?TLS_EMPTY_RENEGOTIATION_INFO_SCSV, CipherSuites) of true -> - {ok, RecordCB:set_renegotiation_flag(true, ConnectionStates)}; + {ok, ssl_record:set_renegotiation_flag(true, ConnectionStates)}; false -> - {ok, RecordCB:set_renegotiation_flag(false, ConnectionStates)} + {ok, ssl_record:set_renegotiation_flag(false, ConnectionStates)} end; -handle_renegotiation_info(RecordCB, _, undefined, ConnectionStates, false, _, _) -> - {ok, RecordCB:set_renegotiation_flag(false, ConnectionStates)}; +handle_renegotiation_info(_RecordCB, _, undefined, ConnectionStates, false, _, _) -> + {ok, ssl_record:set_renegotiation_flag(false, ConnectionStates)}; -handle_renegotiation_info(RecordCB, client, #renegotiation_info{renegotiated_connection = ClientServerVerify}, +handle_renegotiation_info(_RecordCB, client, #renegotiation_info{renegotiated_connection = ClientServerVerify}, ConnectionStates, true, _, _) -> - CS = RecordCB:current_connection_state(ConnectionStates, read), + CS = ssl_record:current_connection_state(ConnectionStates, read), CData = CS#connection_state.client_verify_data, SData = CS#connection_state.server_verify_data, case <> == ClientServerVerify of @@ -1024,14 +1025,14 @@ handle_renegotiation_info(RecordCB, client, #renegotiation_info{renegotiated_con false -> ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE) end; -handle_renegotiation_info(RecordCB, server, #renegotiation_info{renegotiated_connection = ClientVerify}, +handle_renegotiation_info(_RecordCB, server, #renegotiation_info{renegotiated_connection = ClientVerify}, ConnectionStates, true, _, CipherSuites) -> case is_member(?TLS_EMPTY_RENEGOTIATION_INFO_SCSV, CipherSuites) of true -> ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE); false -> - CS = RecordCB:current_connection_state(ConnectionStates, read), + CS = ssl_record:current_connection_state(ConnectionStates, read), Data = CS#connection_state.client_verify_data, case Data == ClientVerify of true -> @@ -1052,8 +1053,8 @@ handle_renegotiation_info(RecordCB, server, undefined, ConnectionStates, true, S handle_renegotiation_info(RecordCB, ConnectionStates, SecureRenegotation) end. -handle_renegotiation_info(RecordCB, ConnectionStates, SecureRenegotation) -> - CS = RecordCB:current_connection_state(ConnectionStates, read), +handle_renegotiation_info(_RecordCB, ConnectionStates, SecureRenegotation) -> + CS = ssl_record:current_connection_state(ConnectionStates, read), case {SecureRenegotation, CS#connection_state.secure_renegotiation} of {_, true} -> ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE); @@ -1186,7 +1187,7 @@ calc_finished({3, 0}, Role, _PrfAlgo, MasterSecret, Handshake) -> calc_finished({3, N}, Role, PrfAlgo, MasterSecret, Handshake) -> tls_v1:finished(Role, N, PrfAlgo, MasterSecret, lists:reverse(Handshake)). -master_secret(RecordCB, Version, MasterSecret, +master_secret(_RecordCB, Version, MasterSecret, #security_parameters{ client_random = ClientRandom, server_random = ServerRandom, @@ -1201,15 +1202,15 @@ master_secret(RecordCB, Version, MasterSecret, setup_keys(Version, PrfAlgo, MasterSecret, ServerRandom, ClientRandom, HashSize, KML, EKML, IVS), - ConnStates1 = RecordCB:set_master_secret(MasterSecret, ConnectionStates), + ConnStates1 = ssl_record:set_master_secret(MasterSecret, ConnectionStates), ConnStates2 = - RecordCB:set_mac_secret(ClientWriteMacSecret, ServerWriteMacSecret, + ssl_record:set_mac_secret(ClientWriteMacSecret, ServerWriteMacSecret, Role, ConnStates1), ClientCipherState = #cipher_state{iv = ClientIV, key = ClientWriteKey}, ServerCipherState = #cipher_state{iv = ServerIV, key = ServerWriteKey}, {MasterSecret, - RecordCB:set_pending_cipher_state(ConnStates2, ClientCipherState, + ssl_record:set_pending_cipher_state(ConnStates2, ClientCipherState, ServerCipherState, Role)}. setup_keys({3,0}, _PrfAlgo, MasterSecret, @@ -1248,12 +1249,12 @@ handle_renegotiation_extension(Role, RecordCB, Version, Info, Random, CipherSuit %% hello messages %% NOTE : Role is the role of the receiver of the hello message %% currently being processed. -hello_pending_connection_states(RecordCB, Role, Version, CipherSuite, Random, Compression, +hello_pending_connection_states(_RecordCB, Role, Version, CipherSuite, Random, Compression, ConnectionStates) -> ReadState = - RecordCB:pending_connection_state(ConnectionStates, read), + ssl_record:pending_connection_state(ConnectionStates, read), WriteState = - RecordCB:pending_connection_state(ConnectionStates, write), + ssl_record:pending_connection_state(ConnectionStates, write), NewReadSecParams = hello_security_parameters(Role, Version, ReadState, CipherSuite, @@ -1263,7 +1264,7 @@ hello_pending_connection_states(RecordCB, Role, Version, CipherSuite, Random, Co hello_security_parameters(Role, Version, WriteState, CipherSuite, Random, Compression), - RecordCB:update_security_params(NewReadSecParams, + ssl_record:set_security_params(NewReadSecParams, NewWriteSecParams, ConnectionStates). diff --git a/lib/ssl/src/ssl_internal.hrl b/lib/ssl/src/ssl_internal.hrl index 3d282dcdfc..96e3280fb5 100644 --- a/lib/ssl/src/ssl_internal.hrl +++ b/lib/ssl/src/ssl_internal.hrl @@ -37,7 +37,6 @@ -type tls_atom_version() :: sslv3 | tlsv1 | 'tlsv1.1' | 'tlsv1.2'. -type certdb_ref() :: reference(). -type db_handle() :: term(). --type key_algo() :: null | rsa | dhe_rsa | dhe_dss | ecdhe_ecdsa| ecdh_ecdsa | ecdh_rsa| srp_rsa| srp_dss | psk | dhe_psk | rsa_psk | dh_anon | ecdh_anon | srp_anon. -type der_cert() :: binary(). -type private_key() :: #'RSAPrivateKey'{} | #'DSAPrivateKey'{} | #'ECPrivateKey'{}. -type issuer() :: tuple(). diff --git a/lib/ssl/src/ssl_record.erl b/lib/ssl/src/ssl_record.erl index ac56e3ab29..50a45dc16b 100644 --- a/lib/ssl/src/ssl_record.erl +++ b/lib/ssl/src/ssl_record.erl @@ -16,28 +16,87 @@ %% %% %CopyrightEnd% +%%---------------------------------------------------------------------- +%% 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_internal.hrl"). -include("ssl_record.hrl"). +-include("ssl_internal.hrl"). +-include("ssl_cipher.hrl"). +-include("ssl_alert.hrl"). --export([empty_connection_state/1, activate_pending_connection_state/2, is_correct_mac/2]). +%% Connection state handling +-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, + set_pending_cipher_state/4, + set_renegotiation_flag/2, + set_client_verify_data/3, + set_server_verify_data/3]). -empty_connection_state(ConnectionEnd) -> - SecParams = empty_security_params(ConnectionEnd), - #connection_state{security_parameters = SecParams}. +%% Encoding records +-export([encode_handshake/3, encode_alert_record/3, + encode_change_cipher_spec/2, encode_data/3]). + +%% Compression +-export([compress/3, uncompress/3, compressions/0]). + +-export([is_correct_mac/2]). + +%%==================================================================== +%% Internal application API +%%==================================================================== + +%%-------------------------------------------------------------------- +-spec init_connection_states(client | server) -> #connection_states{}. +%% +%% Description: Creates a connection_states record with appropriate +%% values for the initial SSL connection setup. +%%-------------------------------------------------------------------- +init_connection_states(Role) -> + ConnectionEnd = record_protocol_role(Role), + Current = initial_connection_state(ConnectionEnd), + Pending = empty_connection_state(ConnectionEnd), + #connection_states{current_read = Current, + pending_read = Pending, + current_write = Current, + pending_write = Pending + }. + +%%-------------------------------------------------------------------- +-spec current_connection_state(#connection_states{}, read | write) -> + #connection_state{}. +%% +%% 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; +current_connection_state(#connection_states{current_write = Current}, + write) -> + Current. + +%%-------------------------------------------------------------------- +-spec pending_connection_state(#connection_states{}, read | write) -> + 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}, + read) -> + Pending; +pending_connection_state(#connection_states{pending_write = Pending}, + write) -> + Pending. -empty_security_params(ConnectionEnd = ?CLIENT) -> - #security_parameters{connection_end = ConnectionEnd, - client_random = random()}; -empty_security_params(ConnectionEnd = ?SERVER) -> - #security_parameters{connection_end = ConnectionEnd, - server_random = random()}. -random() -> - Secs_since_1970 = calendar:datetime_to_gregorian_seconds( - calendar:universal_time()) - 62167219200, - Random_28_bytes = crypto:rand_bytes(28), - <>. %%-------------------------------------------------------------------- -spec activate_pending_connection_state(#connection_states{}, read | write) -> @@ -50,8 +109,7 @@ activate_pending_connection_state(States = #connection_states{current_read = Current, pending_read = Pending}, read) -> - %% Next epoch is a noop for SSL/TLS only uesed by DTLS - NewCurrent = Pending#connection_state{epoch = connection_state_next_epoch(Current), + NewCurrent = Pending#connection_state{epoch = dtls_next_epoch(Current), sequence_number = 0}, SecParams = Pending#connection_state.security_parameters, ConnectionEnd = SecParams#security_parameters.connection_end, @@ -66,8 +124,7 @@ activate_pending_connection_state(States = #connection_states{current_write = Current, pending_write = Pending}, write) -> - %% Next epoch is a noop for SSL/TLS only uesed by DTLS - NewCurrent = Pending#connection_state{epoch = connection_state_next_epoch(Current), + NewCurrent = Pending#connection_state{epoch = dtls_next_epoch(Current), sequence_number = 0}, SecParams = Pending#connection_state.security_parameters, ConnectionEnd = SecParams#security_parameters.connection_end, @@ -78,12 +135,305 @@ activate_pending_connection_state(States = pending_write = NewPending }. -connection_state_next_epoch(#connection_state{epoch = undefined}) -> + +%%-------------------------------------------------------------------- +-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. +%%-------------------------------------------------------------------- +set_security_params(ReadParams, WriteParams, States = + #connection_states{pending_read = Read, + pending_write = Write}) -> + States#connection_states{pending_read = + Read#connection_state{security_parameters = + ReadParams}, + pending_write = + Write#connection_state{security_parameters = + WriteParams} + }. +%%-------------------------------------------------------------------- +-spec set_mac_secret(binary(), binary(), client | server, + #connection_states{}) -> #connection_states{}. +%% +%% Description: update the mac_secret field in pending connection states +%%-------------------------------------------------------------------- +set_mac_secret(ClientWriteMacSecret, ServerWriteMacSecret, client, States) -> + set_mac_secret(ServerWriteMacSecret, ClientWriteMacSecret, States); +set_mac_secret(ClientWriteMacSecret, ServerWriteMacSecret, server, States) -> + set_mac_secret(ClientWriteMacSecret, ServerWriteMacSecret, States). + +set_mac_secret(ReadMacSecret, WriteMacSecret, + States = #connection_states{pending_read = Read, + pending_write = Write}) -> + States#connection_states{ + pending_read = Read#connection_state{mac_secret = ReadMacSecret}, + pending_write = Write#connection_state{mac_secret = WriteMacSecret} + }. + + +%%-------------------------------------------------------------------- +-spec set_master_secret(binary(), #connection_states{}) -> #connection_states{}. +%% +%% Description: Set master_secret in pending connection states +%%-------------------------------------------------------------------- +set_master_secret(MasterSecret, + States = #connection_states{pending_read = Read, + pending_write = Write}) -> + ReadSecPar = Read#connection_state.security_parameters, + Read1 = Read#connection_state{ + security_parameters = ReadSecPar#security_parameters{ + master_secret = MasterSecret}}, + WriteSecPar = Write#connection_state.security_parameters, + Write1 = Write#connection_state{ + security_parameters = WriteSecPar#security_parameters{ + master_secret = MasterSecret}}, + States#connection_states{pending_read = Read1, pending_write = Write1}. + +%%-------------------------------------------------------------------- +-spec set_renegotiation_flag(boolean(), #connection_states{}) -> #connection_states{}. +%% +%% Description: Set secure_renegotiation in pending connection states +%%-------------------------------------------------------------------- +set_renegotiation_flag(Flag, #connection_states{ + current_read = CurrentRead0, + current_write = CurrentWrite0, + pending_read = PendingRead0, + 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, + pending_write = PendingWrite}. + +%%-------------------------------------------------------------------- +-spec set_client_verify_data(current_read | current_write | current_both, + binary(), #connection_states{})-> + #connection_states{}. +%% +%% Description: Set verify data in connection states. +%%-------------------------------------------------------------------- +set_client_verify_data(current_read, Data, + #connection_states{current_read = CurrentRead0, + 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, + #connection_states{pending_read = PendingRead0, + 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, + #connection_states{current_read = CurrentRead0, + current_write = CurrentWrite0} + = ConnectionStates) -> + CurrentRead = CurrentRead0#connection_state{client_verify_data = Data}, + CurrentWrite = CurrentWrite0#connection_state{client_verify_data = Data}, + ConnectionStates#connection_states{current_read = CurrentRead, + current_write = CurrentWrite}. +%%-------------------------------------------------------------------- +-spec set_server_verify_data(current_read | current_write | current_both, + binary(), #connection_states{})-> + #connection_states{}. +%% +%% Description: Set verify data in pending connection states. +%%-------------------------------------------------------------------- +set_server_verify_data(current_write, Data, + #connection_states{pending_read = PendingRead0, + 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, + #connection_states{current_read = CurrentRead0, + 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, + #connection_states{current_read = CurrentRead0, + 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 set_pending_cipher_state(#connection_states{}, #cipher_state{}, + #cipher_state{}, client | server) -> + #connection_states{}. +%% +%% Description: Set the cipher state in the specified pending connection state. +%%-------------------------------------------------------------------- +set_pending_cipher_state(#connection_states{pending_read = Read, + pending_write = Write} = States, + ClientState, ServerState, server) -> + States#connection_states{ + pending_read = Read#connection_state{cipher_state = ClientState}, + pending_write = Write#connection_state{cipher_state = ServerState}}; + +set_pending_cipher_state(#connection_states{pending_read = Read, + pending_write = Write} = States, + ClientState, ServerState, client) -> + States#connection_states{ + pending_read = Read#connection_state{cipher_state = ServerState}, + pending_write = Write#connection_state{cipher_state = ClientState}}. + + +%%-------------------------------------------------------------------- +-spec encode_handshake(iolist(), tls_version(), #connection_states{}) -> + {iolist(), #connection_states{}}. +%% +%% Description: Encodes a handshake message to send on the ssl-socket. +%%-------------------------------------------------------------------- +encode_handshake(Frag, Version, ConnectionStates) -> + encode_plain_text(?HANDSHAKE, Version, Frag, ConnectionStates). + +%%-------------------------------------------------------------------- +-spec encode_alert_record(#alert{}, tls_version(), #connection_states{}) -> + {iolist(), #connection_states{}}. +%% +%% Description: Encodes an alert message to send on the ssl-socket. +%%-------------------------------------------------------------------- +encode_alert_record(#alert{level = Level, description = Description}, + Version, ConnectionStates) -> + encode_plain_text(?ALERT, Version, <>, + ConnectionStates). + +%%-------------------------------------------------------------------- +-spec encode_change_cipher_spec(tls_version(), #connection_states{}) -> + {iolist(), #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). + +%%-------------------------------------------------------------------- +-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). + +uncompress(?NULL, Data, CS) -> + {Data, CS}. + +compress(?NULL, Data, CS) -> + {Data, CS}. + +%%-------------------------------------------------------------------- +-spec compressions() -> [binary()]. +%% +%% Description: return a list of compressions supported (currently none) +%%-------------------------------------------------------------------- +compressions() -> + [?byte(?NULL)]. + +%%-------------------------------------------------------------------- +%%% Internal functions +%%-------------------------------------------------------------------- +empty_connection_state(ConnectionEnd) -> + SecParams = empty_security_params(ConnectionEnd), + #connection_state{security_parameters = SecParams}. + +empty_security_params(ConnectionEnd = ?CLIENT) -> + #security_parameters{connection_end = ConnectionEnd, + client_random = random()}; +empty_security_params(ConnectionEnd = ?SERVER) -> + #security_parameters{connection_end = ConnectionEnd, + server_random = random()}. +random() -> + Secs_since_1970 = calendar:datetime_to_gregorian_seconds( + calendar:universal_time()) - 62167219200, + Random_28_bytes = crypto:rand_bytes(28), + <>. + +dtls_next_epoch(#connection_state{epoch = undefined}) -> %% SSL/TLS undefined; -connection_state_next_epoch(State) -> - State#connection_state.epoch + 1. +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. + +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(<>, 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, []). + +do_split_bin(<<>>, _, Acc) -> + lists:reverse(Acc); +do_split_bin(Bin, ChunkSize, Acc) -> + case Bin of + <> -> + do_split_bin(Rest, ChunkSize, [Chunk | Acc]); + _ -> + lists:reverse(Acc, [Bin]) + end. + +protocol_module({3, _}) -> + tls_record; +protocol_module({254, _}) -> + dtls_record. diff --git a/lib/ssl/src/ssl_record.hrl b/lib/ssl/src/ssl_record.hrl index 34893ce699..c17fa53a62 100644 --- a/lib/ssl/src/ssl_record.hrl +++ b/lib/ssl/src/ssl_record.hrl @@ -68,6 +68,8 @@ exportable % boolean }). +-define(INITIAL_BYTES, 5). + -define(MAX_SEQENCE_NUMBER, 18446744073709552000). %% math:pow(2, 64) - 1 = 1.8446744073709552e19 %% Sequence numbers can not wrap so when max is about to be reached we should renegotiate. %% We will renegotiate a little before so that there will be sequence numbers left diff --git a/lib/ssl/src/tls_connection.erl b/lib/ssl/src/tls_connection.erl index 37e22015bf..5618837506 100644 --- a/lib/ssl/src/tls_connection.erl +++ b/lib/ssl/src/tls_connection.erl @@ -453,7 +453,7 @@ abbreviated(#finished{verify_data = Data} = Finished, get_current_connection_state_prf(ConnectionStates0, write), MasterSecret, Handshake) of verified -> - ConnectionStates = tls_record:set_client_verify_data(current_both, Data, ConnectionStates0), + ConnectionStates = ssl_record:set_client_verify_data(current_both, Data, ConnectionStates0), next_state_connection(abbreviated, ack_connection(State#state{connection_states = ConnectionStates})); #alert{} = Alert -> @@ -469,7 +469,7 @@ abbreviated(#finished{verify_data = Data} = Finished, get_pending_connection_state_prf(ConnectionStates0, write), MasterSecret, Handshake0) of verified -> - ConnectionStates1 = tls_record:set_server_verify_data(current_read, Data, ConnectionStates0), + ConnectionStates1 = ssl_record:set_server_verify_data(current_read, Data, ConnectionStates0), {ConnectionStates, Handshake} = finalize_handshake(State#state{connection_states = ConnectionStates1}, abbreviated), next_state_connection(abbreviated, @@ -1026,7 +1026,7 @@ handle_sync_event({prf, Secret, Label, Seed, WantedLength}, _, StateName, #state{connection_states = ConnectionStates, negotiated_version = Version} = State) -> ConnectionState = - tls_record:current_connection_state(ConnectionStates, read), + ssl_record:current_connection_state(ConnectionStates, read), SecParams = ConnectionState#connection_state.security_parameters, #security_parameters{master_secret = MasterSecret, client_random = ClientRandom, @@ -1622,7 +1622,7 @@ key_exchange(#state{role = server, key_algorithm = Algo, Algo == dh_anon -> DHKeys = public_key:generate_key(Params), ConnectionState = - tls_record:pending_connection_state(ConnectionStates0, read), + ssl_record:pending_connection_state(ConnectionStates0, read), SecParams = ConnectionState#connection_state.security_parameters, #security_parameters{client_random = ClientRandom, server_random = ServerRandom} = SecParams, @@ -1654,7 +1654,7 @@ key_exchange(#state{role = server, key_algorithm = Algo, ECDHKeys = public_key:generate_key(select_curve(State)), ConnectionState = - tls_record:pending_connection_state(ConnectionStates0, read), + ssl_record:pending_connection_state(ConnectionStates0, read), SecParams = ConnectionState#connection_state.security_parameters, #security_parameters{client_random = ClientRandom, server_random = ServerRandom} = SecParams, @@ -1683,7 +1683,7 @@ key_exchange(#state{role = server, key_algorithm = psk, transport_cb = Transport } = State) -> ConnectionState = - tls_record:pending_connection_state(ConnectionStates0, read), + ssl_record:pending_connection_state(ConnectionStates0, read), SecParams = ConnectionState#connection_state.security_parameters, #security_parameters{client_random = ClientRandom, server_random = ServerRandom} = SecParams, @@ -1710,7 +1710,7 @@ key_exchange(#state{role = server, key_algorithm = dhe_psk, } = State) -> DHKeys = public_key:generate_key(Params), ConnectionState = - tls_record:pending_connection_state(ConnectionStates0, read), + ssl_record:pending_connection_state(ConnectionStates0, read), SecParams = ConnectionState#connection_state.security_parameters, #security_parameters{client_random = ClientRandom, server_random = ServerRandom} = SecParams, @@ -1739,7 +1739,7 @@ key_exchange(#state{role = server, key_algorithm = rsa_psk, transport_cb = Transport } = State) -> ConnectionState = - tls_record:pending_connection_state(ConnectionStates0, read), + ssl_record:pending_connection_state(ConnectionStates0, read), SecParams = ConnectionState#connection_state.security_parameters, #security_parameters{client_random = ClientRandom, server_random = ServerRandom} = SecParams, @@ -1775,7 +1775,7 @@ key_exchange(#state{role = server, key_algorithm = Algo, Keys0 end, ConnectionState = - tls_record:pending_connection_state(ConnectionStates0, read), + ssl_record:pending_connection_state(ConnectionStates0, read), SecParams = ConnectionState#connection_state.security_parameters, #security_parameters{client_random = ClientRandom, server_random = ServerRandom} = SecParams, @@ -1943,7 +1943,7 @@ request_client_cert(#state{ssl_options = #ssl_options{verify = verify_peer}, transport_cb = Transport} = State) -> #connection_state{security_parameters = #security_parameters{cipher_suite = CipherSuite}} = - tls_record:pending_connection_state(ConnectionStates0, read), + ssl_record:pending_connection_state(ConnectionStates0, read), Msg = ssl_handshake:certificate_request(CipherSuite, CertDbHandle, CertDbRef, Version), {BinMsg, ConnectionStates, Handshake} = @@ -1960,7 +1960,7 @@ finalize_handshake(State, StateName) -> ConnectionStates0 = cipher_protocol(State), ConnectionStates = - tls_record:activate_pending_connection_state(ConnectionStates0, + ssl_record:activate_pending_connection_state(ConnectionStates0, write), State1 = State#state{connection_states = ConnectionStates}, @@ -2010,13 +2010,13 @@ finished(#state{role = Role, socket = Socket, negotiated_version = Version, {ConnectionStates, Handshake}. save_verify_data(client, #finished{verify_data = Data}, ConnectionStates, certify) -> - tls_record:set_client_verify_data(current_write, Data, ConnectionStates); + ssl_record:set_client_verify_data(current_write, Data, ConnectionStates); save_verify_data(server, #finished{verify_data = Data}, ConnectionStates, cipher) -> - tls_record:set_server_verify_data(current_both, Data, ConnectionStates); + ssl_record:set_server_verify_data(current_both, Data, ConnectionStates); save_verify_data(client, #finished{verify_data = Data}, ConnectionStates, abbreviated) -> - tls_record:set_client_verify_data(current_both, Data, ConnectionStates); + ssl_record:set_client_verify_data(current_both, Data, ConnectionStates); save_verify_data(server, #finished{verify_data = Data}, ConnectionStates, abbreviated) -> - tls_record:set_server_verify_data(current_write, Data, ConnectionStates). + ssl_record:set_server_verify_data(current_write, Data, ConnectionStates). handle_server_key(#server_key_exchange{exchange_keys = Keys}, #state{key_algorithm = KeyAlg, @@ -2040,7 +2040,7 @@ verify_server_key(#server_key_params{params = Params, public_key_info = PubKeyInfo, connection_states = ConnectionStates} = State) -> ConnectionState = - tls_record:pending_connection_state(ConnectionStates, read), + ssl_record:pending_connection_state(ConnectionStates, read), SecParams = ConnectionState#connection_state.security_parameters, #security_parameters{client_random = ClientRandom, server_random = ServerRandom} = SecParams, @@ -2237,12 +2237,12 @@ client_srp_master_secret(Generator, Prime, Salt, ServerPub, ClientKeys, end. cipher_role(client, Data, Session, #state{connection_states = ConnectionStates0} = State) -> - ConnectionStates = tls_record:set_server_verify_data(current_both, Data, ConnectionStates0), + ConnectionStates = ssl_record:set_server_verify_data(current_both, Data, ConnectionStates0), next_state_connection(cipher, ack_connection(State#state{session = Session, connection_states = ConnectionStates})); cipher_role(server, Data, Session, #state{connection_states = ConnectionStates0} = State) -> - ConnectionStates1 = tls_record:set_client_verify_data(current_read, Data, ConnectionStates0), + ConnectionStates1 = ssl_record:set_client_verify_data(current_read, Data, ConnectionStates0), {ConnectionStates, Handshake} = finalize_handshake(State#state{connection_states = ConnectionStates1, session = Session}, cipher), @@ -2252,16 +2252,16 @@ cipher_role(server, Data, Session, #state{connection_states = ConnectionStates0 tls_handshake_history = Handshake})). encode_alert(#alert{} = Alert, Version, ConnectionStates) -> - tls_record:encode_alert_record(Alert, Version, ConnectionStates). + ssl_record:encode_alert_record(Alert, Version, ConnectionStates). encode_change_cipher(#change_cipher_spec{}, Version, ConnectionStates) -> - tls_record:encode_change_cipher_spec(Version, ConnectionStates). + ssl_record:encode_change_cipher_spec(Version, ConnectionStates). encode_handshake(HandshakeRec, Version, ConnectionStates0, Handshake0) -> Frag = tls_handshake:encode_handshake(HandshakeRec, Version), Handshake1 = tls_handshake:update_handshake_history(Handshake0, Frag), {E, ConnectionStates1} = - tls_record:encode_handshake(Frag, Version, ConnectionStates0), + ssl_record:encode_handshake(Frag, Version, ConnectionStates0), {E, ConnectionStates1, Handshake1}. encode_packet(Data, #socket_options{packet=Packet}) -> @@ -2356,7 +2356,7 @@ write_application_data(Data0, From, #state{socket = Socket, renegotiate(State#state{send_queue = queue:in_r({From, Data}, SendQueue), renegotiation = {true, internal}}); false -> - {Msgs, ConnectionStates} = tls_record:encode_data(Data, Version, ConnectionStates0), + {Msgs, ConnectionStates} = ssl_record:encode_data(Data, Version, ConnectionStates0), Result = Transport:send(Socket, Msgs), {reply, Result, connection, State#state{connection_states = ConnectionStates}, get_timeout(State)} @@ -2561,7 +2561,7 @@ next_state(Current, Next, #ssl_tls{type = ?CHANGE_CIPHER_SPEC, fragment = <<1>>} _ChangeCipher, #state{connection_states = ConnectionStates0} = State0) -> ConnectionStates1 = - tls_record:activate_pending_connection_state(ConnectionStates0, read), + ssl_record:activate_pending_connection_state(ConnectionStates0, read), {Record, State} = next_record(State0#state{connection_states = ConnectionStates1}), next_state(Current, Next, Record, State); next_state(Current, Next, #ssl_tls{type = _Unknown}, State0) -> @@ -2613,7 +2613,7 @@ next_state_connection(StateName, #state{send_queue = Queue0, case queue:out(Queue0) of {{value, {From, Data}}, Queue} -> {Msgs, ConnectionStates} = - tls_record:encode_data(Data, Version, ConnectionStates0), + ssl_record:encode_data(Data, Version, ConnectionStates0), Result = Transport:send(Socket, Msgs), gen_fsm:reply(From, Result), next_state_connection(StateName, @@ -2658,7 +2658,7 @@ invalidate_session(server, _, Port, Session) -> initial_state(Role, Host, Port, Socket, {SSLOptions, SocketOptions}, User, {CbModule, DataTag, CloseTag, ErrorTag}) -> - ConnectionStates = tls_record:init_connection_states(Role), + ConnectionStates = ssl_record:init_connection_states(Role), SessionCacheCb = case application:get_env(ssl, session_cb) of {ok, Cb} when is_atom(Cb) -> @@ -2924,7 +2924,7 @@ renegotiate(#state{role = server, Frag = tls_handshake:encode_handshake(HelloRequest, Version), Hs0 = tls_handshake:init_handshake_history(), {BinMsg, ConnectionStates} = - tls_record:encode_handshake(Frag, Version, ConnectionStates0), + ssl_record:encode_handshake(Frag, Version, ConnectionStates0), Transport:send(Socket, BinMsg), {Record, State} = next_record(State0#state{connection_states = ConnectionStates, @@ -2999,10 +2999,10 @@ handle_trusted_certs_db(#state{cert_db_ref = Ref, end. get_current_connection_state_prf(CStates, Direction) -> - CS = tls_record:current_connection_state(CStates, Direction), + CS = ssl_record:current_connection_state(CStates, Direction), CS#connection_state.security_parameters#security_parameters.prf_algorithm. get_pending_connection_state_prf(CStates, Direction) -> - CS = tls_record:pending_connection_state(CStates, Direction), + CS = ssl_record:pending_connection_state(CStates, Direction), CS#connection_state.security_parameters#security_parameters.prf_algorithm. start_or_recv_cancel_timer(infinity, _RecvFrom) -> diff --git a/lib/ssl/src/tls_handshake.erl b/lib/ssl/src/tls_handshake.erl index 0f1d544821..02bfa69fc5 100644 --- a/lib/ssl/src/tls_handshake.erl +++ b/lib/ssl/src/tls_handshake.erl @@ -52,7 +52,7 @@ client_hello(Host, Port, ConnectionStates, } = SslOpts, Cache, CacheCb, Renegotiation, OwnCert) -> Version = tls_record:highest_protocol_version(Versions), - Pending = tls_record:pending_connection_state(ConnectionStates, read), + Pending = ssl_record:pending_connection_state(ConnectionStates, read), SecParams = Pending#connection_state.security_parameters, CipherSuites = ssl_handshake:available_suites(UserSuites, Version), @@ -64,7 +64,7 @@ client_hello(Host, Port, ConnectionStates, #client_hello{session_id = Id, client_version = Version, cipher_suites = ssl_handshake:cipher_suites(CipherSuites, Renegotiation), - compression_methods = tls_record:compressions(), + compression_methods = ssl_record:compressions(), random = SecParams#security_parameters.client_random, extensions = Extensions }. @@ -76,7 +76,7 @@ client_hello(Host, Port, ConnectionStates, %% Description: Creates a server hello message. %%-------------------------------------------------------------------- server_hello(SessionId, Version, ConnectionStates, Extensions) -> - Pending = tls_record:pending_connection_state(ConnectionStates, read), + Pending = ssl_record:pending_connection_state(ConnectionStates, read), SecParams = Pending#connection_state.security_parameters, #server_hello{server_version = Version, diff --git a/lib/ssl/src/tls_record.erl b/lib/ssl/src/tls_record.erl index 9ab512f334..54cf8d0b80 100644 --- a/lib/ssl/src/tls_record.erl +++ b/lib/ssl/src/tls_record.erl @@ -19,8 +19,7 @@ %% %%---------------------------------------------------------------------- -%% Purpose: Help functions for handling the SSL-Record protocol -%% +%% Purpose: Handle TLS/SSL record protocol. (Parts that are not shared with DTLS) %%---------------------------------------------------------------------- -module(tls_record). @@ -31,281 +30,26 @@ -include("tls_handshake.hrl"). -include("ssl_cipher.hrl"). -%% Connection state handling --export([init_connection_states/1, - current_connection_state/2, pending_connection_state/2, - update_security_params/3, - set_mac_secret/4, - set_master_secret/2, - activate_pending_connection_state/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. +%% Encoding +-export([encode_plain_text/4]). + +%% Protocol version handling -export([protocol_version/1, lowest_protocol_version/2, highest_protocol_version/1, supported_protocol_versions/0, is_acceptable_version/1, is_acceptable_version/2]). --export([compressions/0]). - -compile(inline). --define(INITIAL_BYTES, 5). - %%==================================================================== %% Internal application API %%==================================================================== -%%-------------------------------------------------------------------- --spec init_connection_states(client | server) -> #connection_states{}. -%% -%% Description: Creates a connection_states record with appropriate -%% values for the initial SSL connection setup. -%%-------------------------------------------------------------------- -init_connection_states(Role) -> - ConnectionEnd = record_protocol_role(Role), - Current = initial_connection_state(ConnectionEnd), - Pending = ssl_record:empty_connection_state(ConnectionEnd), - #connection_states{current_read = Current, - pending_read = Pending, - current_write = Current, - pending_write = Pending - }. - -%%-------------------------------------------------------------------- --spec current_connection_state(#connection_states{}, read | write) -> - #connection_state{}. -%% -%% 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; -current_connection_state(#connection_states{current_write = Current}, - write) -> - Current. - -%%-------------------------------------------------------------------- --spec pending_connection_state(#connection_states{}, read | write) -> - #connection_state{}. -%% -%% 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}, - read) -> - Pending; -pending_connection_state(#connection_states{pending_write = Pending}, - write) -> - Pending. - -%%-------------------------------------------------------------------- --spec update_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 = - #connection_states{pending_read = Read, - pending_write = Write}) -> - States#connection_states{pending_read = - Read#connection_state{security_parameters = - ReadParams}, - pending_write = - Write#connection_state{security_parameters = - WriteParams} - }. -%%-------------------------------------------------------------------- --spec set_mac_secret(binary(), binary(), client | server, - #connection_states{}) -> #connection_states{}. -%% -%% Description: update the mac_secret field in pending connection states -%%-------------------------------------------------------------------- -set_mac_secret(ClientWriteMacSecret, ServerWriteMacSecret, client, States) -> - set_mac_secret(ServerWriteMacSecret, ClientWriteMacSecret, States); -set_mac_secret(ClientWriteMacSecret, ServerWriteMacSecret, server, States) -> - set_mac_secret(ClientWriteMacSecret, ServerWriteMacSecret, States). - -set_mac_secret(ReadMacSecret, WriteMacSecret, - States = #connection_states{pending_read = Read, - pending_write = Write}) -> - States#connection_states{ - pending_read = Read#connection_state{mac_secret = ReadMacSecret}, - pending_write = Write#connection_state{mac_secret = WriteMacSecret} - }. - - -%%-------------------------------------------------------------------- --spec set_master_secret(binary(), #connection_states{}) -> #connection_states{}. -%% -%% Description: Set master_secret in pending connection states -%%-------------------------------------------------------------------- -set_master_secret(MasterSecret, - States = #connection_states{pending_read = Read, - pending_write = Write}) -> - ReadSecPar = Read#connection_state.security_parameters, - Read1 = Read#connection_state{ - security_parameters = ReadSecPar#security_parameters{ - master_secret = MasterSecret}}, - WriteSecPar = Write#connection_state.security_parameters, - Write1 = Write#connection_state{ - security_parameters = WriteSecPar#security_parameters{ - master_secret = MasterSecret}}, - States#connection_states{pending_read = Read1, pending_write = Write1}. - -%%-------------------------------------------------------------------- --spec set_renegotiation_flag(boolean(), #connection_states{}) -> #connection_states{}. -%% -%% Description: Set secure_renegotiation in pending connection states -%%-------------------------------------------------------------------- -set_renegotiation_flag(Flag, #connection_states{ - current_read = CurrentRead0, - current_write = CurrentWrite0, - pending_read = PendingRead0, - 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, - pending_write = PendingWrite}. - -%%-------------------------------------------------------------------- --spec set_client_verify_data(current_read | current_write | current_both, - binary(), #connection_states{})-> - #connection_states{}. -%% -%% Description: Set verify data in connection states. -%%-------------------------------------------------------------------- -set_client_verify_data(current_read, Data, - #connection_states{current_read = CurrentRead0, - 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, - #connection_states{pending_read = PendingRead0, - 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, - #connection_states{current_read = CurrentRead0, - current_write = CurrentWrite0} - = ConnectionStates) -> - CurrentRead = CurrentRead0#connection_state{client_verify_data = Data}, - CurrentWrite = CurrentWrite0#connection_state{client_verify_data = Data}, - ConnectionStates#connection_states{current_read = CurrentRead, - current_write = CurrentWrite}. -%%-------------------------------------------------------------------- --spec set_server_verify_data(current_read | current_write | current_both, - binary(), #connection_states{})-> - #connection_states{}. -%% -%% Description: Set verify data in pending connection states. -%%-------------------------------------------------------------------- -set_server_verify_data(current_write, Data, - #connection_states{pending_read = PendingRead0, - 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, - #connection_states{current_read = CurrentRead0, - 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, - #connection_states{current_read = CurrentRead0, - 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 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 = ssl_record: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 = ssl_record: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) -> - #connection_states{}. -%% -%% Description: Set the cipher state in the specified pending connection state. -%%-------------------------------------------------------------------- -set_pending_cipher_state(#connection_states{pending_read = Read, - pending_write = Write} = States, - ClientState, ServerState, server) -> - States#connection_states{ - pending_read = Read#connection_state{cipher_state = ClientState}, - pending_write = Write#connection_state{cipher_state = ServerState}}; - -set_pending_cipher_state(#connection_states{pending_read = Read, - pending_write = Write} = States, - ClientState, ServerState, client) -> - States#connection_states{ - 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{}. %% @@ -376,6 +120,43 @@ get_tls_records_aux(Data, Acc) -> false -> ?ALERT_REC(?FATAL, ?UNEXPECTED_MESSAGE) 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} = ssl_record:compress(CompAlg, Data, CompS0), + CS1 = CS0#connection_state{compression_state = CompS1}, + {CipherFragment, CS2} = cipher(Type, Version, Comp, CS1), + CTBin = encode_tls_cipher_text(Type, Version, CipherFragment), + {CTBin, ConnectionStates#connection_states{current_write = CS2}}. + +%%-------------------------------------------------------------------- +-spec decode_cipher_text(#ssl_tls{}, #connection_states{}) -> + {#ssl_tls{}, #connection_states{}}| #alert{}. +%% +%% Description: Decode cipher text +%%-------------------------------------------------------------------- +decode_cipher_text(#ssl_tls{type = Type, version = Version, + fragment = CipherFragment} = 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(Type, Version, CipherFragment, ReadState0) of + {PlainFragment, ReadState1} -> + {Plain, CompressionS1} = ssl_record:uncompress(CompressAlg, + PlainFragment, CompressionS0), + ConnnectionStates = ConnnectionStates0#connection_states{ + current_read = ReadState1#connection_state{ + compression_state = CompressionS1}}, + {CipherText#ssl_tls{fragment = Plain}, ConnnectionStates}; + #alert{} = Alert -> + Alert + end. + %%-------------------------------------------------------------------- -spec protocol_version(tls_atom_version() | tls_version()) -> tls_version() | tls_atom_version(). @@ -492,169 +273,40 @@ is_acceptable_version({N,_} = Version, Versions) 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{}) -> - {iolist(), #connection_states{}}. -%% -%% Description: Encodes a handshake message to send on the ssl-socket. -%%-------------------------------------------------------------------- -encode_handshake(Frag, Version, ConnectionStates) -> - encode_plain_text(?HANDSHAKE, Version, Frag, ConnectionStates). - -%%-------------------------------------------------------------------- --spec encode_alert_record(#alert{}, tls_version(), #connection_states{}) -> - {iolist(), #connection_states{}}. -%% -%% Description: Encodes an alert message to send on the ssl-socket. -%%-------------------------------------------------------------------- -encode_alert_record(#alert{level = Level, description = Description}, - Version, ConnectionStates) -> - encode_plain_text(?ALERT, Version, <>, - ConnectionStates). - -%%-------------------------------------------------------------------- --spec encode_change_cipher_spec(tls_version(), #connection_states{}) -> - {iolist(), #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). - %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- -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}. - -highest_protocol_version() -> - highest_protocol_version(supported_protocol_versions()). - -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(highest_protocol_version(), ?TLS_NULL_WITH_NULL_NULL, - SecParams). - - -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(<>, 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, []). - -do_split_bin(<<>>, _, Acc) -> - lists:reverse(Acc); -do_split_bin(Bin, ChunkSize, Acc) -> - case Bin of - <> -> - do_split_bin(Rest, ChunkSize, [Chunk | 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), [<>, 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 = +cipher(Type, Version, Fragment, + #connection_state{cipher_state = CipherS0, + sequence_number = SeqNo, + 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, + } = WriteState0) -> + MacHash = calc_mac_hash(Type, Version, Fragment, WriteState0), + {CipherFragment, CipherS1} = + ssl_cipher:cipher(BCA, CipherS0, MacHash, Fragment, Version), + WriteState = WriteState0#connection_state{cipher_state=CipherS1}, + {CipherFragment, WriteState#connection_state{sequence_number = SeqNo+1}}. + +decipher(Type, Version, CipherFragment, + #connection_state{sequence_number = SeqNo} = ReadState) -> + SP = ReadState#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), + CipherS0 = ReadState#connection_state.cipher_state, + case ssl_cipher:decipher(BCA, HashSz, CipherS0, CipherFragment, Version) of + {PlainFragment, Mac, CipherS1} -> + CS1 = ReadState#connection_state{cipher_state = CipherS1}, + MacHash = calc_mac_hash(Type, Version, PlainFragment, ReadState), case ssl_record:is_correct_mac(Mac, MacHash) of - true -> - {TLS#ssl_tls{fragment = T}, CS2}; + true -> + {PlainFragment, + CS1#connection_state{sequence_number = SeqNo+1}}; false -> ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC) end; @@ -662,26 +314,6 @@ decipher(TLS=#ssl_tls{type=Type, version=Version, fragment=Fragment}, CS0) -> 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}}. - - mac_hash({_,_}, ?NULL, _MacSecret, _SeqNo, _Type, _Length, _Fragment) -> <<>>; @@ -692,6 +324,19 @@ mac_hash({3, N} = Version, MacAlg, MacSecret, SeqNo, Type, Length, Fragment) tls_v1:mac_hash(MacAlg, MacSecret, SeqNo, Type, Version, Length, Fragment). +highest_protocol_version() -> + highest_protocol_version(supported_protocol_versions()). + sufficient_tlsv1_2_crypto_support() -> CryptoSupport = crypto:supports(), proplists:get_bool(sha256, proplists:get_value(hashs, CryptoSupport)). + +calc_mac_hash(Type, Version, + PlainFragment, #connection_state{sequence_number = SeqNo, + mac_secret = MacSecret, + security_parameters = + SecPars}) -> + Length = erlang:iolist_size(PlainFragment), + mac_hash(Version, SecPars#security_parameters.mac_algorithm, + MacSecret, SeqNo, Type, + Length, PlainFragment). diff --git a/lib/ssl/src/tls_record.hrl b/lib/ssl/src/tls_record.hrl index 0be1e35458..30d7343074 100644 --- a/lib/ssl/src/tls_record.hrl +++ b/lib/ssl/src/tls_record.hrl @@ -27,8 +27,8 @@ -define(tls_record, true). -include("ssl_record.hrl"). %% Common TLS and DTLS records and Constantes -%% Used to handle tls_plain_text, tls_compressed and tls_cipher_text +%% Used to handle tls_plain_text, tls_compressed and tls_cipher_text -record(ssl_tls, { type, version, -- cgit v1.2.3