From 2be6aa6c6a3f44d86fe401e0d467c66c3d4114aa Mon Sep 17 00:00:00 2001 From: Andreas Schultz Date: Tue, 11 Jun 2013 19:59:54 +0200 Subject: ssl: Add DTLS handshake primitivs. This code is to 99 % written by Andreas Schultz only some small changes to start integrating with OTPs DTLS solution. --- lib/ssl/src/dtls_handshake.erl | 406 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 406 insertions(+) (limited to 'lib/ssl/src') diff --git a/lib/ssl/src/dtls_handshake.erl b/lib/ssl/src/dtls_handshake.erl index b25daa59d9..492212cbae 100644 --- a/lib/ssl/src/dtls_handshake.erl +++ b/lib/ssl/src/dtls_handshake.erl @@ -16,3 +16,409 @@ %% %% %CopyrightEnd% -module(dtls_handshake). + +-include("dtls_handshake.hrl"). +-include("dtls_record.hrl"). +-include("ssl_internal.hrl"). + +-export([get_dtls_handshake/2, + dtls_handshake_new_flight/1, dtls_handshake_new_epoch/1, + encode_handshake/4]). + +-record(dtls_hs_state, {current_read_seq, starting_read_seq, highest_record_seq, fragments, completed}). + +-type dtls_handshake() :: #client_hello{} | #server_hello{} | #hello_verify_request{} | + #server_hello_done{} | #certificate{} | #certificate_request{} | + #client_key_exchange{} | #finished{} | #certificate_verify{} | + #hello_request{} | #next_protocol{}. + +%%==================================================================== +%% Internal application API +%%==================================================================== +encode_handshake(Package, Version, MsgSeq, Mss) -> + {MsgType, Bin} = enc_hs(Package, Version), + Len = byte_size(Bin), + HsHistory = [MsgType, ?uint24(Len), ?uint16(MsgSeq), ?uint24(0), ?uint24(Len), Bin], + BinMsg = dtls_split_handshake(Mss, MsgType, Len, MsgSeq, Bin, 0, []), + {HsHistory, BinMsg}. + +%-------------------------------------------------------------------- +-spec get_dtls_handshake(#ssl_tls{}, #dtls_hs_state{} | binary()) -> + {[dtls_handshake()], #ssl_tls{}}. +% +% Description: Given a DTLS state and new data from ssl_record, collects +% and returns it as a list of handshake messages, also returns a new +% DTLS state +%-------------------------------------------------------------------- +% get_dtls_handshake(Record, <<>>) -> +% get_dtls_handshake_aux(Record, dtls_hs_state_init()); +get_dtls_handshake(Record, HsState) -> + get_dtls_handshake_aux(Record, HsState). + +%-------------------------------------------------------------------- +-spec dtls_handshake_new_epoch(#dtls_hs_state{}) -> #dtls_hs_state{}. +% +% Description: Reset the DTLS decoder state for a new Epoch +%-------------------------------------------------------------------- +% dtls_handshake_new_epoch(<<>>) -> +% dtls_hs_state_init(); +dtls_handshake_new_epoch(HsState) -> + HsState#dtls_hs_state{highest_record_seq = 0, + starting_read_seq = HsState#dtls_hs_state.current_read_seq, + fragments = gb_trees:empty(), completed = []}. + +%-------------------------------------------------------------------- +-spec dtls_handshake_new_flight(integer() | undefined) -> #dtls_hs_state{}. +% +% Description: Init the DTLS decoder state for a new Flight +dtls_handshake_new_flight(ExpectedReadReq) -> + #dtls_hs_state{current_read_seq = ExpectedReadReq, + highest_record_seq = 0, + starting_read_seq = 0, + fragments = gb_trees:empty(), completed = []}. + +%%-------------------------------------------------------------------- +%%% Internal functions +%%-------------------------------------------------------------------- + +dtls_split_handshake(Mss, MsgType, Len, MsgSeq, Bin, Offset, Acc) + when byte_size(Bin) + 12 < Mss -> + FragmentLen = byte_size(Bin), + BinMsg = [MsgType, ?uint24(Len), ?uint16(MsgSeq), ?uint24(Offset), ?uint24(FragmentLen), Bin], + lists:reverse([BinMsg|Acc]); +dtls_split_handshake(Mss, MsgType, Len, MsgSeq, Bin, Offset, Acc) -> + FragmentLen = Mss - 12, + <> = Bin, + BinMsg = [MsgType, ?uint24(Len), ?uint16(MsgSeq), ?uint24(Offset), ?uint24(FragmentLen), Fragment], + dtls_split_handshake(Mss, MsgType, Len, MsgSeq, Rest, Offset + FragmentLen, [BinMsg|Acc]). + +get_dtls_handshake_aux(#ssl_tls{version = Version, + record_seq = SeqNo, + fragment = Data}, HsState) -> + get_dtls_handshake_aux(Version, SeqNo, Data, HsState). + +get_dtls_handshake_aux(Version, SeqNo, + <>, + HsState0) -> + case reassemble_dtls_fragment(SeqNo, Type, Length, MessageSeq, + FragmentOffset, FragmentLength, + Body, HsState0) of + {retransmit, HsState1} -> + case Rest of + <<>> -> + {retransmit, HsState1}; + _ -> + get_dtls_handshake_aux(Version, SeqNo, Rest, HsState1) + end; + {HsState1, HighestSeqNo, MsgBody} -> + HsState2 = dec_dtls_fragment(Version, HighestSeqNo, Type, Length, MessageSeq, MsgBody, HsState1), + HsState3 = process_dtls_fragments(Version, HsState2), + get_dtls_handshake_aux(Version, SeqNo, Rest, HsState3); + HsState2 -> + HsState3 = process_dtls_fragments(Version, HsState2), + get_dtls_handshake_aux(Version, SeqNo, Rest, HsState3) + end; + +get_dtls_handshake_aux(_Version, _SeqNo, <<>>, HsState) -> + {lists:reverse(HsState#dtls_hs_state.completed), + HsState#dtls_hs_state.highest_record_seq, + HsState#dtls_hs_state{completed = []}}. + +dec_dtls_fragment(Version, SeqNo, Type, Length, MessageSeq, MsgBody, + HsState = #dtls_hs_state{highest_record_seq = HighestSeqNo, completed = Acc}) -> + Raw = <>, + H = dec_hs(Version, Type, MsgBody), + HsState#dtls_hs_state{completed = [{H,Raw}|Acc], highest_record_seq = erlang:max(HighestSeqNo, SeqNo)}. + +process_dtls_fragments(Version, + HsState0 = #dtls_hs_state{current_read_seq = CurrentReadSeq, + fragments = Fragments0}) -> + case gb_trees:is_empty(Fragments0) of + true -> + HsState0; + _ -> + case gb_trees:smallest(Fragments0) of + {CurrentReadSeq, {SeqNo, Type, Length, CurrentReadSeq, {Length, [{0, Length}], MsgBody}}} -> + HsState1 = dtls_hs_state_process_seq(HsState0), + HsState2 = dec_dtls_fragment(Version, SeqNo, Type, Length, CurrentReadSeq, MsgBody, HsState1), + process_dtls_fragments(Version, HsState2); + _ -> + HsState0 + end + end. + +dtls_hs_state_process_seq(HsState0 = #dtls_hs_state{current_read_seq = CurrentReadSeq, + fragments = Fragments0}) -> + Fragments1 = gb_trees:delete_any(CurrentReadSeq, Fragments0), + HsState0#dtls_hs_state{current_read_seq = CurrentReadSeq + 1, + fragments = Fragments1}. + +dtls_hs_state_add_fragment(MessageSeq, Fragment, HsState0 = #dtls_hs_state{fragments = Fragments0}) -> + Fragments1 = gb_trees:enter(MessageSeq, Fragment, Fragments0), + HsState0#dtls_hs_state{fragments = Fragments1}. + +reassemble_dtls_fragment(SeqNo, Type, Length, MessageSeq, 0, Length, + Body, HsState0 = #dtls_hs_state{current_read_seq = undefined}) + when Type == ?CLIENT_HELLO; + Type == ?SERVER_HELLO; + Type == ?HELLO_VERIFY_REQUEST -> + %% First message, should be client hello + %% return the current message and set the next expected Sequence + %% + %% Note: this could (should?) be restricted further, ClientHello and + %% HelloVerifyRequest have to have message_seq = 0, ServerHello + %% can have a message_seq of 0 or 1 + %% + {HsState0#dtls_hs_state{current_read_seq = MessageSeq + 1}, SeqNo, Body}; + +reassemble_dtls_fragment(_SeqNo, _Type, Length, _MessageSeq, _, Length, + _Body, HsState = #dtls_hs_state{current_read_seq = undefined}) -> + %% not what we expected, drop it + HsState; + +reassemble_dtls_fragment(SeqNo, _Type, Length, MessageSeq, 0, Length, + Body, HsState0 = + #dtls_hs_state{starting_read_seq = StartingReadSeq}) + when MessageSeq < StartingReadSeq -> + %% this has to be the start of a new flight, let it through + %% + %% Note: this could (should?) be restricted further, the first message of a + %% new flight has to have message_seq = 0 + %% + HsState = dtls_hs_state_process_seq(HsState0), + {HsState, SeqNo, Body}; + +reassemble_dtls_fragment(_SeqNo, _Type, Length, MessageSeq, 0, Length, + _Body, HsState = + #dtls_hs_state{current_read_seq = CurrentReadSeq}) + when MessageSeq < CurrentReadSeq -> + {retransmit, HsState}; + +reassemble_dtls_fragment(_SeqNo, _Type, Length, MessageSeq, 0, Length, + _Body, HsState = #dtls_hs_state{current_read_seq = CurrentReadSeq}) + when MessageSeq < CurrentReadSeq -> + HsState; + +reassemble_dtls_fragment(SeqNo, _Type, Length, MessageSeq, 0, Length, + Body, HsState0 = #dtls_hs_state{current_read_seq = MessageSeq}) -> + %% Message fully contained and it's the current seq + HsState1 = dtls_hs_state_process_seq(HsState0), + {HsState1, SeqNo, Body}; + +reassemble_dtls_fragment(SeqNo, Type, Length, MessageSeq, 0, Length, + Body, HsState) -> + %% Message fully contained and it's the NOT the current seq -> buffer + Fragment = {SeqNo, Type, Length, MessageSeq, + dtls_fragment_init(Length, 0, Length, Body)}, + dtls_hs_state_add_fragment(MessageSeq, Fragment, HsState); + +reassemble_dtls_fragment(_SeqNo, _Type, Length, MessageSeq, FragmentOffset, FragmentLength, + _Body, + HsState = #dtls_hs_state{current_read_seq = CurrentReadSeq}) + when FragmentOffset + FragmentLength == Length andalso MessageSeq == (CurrentReadSeq - 1) -> + {retransmit, HsState}; + +reassemble_dtls_fragment(_SeqNo, _Type, _Length, MessageSeq, _FragmentOffset, _FragmentLength, + _Body, + HsState = #dtls_hs_state{current_read_seq = CurrentReadSeq}) + when MessageSeq < CurrentReadSeq -> + HsState; + +reassemble_dtls_fragment(SeqNo, Type, Length, MessageSeq, + FragmentOffset, FragmentLength, + Body, + HsState = #dtls_hs_state{fragments = Fragments0}) -> + case gb_trees:lookup(MessageSeq, Fragments0) of + {value, Fragment} -> + dtls_fragment_reassemble(SeqNo, Type, Length, MessageSeq, + FragmentOffset, FragmentLength, + Body, Fragment, HsState); + none -> + dtls_fragment_start(SeqNo, Type, Length, MessageSeq, + FragmentOffset, FragmentLength, + Body, HsState) + end. + +dtls_fragment_start(SeqNo, Type, Length, MessageSeq, + FragmentOffset, FragmentLength, + Body, HsState = #dtls_hs_state{fragments = Fragments0}) -> + Fragment = {SeqNo, Type, Length, MessageSeq, + dtls_fragment_init(Length, FragmentOffset, FragmentLength, Body)}, + Fragments1 = gb_trees:insert(MessageSeq, Fragment, Fragments0), + HsState#dtls_hs_state{fragments = Fragments1}. + +dtls_fragment_reassemble(SeqNo, Type, Length, MessageSeq, + FragmentOffset, FragmentLength, + Body, + {LastSeqNo, Type, Length, MessageSeq, FragBuffer0}, + HsState = #dtls_hs_state{fragments = Fragments0}) -> + FragBuffer1 = dtls_fragment_add(FragBuffer0, FragmentOffset, FragmentLength, Body), + Fragment = {erlang:max(SeqNo, LastSeqNo), Type, Length, MessageSeq, FragBuffer1}, + Fragments1 = gb_trees:enter(MessageSeq, Fragment, Fragments0), + HsState#dtls_hs_state{fragments = Fragments1}; + +%% Type, Length or Seq mismatch, drop everything... +%% Note: the RFC is not clear on how to handle this... +dtls_fragment_reassemble(_SeqNo, _Type, _Length, MessageSeq, + _FragmentOffset, _FragmentLength, _Body, _Fragment, + HsState = #dtls_hs_state{fragments = Fragments0}) -> + Fragments1 = gb_trees:delete_any(MessageSeq, Fragments0), + HsState#dtls_hs_state{fragments = Fragments1}. + +dtls_fragment_add({Length, FragmentList0, Bin0}, FragmentOffset, FragmentLength, Body) -> + Bin1 = dtls_fragment_bin_add(FragmentOffset, FragmentLength, Body, Bin0), + FragmentList1 = add_fragment(FragmentList0, {FragmentOffset, FragmentLength}), + {Length, FragmentList1, Bin1}. + +dtls_fragment_init(Length, 0, Length, Body) -> + {Length, [{0, Length}], Body}; +dtls_fragment_init(Length, FragmentOffset, FragmentLength, Body) -> + Bin = dtls_fragment_bin_add(FragmentOffset, FragmentLength, Body, <<0:(Length*8)>>), + {Length, [{FragmentOffset, FragmentLength}], Bin}. + +dtls_fragment_bin_add(FragmentOffset, FragmentLength, Add, Buffer) -> + <> = Buffer, + <>. + +merge_fragment_list([], Fragment, Acc) -> + lists:reverse([Fragment|Acc]); + +merge_fragment_list([H = {_, HEnd}|Rest], Frag = {FStart, _}, Acc) + when FStart > HEnd -> + merge_fragment_list(Rest, Frag, [H|Acc]); + +merge_fragment_list(Rest = [{HStart, _HEnd}|_], Frag = {_FStart, FEnd}, Acc) + when FEnd < HStart -> + lists:reverse(Acc) ++ [Frag|Rest]; + +merge_fragment_list([{HStart, HEnd}|Rest], _Frag = {FStart, FEnd}, Acc) + when + FStart =< HEnd orelse FEnd >= HStart -> + Start = erlang:min(HStart, FStart), + End = erlang:max(HEnd, FEnd), + NewFrag = {Start, End}, + merge_fragment_list(Rest, NewFrag, Acc). + +add_fragment(List, {FragmentOffset, FragmentLength}) -> + merge_fragment_list(List, {FragmentOffset, FragmentOffset + FragmentLength}, []). + + +dec_hs(_Version, ?HELLO_VERIFY_REQUEST, <>) + when Major >= 128 -> + + #hello_verify_request{ + protocol_version = {Major, Minor}, + cookie = Cookie}; + +dec_hs(_Version, ?CLIENT_HELLO, <>) + when Major >= 128 -> + + DecodedExtensions = tls_handshake:dec_hello_extensions(Extensions), + RenegotiationInfo = proplists:get_value(renegotiation_info, DecodedExtensions, undefined), + SRP = proplists:get_value(srp, DecodedExtensions, undefined), + HashSigns = proplists:get_value(hash_signs, DecodedExtensions, undefined), + EllipticCurves = proplists:get_value(elliptic_curves, DecodedExtensions, + undefined), + NextProtocolNegotiation = proplists:get_value(next_protocol_negotiation, DecodedExtensions, undefined), + + #client_hello{ + client_version = {Major,Minor}, + random = Random, + session_id = Session_ID, + cookie = Cookie, + cipher_suites = tls_handshake:decode_suites('2_bytes', CipherSuites), + compression_methods = Comp_methods, + renegotiation_info = RenegotiationInfo, + srp = SRP, + hash_signs = HashSigns, + elliptic_curves = EllipticCurves, + next_protocol_negotiation = NextProtocolNegotiation + }. + +enc_hs(#hello_verify_request{protocol_version = {Major, Minor}, + cookie = Cookie}, _Version) -> + CookieLength = byte_size(Cookie), + {?HELLO_VERIFY_REQUEST, <>}; + +enc_hs(#client_hello{client_version = {Major, Minor}, + random = Random, + session_id = SessionID, + cookie = Cookie, + cipher_suites = CipherSuites, + compression_methods = CompMethods, + renegotiation_info = RenegotiationInfo, + srp = SRP, + hash_signs = HashSigns, + ec_point_formats = EcPointFormats, + elliptic_curves = EllipticCurves, + next_protocol_negotiation = NextProtocolNegotiation}, Version) -> + SIDLength = byte_size(SessionID), + BinCookie = enc_client_hello_cookie(Version, Cookie), + BinCompMethods = list_to_binary(CompMethods), + CmLength = byte_size(BinCompMethods), + BinCipherSuites = list_to_binary(CipherSuites), + CsLength = byte_size(BinCipherSuites), + Extensions = tls_handshake:hello_extensions(RenegotiationInfo, SRP, NextProtocolNegotiation) + ++ tls_handshake:ec_hello_extensions(lists:map(fun ssl_cipher:suite_definition/1, CipherSuites), EcPointFormats) + ++ tls_handshake:ec_hello_extensions(lists:map(fun ssl_cipher:suite_definition/1, CipherSuites), EllipticCurves) + ++ tls_handshake:hello_extensions(HashSigns), + ExtensionsBin = tls_handshake:enc_hello_extensions(Extensions), + + {?CLIENT_HELLO, <>}. + +enc_client_hello_cookie(_, Cookie) -> + CookieLength = byte_size(Cookie), + <>; +enc_client_hello_cookie(_, _) -> + <<>>. + +dec_hs(_Version, ?CLIENT_HELLO, <>) -> + + DecodedExtensions = dec_hello_extensions(Extensions), + RenegotiationInfo = proplists:get_value(renegotiation_info, DecodedExtensions, undefined), + SRP = proplists:get_value(srp, DecodedExtensions, undefined), + HashSigns = proplists:get_value(hash_signs, DecodedExtensions, undefined), + EllipticCurves = proplists:get_value(elliptic_curves, DecodedExtensions, + undefined), + NextProtocolNegotiation = proplists:get_value(next_protocol_negotiation, DecodedExtensions, undefined), + + #client_hello{ + client_version = {Major,Minor}, + random = Random, + session_id = Session_ID, + cookie = Cookie, + cipher_suites = from_2bytes(CipherSuites), + compression_methods = Comp_methods, + renegotiation_info = RenegotiationInfo, + srp = SRP, + hash_signs = HashSigns, + elliptic_curves = EllipticCurves, + next_protocol_negotiation = NextProtocolNegotiation + }; + +dec_hs(_Version, ?HELLO_VERIFY_REQUEST, <>) -> + + #hello_verify_request{ + server_version = {Major,Minor}, + cookie = Cookie}; -- cgit v1.2.3 From f3be514fd1e015f78a227d25c3471dbe2cfb3d51 Mon Sep 17 00:00:00 2001 From: Ingela Anderton Andin Date: Tue, 11 Jun 2013 20:44:04 +0200 Subject: ssl: Refactor to provide common handshake functions for TLS/DTLS Common functions will be located in ssl_handshake.erl while specific functions will be located in tls_handshake.erl and dtls_handshake.erl --- lib/ssl/src/Makefile | 1 + lib/ssl/src/dtls_connection.erl | 316 +++++++ lib/ssl/src/dtls_handshake.erl | 168 ++-- lib/ssl/src/dtls_handshake.hrl | 18 +- lib/ssl/src/dtls_v1.erl | 40 + lib/ssl/src/ssl.app.src | 2 +- lib/ssl/src/ssl_handshake.erl | 1609 ++++++++++++++++++++++++++++++++++++ lib/ssl/src/ssl_handshake.hrl | 34 +- lib/ssl/src/ssl_internal.hrl | 8 + lib/ssl/src/tls_connection.erl | 107 ++- lib/ssl/src/tls_handshake.erl | 1733 ++------------------------------------- lib/ssl/src/tls_handshake.hrl | 9 +- 12 files changed, 2228 insertions(+), 1817 deletions(-) create mode 100644 lib/ssl/src/dtls_v1.erl create mode 100644 lib/ssl/src/ssl_handshake.erl (limited to 'lib/ssl/src') diff --git a/lib/ssl/src/Makefile b/lib/ssl/src/Makefile index cf9f7d5001..a467b3f608 100644 --- a/lib/ssl/src/Makefile +++ b/lib/ssl/src/Makefile @@ -58,6 +58,7 @@ MODULES= \ ssl_connection_sup \ tls_handshake \ dtls_handshake\ + ssl_handshake\ ssl_manager \ ssl_session \ ssl_session_cache \ diff --git a/lib/ssl/src/dtls_connection.erl b/lib/ssl/src/dtls_connection.erl index ac2ee0d09f..f6a1dc699e 100644 --- a/lib/ssl/src/dtls_connection.erl +++ b/lib/ssl/src/dtls_connection.erl @@ -17,3 +17,319 @@ %% %CopyrightEnd% %% -module(dtls_connection). + +%%-behaviour(gen_fsm). + +%% -include("dtls_handshake.hrl"). +%% -include("ssl_alert.hrl"). +%% -include("dtls_record.hrl"). +%% -include("ssl_cipher.hrl"). +%% -include("ssl_internal.hrl"). +%% -include("ssl_srp.hrl"). +%% -include_lib("public_key/include/public_key.hrl"). + + +%% %% Called by dtls_connection_sup +%% %%-export([start_link/7]). + +%% %% gen_fsm callbacks +%% -export([init/1, hello/2, certify/2, cipher/2, +%% abbreviated/2, connection/2, handle_event/3, +%% handle_sync_event/4, handle_info/3, terminate/3, code_change/4]). + +%% -record(message_sequences, { +%% read = 0, +%% write = 0 +%% }). + +%% -record(state, { +%% role, % client | server +%% user_application, % {MonitorRef, pid()} +%% transport_cb, % atom() - callback module +%% data_tag, % atom() - ex tcp. +%% close_tag, % atom() - ex tcp_closed +%% error_tag, % atom() - ex tcp_error +%% host, % string() | ipadress() +%% port, % integer() +%% socket, % socket() +%% ssl_options, % #ssl_options{} +%% socket_options, % #socket_options{} +%% connection_states, % #connection_states{} from ssl_record.hrl +%% message_sequences = #message_sequences{}, +%% dtls_packets = [], % Not yet handled decode ssl/tls packets. +%% dtls_record_buffer, % binary() buffer of incomplete records +%% dtls_handshake_buffer, % binary() buffer of incomplete handshakes +%% dtls_handshake_history, % tls_handshake_history() +%% dtls_cipher_texts, % list() received but not deciphered yet +%% cert_db, % +%% session, % #session{} from tls_handshake.hrl +%% session_cache, % +%% session_cache_cb, % +%% negotiated_version, % tls_version() +%% client_certificate_requested = false, +%% key_algorithm, % atom as defined by cipher_suite +%% hashsign_algorithm, % atom as defined by cipher_suite +%% public_key_info, % PKIX: {Algorithm, PublicKey, PublicKeyParams} +%% private_key, % PKIX: #'RSAPrivateKey'{} +%% diffie_hellman_params, % PKIX: #'DHParameter'{} relevant for server side +%% diffie_hellman_keys, % {PublicKey, PrivateKey} +%% psk_identity, % binary() - server psk identity hint +%% srp_params, % #srp_user{} +%% srp_keys, % {PublicKey, PrivateKey} +%% premaster_secret, % +%% file_ref_db, % ets() +%% cert_db_ref, % ref() +%% bytes_to_read, % integer(), # bytes to read in passive mode +%% user_data_buffer, % binary() +%% log_alert, % boolean() +%% renegotiation, % {boolean(), From | internal | peer} +%% start_or_recv_from, % "gen_fsm From" +%% timer, % start_or_recv_timer +%% send_queue, % queue() +%% terminated = false, % +%% allow_renegotiate = true, +%% expecting_next_protocol_negotiation = false :: boolean(), +%% next_protocol = undefined :: undefined | binary(), +%% client_ecc, % {Curves, PointFmt} +%% client_cookie = <<>> +%% }). + + + +%% %%==================================================================== +%% %% Internal application API +%% %%==================================================================== + + +%% %%==================================================================== +%% %% State functions +%% %%==================================================================== + +%% -spec hello(start | #hello_request{} | #client_hello{} | #server_hello{} | term(), +%% #state{}) -> gen_fsm_state_return(). +%% %%-------------------------------------------------------------------- +%% hello(start, #state{host = Host, port = Port, role = client, +%% ssl_options = SslOpts, +%% session = #session{own_certificate = Cert} = Session0, +%% session_cache = Cache, session_cache_cb = CacheCb, +%% connection_states = ConnectionStates0, +%% renegotiation = {Renegotiation, _}, +%% client_cookie = Cookie} = State0) -> +%% Hello = dtls_handshake:client_hello(Host, Port, Cookie, ConnectionStates0, SslOpts, +%% Cache, CacheCb, Renegotiation, Cert), + +%% Version = Hello#client_hello.client_version, +%% State1 = State0#state{negotiated_version = Version, %% Requested version +%% session = +%% Session0#session{session_id = Hello#client_hello.session_id}, +%% dtls_handshake_history = ssl_handshake:init_handshake_history()}, + +%% State2 = send_flight(Hello, waiting, State1), + +%% {Record, State} = next_record(State2), +%% next_state(hello, hello, Record, State); + +%% hello(start, #state{role = server} = State0) -> +%% {Record, State} = next_record(State0), +%% next_state(hello, hello, Record, State); + +%% hello(#hello_request{}, #state{role = client} = State0) -> +%% {Record, State} = next_record(State0), +%% next_state(hello, hello, Record, State); + +%% hello(#server_hello{cipher_suite = CipherSuite, +%% compression_method = Compression} = Hello, +%% #state{session = #session{session_id = OldId}, +%% connection_states = ConnectionStates0, +%% role = client, +%% negotiated_version = ReqVersion, +%% renegotiation = {Renegotiation, _}, +%% ssl_options = SslOptions} = State1) -> +%% State0 = flight_done(State1), +%% case ssl_handshake:hello(Hello, SslOptions, ConnectionStates0, Renegotiation) of +%% #alert{} = Alert -> +%% handle_own_alert(Alert, ReqVersion, hello, State0); +%% {Version, NewId, ConnectionStates, NextProtocol} -> +%% {KeyAlgorithm, _, _, _} = +%% ssl_cipher:suite_definition(CipherSuite), + +%% PremasterSecret = make_premaster_secret(ReqVersion, KeyAlgorithm), + +%% NewNextProtocol = case NextProtocol of +%% undefined -> +%% State0#state.next_protocol; +%% _ -> +%% NextProtocol +%% end, + +%% State = State0#state{key_algorithm = KeyAlgorithm, +%% hashsign_algorithm = default_hashsign(Version, KeyAlgorithm), +%% negotiated_version = Version, +%% connection_states = ConnectionStates, +%% premaster_secret = PremasterSecret, +%% expecting_next_protocol_negotiation = NextProtocol =/= undefined, +%% next_protocol = NewNextProtocol}, + +%% case ssl_session:is_new(OldId, NewId) of +%% true -> +%% handle_new_session(NewId, CipherSuite, Compression, +%% State#state{connection_states = ConnectionStates}); +%% false -> +%% handle_resumed_session(NewId, State#state{connection_states = ConnectionStates}) +%% end +%% end; + +%% hello(#hello_verify_request{cookie = Cookie}, +%% #state{host = Host, port = Port, +%% session = #session{own_certificate = Cert}, +%% session_cache = Cache, session_cache_cb = CacheCb, +%% ssl_options = SslOpts, +%% connection_states = ConnectionStates0, +%% renegotiation = {Renegotiation, _}} = State0) -> +%% Hello = ssl_handshake:client_hello(Host, Port, Cookie, ConnectionStates0, SslOpts, +%% Cache, CacheCb, Renegotiation, Cert), +%% State1 = State0#state{ +%% tls_handshake_history = ssl_handshake:init_handshake_history(), +%% client_cookie = Cookie}, +%% State2 = send_flight(Hello, waiting, State1), + +%% {Record, State} = next_record(State2), +%% next_state(hello, hello, Record, State); + +%% hello(Hello = #client_hello{client_version = ClientVersion}, +%% State = #state{connection_states = ConnectionStates0, +%% port = Port, session = #session{own_certificate = Cert} = Session0, +%% renegotiation = {Renegotiation, _}, +%% session_cache = Cache, +%% session_cache_cb = CacheCb, +%% ssl_options = SslOpts}) -> +%% case ssl_handshake:hello(Hello, SslOpts, {Port, Session0, Cache, CacheCb, +%% ConnectionStates0, Cert}, Renegotiation) of +%% {Version, {Type, Session}, ConnectionStates, ProtocolsToAdvertise, +%% EcPointFormats, EllipticCurves} -> +%% do_server_hello(Type, ProtocolsToAdvertise, +%% EcPointFormats, EllipticCurves, +%% State#state{connection_states = ConnectionStates, +%% negotiated_version = Version, +%% session = Session, +%% client_ecc = {EllipticCurves, EcPointFormats}}); +%% #alert{} = Alert -> +%% handle_own_alert(Alert, ClientVersion, hello, State) +%% end; + +%% hello(timeout, State) -> +%% { next_state, hello, State, hibernate }; + +%% hello(Msg, State) -> +%% handle_unexpected_message(Msg, hello, State). +%% %%-------------------------------------------------------------------- +%% -spec abbreviated(#hello_request{} | #finished{} | term(), +%% #state{}) -> gen_fsm_state_return(). +%% %%-------------------------------------------------------------------- + +%% abbreviated(timeout, State) -> +%% { next_state, abbreviated, State, hibernate }; + +%% abbreviated(Msg, State) -> +%% handle_unexpected_message(Msg, abbreviated, State). + +%% %%-------------------------------------------------------------------- +%% -spec certify(#hello_request{} | #certificate{} | #server_key_exchange{} | +%% #certificate_request{} | #server_hello_done{} | #client_key_exchange{} | term(), +%% #state{}) -> gen_fsm_state_return(). +%% %%-------------------------------------------------------------------- + + +%% certify(timeout, State) -> +%% { next_state, certify, State, hibernate }; + +%% certify(Msg, State) -> +%% handle_unexpected_message(Msg, certify, State). + + +%% %%-------------------------------------------------------------------- +%% -spec cipher(#hello_request{} | #certificate_verify{} | #finished{} | term(), +%% #state{}) -> gen_fsm_state_return(). +%% %%-------------------------------------------------------------------- + +%% cipher(timeout, State) -> +%% { next_state, cipher, State, hibernate }; + +%% cipher(Msg, State) -> +%% handle_unexpected_message(Msg, cipher, State). + +%% %%-------------------------------------------------------------------- +%% -spec connection(#hello_request{} | #client_hello{} | term(), +%% #state{}) -> gen_fsm_state_return(). +%% %%-------------------------------------------------------------------- + +%% connection(timeout, State) -> +%% {next_state, connection, State, hibernate}; + +%% connection(Msg, State) -> +%% handle_unexpected_message(Msg, connection, State). + +%% %%-------------------------------------------------------------------- +%% %%% Internal functions +%% %%-------------------------------------------------------------------- +%% handle_unexpected_message(Msg, Info, #state{negotiated_version = Version} = State) -> +%% Alert = ?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE), +%% handle_own_alert(Alert, Version, {Info, Msg}, State). + +%% send_flight(HandshakeRec, FlightState, State) -> +%% send_flight(FlightState, buffer_flight(HandshakeRec, State)). + +%% send_flight(FlightState, State = #state{negotiated_version = Version, +%% flight_buffer = Buffer}) -> + +%% State1 = do_send_flight(queue:to_list(Buffer), [], State), +%% finish_send_flight(Version, FlightState, State1). + +%% resend_flight(State = #state{negotiated_version = Version, +%% flight_state = FlightState, +%% flight_buffer = Buffer}) +%% when FlightState == finished; FlightState == waiting -> +%% State1 = do_send_flight(queue:to_list(Buffer), [], State), +%% finish_send_flight(Version, FlightState, State1); + +%% resend_flight(State) -> +%% State. + +%% flight_done(State) -> +%% cancel_dtls_retransmit_timer(State#state{flight_state = done, +%% flight_buffer = undefined}). + +%% do_send_flight([], BinMsgs, State = #state{transport_cb = Transport, socket = Socket}) -> +%% Transport:send(Socket, lists:reverse(BinMsgs)), +%% State; +%% do_send_flight([{Epoch, MsgSeq, HandshakeRec}|T], BinMsgs0, +%% State = #state{negotiated_version = Version, +%% connection_states = ConnectionStates0}) -> +%% CS0 = ssl_record:connection_state_by_epoch(ConnectionStates0, Epoch, write), +%% {BinMsgs, CS1} = encode_handshake_rec(HandshakeRec, Version, MsgSeq, BinMsgs0, CS0), +%% ConnectionStates1 = ssl_record:set_connection_state_by_epoch(ConnectionStates0, CS1, write), +%% do_send_flight(T, BinMsgs, State#state{connection_states = ConnectionStates1}). + +%% cancel_dtls_retransmit_timer(State = #state{dtls_retransmit_timer = TimerRef}) -> +%% cancel_timer(TimerRef), +%% State#state{dtls_retransmit_timer = undefined}. + +%% rearm_dtls_retransmit_timer(State = #state{dtls_retransmit_timer = undefined}) -> +%% TimerRef = erlang:start_timer(1000, self(), dtls_retransmit), +%% State#state{dtls_retransmit_timer = TimerRef}; +%% rearm_dtls_retransmit_timer(State) -> +%% State. + +%% finish_send_flight({254, _}, waiting, State) -> +%% TimerRef = erlang:start_timer(1000, self(), dtls_retransmit), +%% State#state{ +%% dtls_retransmit_timer = TimerRef, +%% last_retransmit = timestamp(), +%% flight_state = waiting}; + +%% finish_send_flight(_, FlightState, State) -> +%% State#state{flight_state = FlightState}. + +%% timestamp() -> +%% {Mega, Sec, Micro} = erlang:now(), +%% Mega * 1000000 * 1000 + Sec * 1000 + (Micro div 1000). diff --git a/lib/ssl/src/dtls_handshake.erl b/lib/ssl/src/dtls_handshake.erl index 492212cbae..8a481af76d 100644 --- a/lib/ssl/src/dtls_handshake.erl +++ b/lib/ssl/src/dtls_handshake.erl @@ -21,20 +21,80 @@ -include("dtls_record.hrl"). -include("ssl_internal.hrl"). --export([get_dtls_handshake/2, +-export([client_hello/9, hello/3, get_dtls_handshake/2, dtls_handshake_new_flight/1, dtls_handshake_new_epoch/1, encode_handshake/4]). --record(dtls_hs_state, {current_read_seq, starting_read_seq, highest_record_seq, fragments, completed}). - --type dtls_handshake() :: #client_hello{} | #server_hello{} | #hello_verify_request{} | - #server_hello_done{} | #certificate{} | #certificate_request{} | - #client_key_exchange{} | #finished{} | #certificate_verify{} | - #hello_request{} | #next_protocol{}. - %%==================================================================== %% Internal application API %%==================================================================== + +%%-------------------------------------------------------------------- +-spec client_hello(host(), inet:port_number(), term(), #connection_states{}, + #ssl_options{}, integer(), atom(), boolean(), der_cert()) -> + #client_hello{}. +%% +%% Description: Creates a client hello message. +%%-------------------------------------------------------------------- +client_hello(Host, Port, Cookie, ConnectionStates, + #ssl_options{versions = Versions, + ciphers = UserSuites + } = SslOpts, + Cache, CacheCb, Renegotiation, OwnCert) -> + Version = dtls_record:highest_protocol_version(Versions), + Pending = ssl_record:pending_connection_state(ConnectionStates, read), + SecParams = Pending#connection_state.security_parameters, + CipherSuites = ssl_handshake:available_suites(UserSuites, Version), + + Extensions = ssl_handshake:client_hello_extensions(Version, CipherSuites, + SslOpts, ConnectionStates, Renegotiation), + + Id = ssl_session:client_id({Host, Port, SslOpts}, Cache, CacheCb, OwnCert), + + #client_hello{session_id = Id, + client_version = Version, + cipher_suites = ssl_handshake:cipher_suites(CipherSuites, Renegotiation), + compression_methods = tls_record:compressions(), + random = SecParams#security_parameters.client_random, + cookie = Cookie, + extensions = Extensions + }. + +hello(Address, Port, + #ssl_tls{epoch = Epoch, record_seq = Seq, + version = Version} = Record) -> + {[{Hello, _}], _, _} = + ssl_handshake:get_dtls_handshake(Record, + ssl_handshake:dtls_handshake_new_flight(undefined)), + #client_hello{client_version = {Major, Minor}, + random = Random, + session_id = SessionId, + cipher_suites = CipherSuites, + compression_methods = CompressionMethods} = Hello, + CookieData = [address_to_bin(Address, Port), + <>, + Random, SessionId, CipherSuites, CompressionMethods], + Cookie = crypto:hmac(sha, <<"secret">>, CookieData), + + case Hello of + #client_hello{cookie = Cookie} -> + 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), + {reply, HelloVerifyRequest} + end. + +address_to_bin({A,B,C,D}, Port) -> + <<0:80,16#ffff:16,A,B,C,D,Port:16>>; +address_to_bin({A,B,C,D,E,F,G,H}, Port) -> + <>. + +%%-------------------------------------------------------------------- encode_handshake(Package, Version, MsgSeq, Mss) -> {MsgType, Bin} = enc_hs(Package, Version), Len = byte_size(Bin), @@ -130,7 +190,7 @@ get_dtls_handshake_aux(_Version, _SeqNo, <<>>, HsState) -> dec_dtls_fragment(Version, SeqNo, Type, Length, MessageSeq, MsgBody, HsState = #dtls_hs_state{highest_record_seq = HighestSeqNo, completed = Acc}) -> Raw = <>, - H = dec_hs(Version, Type, MsgBody), + H = decode_handshake(Version, Type, MsgBody), HsState#dtls_hs_state{completed = [{H,Raw}|Acc], highest_record_seq = erlang:max(HighestSeqNo, SeqNo)}. process_dtls_fragments(Version, @@ -305,45 +365,6 @@ merge_fragment_list([{HStart, HEnd}|Rest], _Frag = {FStart, FEnd}, Acc) add_fragment(List, {FragmentOffset, FragmentLength}) -> merge_fragment_list(List, {FragmentOffset, FragmentOffset + FragmentLength}, []). - -dec_hs(_Version, ?HELLO_VERIFY_REQUEST, <>) - when Major >= 128 -> - - #hello_verify_request{ - protocol_version = {Major, Minor}, - cookie = Cookie}; - -dec_hs(_Version, ?CLIENT_HELLO, <>) - when Major >= 128 -> - - DecodedExtensions = tls_handshake:dec_hello_extensions(Extensions), - RenegotiationInfo = proplists:get_value(renegotiation_info, DecodedExtensions, undefined), - SRP = proplists:get_value(srp, DecodedExtensions, undefined), - HashSigns = proplists:get_value(hash_signs, DecodedExtensions, undefined), - EllipticCurves = proplists:get_value(elliptic_curves, DecodedExtensions, - undefined), - NextProtocolNegotiation = proplists:get_value(next_protocol_negotiation, DecodedExtensions, undefined), - - #client_hello{ - client_version = {Major,Minor}, - random = Random, - session_id = Session_ID, - cookie = Cookie, - cipher_suites = tls_handshake:decode_suites('2_bytes', CipherSuites), - compression_methods = Comp_methods, - renegotiation_info = RenegotiationInfo, - srp = SRP, - hash_signs = HashSigns, - elliptic_curves = EllipticCurves, - next_protocol_negotiation = NextProtocolNegotiation - }. - enc_hs(#hello_verify_request{protocol_version = {Major, Minor}, cookie = Cookie}, _Version) -> CookieLength = byte_size(Cookie), @@ -357,68 +378,53 @@ enc_hs(#client_hello{client_version = {Major, Minor}, cookie = Cookie, cipher_suites = CipherSuites, compression_methods = CompMethods, - renegotiation_info = RenegotiationInfo, - srp = SRP, - hash_signs = HashSigns, - ec_point_formats = EcPointFormats, - elliptic_curves = EllipticCurves, - next_protocol_negotiation = NextProtocolNegotiation}, Version) -> + extensions = HelloExtensions}, Version) -> SIDLength = byte_size(SessionID), BinCookie = enc_client_hello_cookie(Version, Cookie), BinCompMethods = list_to_binary(CompMethods), CmLength = byte_size(BinCompMethods), BinCipherSuites = list_to_binary(CipherSuites), CsLength = byte_size(BinCipherSuites), - Extensions = tls_handshake:hello_extensions(RenegotiationInfo, SRP, NextProtocolNegotiation) - ++ tls_handshake:ec_hello_extensions(lists:map(fun ssl_cipher:suite_definition/1, CipherSuites), EcPointFormats) - ++ tls_handshake:ec_hello_extensions(lists:map(fun ssl_cipher:suite_definition/1, CipherSuites), EllipticCurves) - ++ tls_handshake:hello_extensions(HashSigns), - ExtensionsBin = tls_handshake:enc_hello_extensions(Extensions), + ExtensionsBin = ssl_handshake:encode_hello_extensions(HelloExtensions), {?CLIENT_HELLO, <>}. + ?UINT16(CsLength), BinCipherSuites/binary, + ?BYTE(CmLength), BinCompMethods/binary, ExtensionsBin/binary>>}; +enc_hs(HandshakeMsg, Version) -> + ssl_handshake:encode_handshake(HandshakeMsg, Version). +enc_client_hello_cookie(_, <<>>) -> + <<>>; enc_client_hello_cookie(_, Cookie) -> CookieLength = byte_size(Cookie), - <>; -enc_client_hello_cookie(_, _) -> - <<>>. + <>. -dec_hs(_Version, ?CLIENT_HELLO, <>) -> - DecodedExtensions = dec_hello_extensions(Extensions), - RenegotiationInfo = proplists:get_value(renegotiation_info, DecodedExtensions, undefined), - SRP = proplists:get_value(srp, DecodedExtensions, undefined), - HashSigns = proplists:get_value(hash_signs, DecodedExtensions, undefined), - EllipticCurves = proplists:get_value(elliptic_curves, DecodedExtensions, - undefined), - NextProtocolNegotiation = proplists:get_value(next_protocol_negotiation, DecodedExtensions, undefined), + DecodedExtensions = ssl_handshake:decode_hello_extensions(Extensions), #client_hello{ client_version = {Major,Minor}, random = Random, session_id = Session_ID, cookie = Cookie, - cipher_suites = from_2bytes(CipherSuites), + cipher_suites = tls_handshake:decode_suites('2_bytes', CipherSuites), compression_methods = Comp_methods, - renegotiation_info = RenegotiationInfo, - srp = SRP, - hash_signs = HashSigns, - elliptic_curves = EllipticCurves, - next_protocol_negotiation = NextProtocolNegotiation + extensions = DecodedExtensions }; -dec_hs(_Version, ?HELLO_VERIFY_REQUEST, <>) -> +decode_handshake(_Version, ?HELLO_VERIFY_REQUEST, <>) -> #hello_verify_request{ - server_version = {Major,Minor}, + protocol_version = {Major,Minor}, cookie = Cookie}; +decode_handshake(Version, Tag, Msg) -> + ssl_handshake:decode_handshake(Version, Tag, Msg). diff --git a/lib/ssl/src/dtls_handshake.hrl b/lib/ssl/src/dtls_handshake.hrl index db7b8596ae..5bdf45f627 100644 --- a/lib/ssl/src/dtls_handshake.hrl +++ b/lib/ssl/src/dtls_handshake.hrl @@ -27,6 +27,8 @@ -include("ssl_handshake.hrl"). %% Common TLS and DTLS records and Constantes +-define(HELLO_VERIFY_REQUEST, 3). + -record(client_hello, { client_version, random, @@ -35,16 +37,22 @@ cipher_suites, % cipher_suites<2..2^16-1> compression_methods, % compression_methods<1..2^8-1>, %% Extensions - renegotiation_info, - hash_signs, % supported combinations of hashes/signature algos - next_protocol_negotiation = undefined % [binary()] + extensions }). --record(hello_verify_request { +-record(hello_verify_request, { protocol_version, cookie }). --define(HELLO_VERIFY_REQUEST, 3). +-record(dtls_hs_state, + {current_read_seq, + starting_read_seq, + highest_record_seq, + fragments, + completed + }). + +-type dtls_handshake() :: #client_hello{} | #hello_verify_request{} | ssl_handshake(). -endif. % -ifdef(dtls_handshake). diff --git a/lib/ssl/src/dtls_v1.erl b/lib/ssl/src/dtls_v1.erl new file mode 100644 index 0000000000..930998b460 --- /dev/null +++ b/lib/ssl/src/dtls_v1.erl @@ -0,0 +1,40 @@ +%% +%% %CopyrightBegin% +%% +%% 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 +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% +-module(dtls_v1). + +-include("ssl_cipher.hrl"). + +-export([suites/1, mac_hash/7, ecc_curves/1]). + +-spec suites(Minor:: 253|255) -> [cipher_suite()]. + +suites(Minor) -> + tls_v1:suites(corresponding_minor_tls_version(Minor)); + +mac_hash(Version, MacAlg, MacSecret, SeqNo, Type, Length, Fragment) -> + tls_v1:mac_hash(MacAlg, MacSecret, SeqNo, Type, Version, + Length, Fragment). + +ecc_curves({_Major, Minor}) -> + tls_v1:ecc_curves(corresponding_minor_tls_version(Minor)). + +corresponding_minor_tls_version(255) -> + 2; +corresponding_minor_tls_version(253) -> + 3. diff --git a/lib/ssl/src/ssl.app.src b/lib/ssl/src/ssl.app.src index 582a60635f..0a2ae92e9d 100644 --- a/lib/ssl/src/ssl.app.src +++ b/lib/ssl/src/ssl.app.src @@ -29,7 +29,7 @@ ssl_socket, %%ssl_record, ssl_manager, - %%ssl_handshake, + ssl_handshake, ssl_connection_sup, %%ssl_connection, ssl_cipher, diff --git a/lib/ssl/src/ssl_handshake.erl b/lib/ssl/src/ssl_handshake.erl new file mode 100644 index 0000000000..0f5d5def48 --- /dev/null +++ b/lib/ssl/src/ssl_handshake.erl @@ -0,0 +1,1609 @@ +%% +%% %CopyrightBegin% +%% +%% 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 +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% + +%---------------------------------------------------------------------- +%% Purpose: Help funtions for handling the SSL-handshake protocol +%%---------------------------------------------------------------------- + +-module(ssl_handshake). + +-include("ssl_handshake.hrl"). +-include("ssl_record.hrl"). +-include("ssl_cipher.hrl"). +-include("ssl_alert.hrl"). +-include("ssl_internal.hrl"). +-include("ssl_srp.hrl"). +-include_lib("public_key/include/public_key.hrl"). + +%% Handshake messages +-export([hello_request/0, server_hello_done/0, + certificate/4, certificate_request/3, key_exchange/3, + finished/5, next_protocol/1]). + +%% Handle handshake messages +-export([certify/7, client_certificate_verify/6, certificate_verify/6, verify_signature/5, + master_secret/5, server_key_exchange_hash/2, verify_connection/6]). + +%% Encode/Decode +-export([encode_handshake/2, encode_hello_extensions/1, + encode_client_protocol_negotiation/2, encode_protocols_advertised_on_server/1, + decode_handshake/3, decode_hello_extensions/1, + decode_server_key/3, decode_client_key/3, + decode_suites/2 + ]). + +%% Cipher suites handling +-export([available_suites/2, available_suites/3, cipher_suites/2, + select_session/10]). + +%% Extensions handling +-export([client_hello_extensions/5, + handle_client_hello_extensions/8, %% Returns server hello extensions + handle_server_hello_extensions/9 + ]). + +%% MISC +-export([select_version/3, prf/5, decrypt_premaster_secret/2]). + +%%==================================================================== +%% Internal application API +%%==================================================================== + +%% ---------- Create handshake messages ---------- + +%%-------------------------------------------------------------------- +-spec hello_request() -> #hello_request{}. +%% +%% Description: Creates a hello request message sent by server to +%% trigger renegotiation. +%%-------------------------------------------------------------------- +hello_request() -> + #hello_request{}. + +%%-------------------------------------------------------------------- +-spec server_hello_done() -> #server_hello_done{}. +%% +%% Description: Creates a server hello done message. +%%-------------------------------------------------------------------- +server_hello_done() -> + #server_hello_done{}. + +client_hello_extensions(Version = {Major, Minor}, CipherSuites, SslOpts, ConnectionStates, Renegotiation) -> + {EcPointFormats, EllipticCurves} = + case advertises_ec_ciphers(lists:map(fun ssl_cipher:suite_definition/1, CipherSuites)) of + true -> + ecc_extensions(tls_v1, Version); + false -> + {undefined, undefined} + end, + + HashSign = if + Major == 3, Minor >=3 -> + default_hash_signs(); + true -> + undefined + end, + + SRP = srp_user(SslOpts), + + #hello_extensions{ + renegotiation_info = renegotiation_info(tls_record, client, + ConnectionStates, Renegotiation), + srp = SRP, + hash_signs = HashSign, + ec_point_formats = EcPointFormats, + elliptic_curves = EllipticCurves, + next_protocol_negotiation = + encode_client_protocol_negotiation(SslOpts#ssl_options.next_protocol_selector, + Renegotiation)}. + +%%-------------------------------------------------------------------- +-spec certificate(der_cert(), db_handle(), certdb_ref(), client | server) -> #certificate{} | #alert{}. +%% +%% Description: Creates a certificate message. +%%-------------------------------------------------------------------- +certificate(OwnCert, CertDbHandle, CertDbRef, client) -> + Chain = + case ssl_certificate:certificate_chain(OwnCert, CertDbHandle, CertDbRef) of + {ok, CertChain} -> + CertChain; + {error, _} -> + %% If no suitable certificate is available, the client + %% SHOULD send a certificate message containing no + %% certificates. (chapter 7.4.6. RFC 4346) + [] + end, + #certificate{asn1_certificates = Chain}; + +certificate(OwnCert, CertDbHandle, CertDbRef, server) -> + case ssl_certificate:certificate_chain(OwnCert, CertDbHandle, CertDbRef) of + {ok, Chain} -> + #certificate{asn1_certificates = Chain}; + {error, _} -> + ?ALERT_REC(?FATAL, ?INTERNAL_ERROR) + end. + +%%-------------------------------------------------------------------- +-spec next_protocol(binary()) -> #next_protocol{}. +%% +%% Description: Creates a next protocol message +%%------------------------------------------------------------------- +next_protocol(SelectedProtocol) -> + #next_protocol{selected_protocol = SelectedProtocol}. + +%%-------------------------------------------------------------------- +-spec client_certificate_verify(undefined | der_cert(), binary(), + tls_version(), term(), private_key(), + tls_handshake_history()) -> + #certificate_verify{} | ignore | #alert{}. +%% +%% Description: Creates a certificate_verify message, called by the client. +%%-------------------------------------------------------------------- +client_certificate_verify(undefined, _, _, _, _, _) -> + ignore; +client_certificate_verify(_, _, _, _, undefined, _) -> + ignore; +client_certificate_verify(OwnCert, MasterSecret, Version, + {HashAlgo, SignAlgo}, + PrivateKey, {Handshake, _}) -> + case public_key:pkix_is_fixed_dh_cert(OwnCert) of + true -> + ?ALERT_REC(?FATAL, ?UNSUPPORTED_CERTIFICATE); + false -> + Hashes = + calc_certificate_verify(Version, HashAlgo, MasterSecret, Handshake), + Signed = digitally_signed(Version, Hashes, HashAlgo, PrivateKey), + #certificate_verify{signature = Signed, hashsign_algorithm = {HashAlgo, SignAlgo}} + end. + +%%-------------------------------------------------------------------- +-spec certificate_request(erl_cipher_suite(), db_handle(), certdb_ref()) -> + #certificate_request{}. +%% +%% Description: Creates a certificate_request message, called by the server. +%%-------------------------------------------------------------------- +certificate_request(CipherSuite, CertDbHandle, CertDbRef) -> + Types = certificate_types(CipherSuite), + HashSigns = default_hash_signs(), + Authorities = certificate_authorities(CertDbHandle, CertDbRef), + #certificate_request{ + certificate_types = Types, + hashsign_algorithms = HashSigns, + certificate_authorities = Authorities + }. +%%-------------------------------------------------------------------- +-spec key_exchange(client | server, tls_version(), + {premaster_secret, binary(), public_key_info()} | + {dh, binary()} | + {dh, {binary(), binary()}, #'DHParameter'{}, {HashAlgo::atom(), SignAlgo::atom()}, + binary(), binary(), private_key()} | + {ecdh, #'ECPrivateKey'{}} | + {psk, binary()} | + {dhe_psk, binary(), binary()} | + {srp, {binary(), binary()}, #srp_user{}, {HashAlgo::atom(), SignAlgo::atom()}, + binary(), binary(), private_key()}) -> + #client_key_exchange{} | #server_key_exchange{}. + +%% +%% Description: Creates a keyexchange message. +%%-------------------------------------------------------------------- +key_exchange(client, _Version, {premaster_secret, Secret, {_, PublicKey, _}}) -> + EncPremasterSecret = + encrypted_premaster_secret(Secret, PublicKey), + #client_key_exchange{exchange_keys = EncPremasterSecret}; + +key_exchange(client, _Version, {dh, PublicKey}) -> + #client_key_exchange{ + exchange_keys = #client_diffie_hellman_public{ + dh_public = PublicKey} + }; + +key_exchange(client, _Version, {ecdh, #'ECPrivateKey'{publicKey = {0, ECPublicKey}}}) -> + #client_key_exchange{ + exchange_keys = #client_ec_diffie_hellman_public{ + dh_public = ECPublicKey} + }; + +key_exchange(client, _Version, {psk, Identity}) -> + #client_key_exchange{ + exchange_keys = #client_psk_identity{ + identity = Identity} + }; + +key_exchange(client, _Version, {dhe_psk, Identity, PublicKey}) -> + #client_key_exchange{ + exchange_keys = #client_dhe_psk_identity{ + identity = Identity, + dh_public = PublicKey} + }; + +key_exchange(client, _Version, {psk_premaster_secret, PskIdentity, Secret, {_, PublicKey, _}}) -> + EncPremasterSecret = + encrypted_premaster_secret(Secret, PublicKey), + #client_key_exchange{ + exchange_keys = #client_rsa_psk_identity{ + identity = PskIdentity, + exchange_keys = EncPremasterSecret}}; + +key_exchange(client, _Version, {srp, PublicKey}) -> + #client_key_exchange{ + exchange_keys = #client_srp_public{ + srp_a = PublicKey} + }; + +key_exchange(server, Version, {dh, {PublicKey, _}, + #'DHParameter'{prime = P, base = G}, + HashSign, ClientRandom, ServerRandom, PrivateKey}) -> + ServerDHParams = #server_dh_params{dh_p = int_to_bin(P), + dh_g = int_to_bin(G), dh_y = PublicKey}, + enc_server_key_exchange(Version, ServerDHParams, HashSign, + ClientRandom, ServerRandom, PrivateKey); + +key_exchange(server, Version, {ecdh, #'ECPrivateKey'{publicKey = {0, ECPublicKey}, + parameters = ECCurve}, HashSign, + ClientRandom, ServerRandom, PrivateKey}) -> + ServerECParams = #server_ecdh_params{curve = ECCurve, public = ECPublicKey}, + enc_server_key_exchange(Version, ServerECParams, HashSign, + ClientRandom, ServerRandom, PrivateKey); + +key_exchange(server, Version, {psk, PskIdentityHint, + HashSign, ClientRandom, ServerRandom, PrivateKey}) -> + ServerPSKParams = #server_psk_params{hint = PskIdentityHint}, + enc_server_key_exchange(Version, ServerPSKParams, HashSign, + ClientRandom, ServerRandom, PrivateKey); + +key_exchange(server, Version, {dhe_psk, PskIdentityHint, {PublicKey, _}, + #'DHParameter'{prime = P, base = G}, + HashSign, ClientRandom, ServerRandom, PrivateKey}) -> + ServerEDHPSKParams = #server_dhe_psk_params{ + hint = PskIdentityHint, + dh_params = #server_dh_params{dh_p = int_to_bin(P), + dh_g = int_to_bin(G), dh_y = PublicKey} + }, + enc_server_key_exchange(Version, ServerEDHPSKParams, + HashSign, ClientRandom, ServerRandom, PrivateKey); + +key_exchange(server, Version, {srp, {PublicKey, _}, + #srp_user{generator = Generator, prime = Prime, + salt = Salt}, + HashSign, ClientRandom, ServerRandom, PrivateKey}) -> + ServerSRPParams = #server_srp_params{srp_n = Prime, srp_g = Generator, + srp_s = Salt, srp_b = PublicKey}, + enc_server_key_exchange(Version, ServerSRPParams, HashSign, + ClientRandom, ServerRandom, PrivateKey). + +%%-------------------------------------------------------------------- +-spec finished(tls_version(), client | server, integer(), binary(), tls_handshake_history()) -> + #finished{}. +%% +%% Description: Creates a handshake finished message +%%------------------------------------------------------------------- +finished(Version, Role, PrfAlgo, MasterSecret, {Handshake, _}) -> % use the current handshake + #finished{verify_data = + calc_finished(Version, Role, PrfAlgo, MasterSecret, Handshake)}. + +%% ---------- Handle handshake messages ---------- + +%%-------------------------------------------------------------------- +-spec certificate_verify(binary(), public_key_info(), tls_version(), term(), + binary(), tls_handshake_history()) -> valid | #alert{}. +%% +%% Description: Checks that the certificate_verify message is valid. +%%-------------------------------------------------------------------- +certificate_verify(Signature, PublicKeyInfo, Version, + HashSign = {HashAlgo, _}, MasterSecret, {_, Handshake}) -> + Hash = calc_certificate_verify(Version, HashAlgo, MasterSecret, Handshake), + case verify_signature(Version, Hash, HashSign, Signature, PublicKeyInfo) of + true -> + valid; + _ -> + ?ALERT_REC(?FATAL, ?BAD_CERTIFICATE) + end. +%%-------------------------------------------------------------------- +-spec verify_signature(tls_version(), binary(), {term(), term()}, binary(), + public_key_info()) -> true | false. +%% +%% Description: Checks that a public_key signature is valid. +%%-------------------------------------------------------------------- +verify_signature(_Version, _Hash, {_HashAlgo, anon}, _Signature, _) -> + true; +verify_signature({3, Minor}, Hash, {HashAlgo, rsa}, Signature, {?rsaEncryption, PubKey, _PubKeyParams}) + when Minor >= 3 -> + public_key:verify({digest, Hash}, HashAlgo, Signature, PubKey); +verify_signature(_Version, Hash, _HashAlgo, Signature, {?rsaEncryption, PubKey, _PubKeyParams}) -> + case public_key:decrypt_public(Signature, PubKey, + [{rsa_pad, rsa_pkcs1_padding}]) of + Hash -> true; + _ -> false + end; +verify_signature(_Version, Hash, {HashAlgo, dsa}, Signature, {?'id-dsa', PublicKey, PublicKeyParams}) -> + public_key:verify({digest, Hash}, HashAlgo, Signature, {PublicKey, PublicKeyParams}); +verify_signature(_Version, Hash, {HashAlgo, ecdsa}, Signature, + {?'id-ecPublicKey', PublicKey, PublicKeyParams}) -> + public_key:verify({digest, Hash}, HashAlgo, Signature, {PublicKey, PublicKeyParams}). + +%%-------------------------------------------------------------------- +-spec certify(#certificate{}, db_handle(), certdb_ref(), integer() | nolimit, + verify_peer | verify_none, {fun(), term}, + client | server) -> {der_cert(), public_key_info()} | #alert{}. +%% +%% Description: Handles a certificate handshake message +%%-------------------------------------------------------------------- +certify(#certificate{asn1_certificates = ASN1Certs}, CertDbHandle, CertDbRef, + MaxPathLen, _Verify, VerifyFunAndState, Role) -> + [PeerCert | _] = ASN1Certs, + + ValidationFunAndState = + case VerifyFunAndState of + undefined -> + {fun(OtpCert, ExtensionOrVerifyResult, SslState) -> + ssl_certificate:validate_extension(OtpCert, + ExtensionOrVerifyResult, SslState) + end, Role}; + {Fun, UserState0} -> + {fun(OtpCert, {extension, _} = Extension, {SslState, UserState}) -> + case ssl_certificate:validate_extension(OtpCert, + Extension, + SslState) of + {valid, NewSslState} -> + {valid, {NewSslState, UserState}}; + {fail, Reason} -> + apply_user_fun(Fun, OtpCert, Reason, UserState, + SslState); + {unknown, _} -> + apply_user_fun(Fun, OtpCert, + Extension, UserState, SslState) + end; + (OtpCert, VerifyResult, {SslState, UserState}) -> + apply_user_fun(Fun, OtpCert, VerifyResult, UserState, + SslState) + end, {Role, UserState0}} + end, + + try + {TrustedErlCert, CertPath} = + ssl_certificate:trusted_cert_and_path(ASN1Certs, CertDbHandle, CertDbRef), + case public_key:pkix_path_validation(TrustedErlCert, + CertPath, + [{max_path_length, + MaxPathLen}, + {verify_fun, ValidationFunAndState}]) of + {ok, {PublicKeyInfo,_}} -> + {PeerCert, PublicKeyInfo}; + {error, Reason} -> + path_validation_alert(Reason) + end + catch + error:_ -> + %% ASN-1 decode of certificate somehow failed + ?ALERT_REC(?FATAL, ?CERTIFICATE_UNKNOWN) + end. + +%%-------------------------------------------------------------------- +-spec verify_connection(tls_version(), #finished{}, client | server, integer(), binary(), + tls_handshake_history()) -> verified | #alert{}. +%% +%% Description: Checks the ssl handshake finished message to verify +%% the connection. +%%------------------------------------------------------------------- +verify_connection(Version, #finished{verify_data = Data}, + Role, PrfAlgo, MasterSecret, {_, Handshake}) -> + %% use the previous hashes + case calc_finished(Version, Role, PrfAlgo, MasterSecret, Handshake) of + Data -> + verified; + _ -> + ?ALERT_REC(?FATAL, ?DECRYPT_ERROR) + end. +%%-------------------------------------------------------------------- +-spec decrypt_premaster_secret(binary(), #'RSAPrivateKey'{}) -> binary(). + +%% +%% Description: Public key decryption using the private key. +%%-------------------------------------------------------------------- +decrypt_premaster_secret(Secret, RSAPrivateKey) -> + try public_key:decrypt_private(Secret, RSAPrivateKey, + [{rsa_pad, rsa_pkcs1_padding}]) + catch + _:_ -> + throw(?ALERT_REC(?FATAL, ?DECRYPT_ERROR)) + end. +%%-------------------------------------------------------------------- +-spec server_key_exchange_hash(md5sha | md5 | sha | sha224 |sha256 | sha384 | sha512, binary()) -> binary(). +%% +%% Description: Calculate server key exchange hash +%%-------------------------------------------------------------------- +server_key_exchange_hash(md5sha, Value) -> + MD5 = crypto:hash(md5, Value), + SHA = crypto:hash(sha, Value), + <>; + +server_key_exchange_hash(Hash, Value) -> + crypto:hash(Hash, Value). +%%-------------------------------------------------------------------- +-spec prf(tls_version(), binary(), binary(), [binary()], non_neg_integer()) -> + {ok, binary()} | {error, undefined}. +%% +%% Description: use the TLS PRF to generate key material +%%-------------------------------------------------------------------- +prf({3,0}, _, _, _, _) -> + {error, undefined}; +prf({3,1}, Secret, Label, Seed, WantedLength) -> + {ok, tls_v1:prf(?MD5SHA, Secret, Label, Seed, WantedLength)}; +prf({3,_N}, Secret, Label, Seed, WantedLength) -> + {ok, tls_v1:prf(?SHA256, Secret, Label, Seed, WantedLength)}. + +%%-------------------------------------------------------------------- +-spec master_secret(atom(), tls_version(), #session{} | binary(), #connection_states{}, + client | server) -> {binary(), #connection_states{}} | #alert{}. +%% +%% Description: Sets or calculates the master secret and calculate keys, +%% updating the pending connection states. The Mastersecret and the update +%% connection states are returned or an alert if the calculation fails. +%%------------------------------------------------------------------- +master_secret(RecordCB, Version, #session{master_secret = Mastersecret}, + ConnectionStates, Role) -> + ConnectionState = + RecordCB:pending_connection_state(ConnectionStates, read), + SecParams = ConnectionState#connection_state.security_parameters, + try master_secret(RecordCB, Version, Mastersecret, SecParams, + ConnectionStates, Role) + catch + exit:Reason -> + Report = io_lib:format("Key calculation failed due to ~p", + [Reason]), + error_logger:error_report(Report), + ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE) + end; + +master_secret(RecordCB, Version, PremasterSecret, ConnectionStates, Role) -> + ConnectionState = + RecordCB:pending_connection_state(ConnectionStates, read), + SecParams = ConnectionState#connection_state.security_parameters, + #security_parameters{prf_algorithm = PrfAlgo, + client_random = ClientRandom, + server_random = ServerRandom} = SecParams, + try master_secret(RecordCB, Version, + calc_master_secret(Version,PrfAlgo,PremasterSecret, + ClientRandom, ServerRandom), + SecParams, ConnectionStates, Role) + catch + exit:Reason -> + Report = io_lib:format("Master secret calculation failed" + " due to ~p", [Reason]), + error_logger:error_report(Report), + ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE) + end. + +%%-------------Encode/Decode -------------------------------- +encode_handshake(#next_protocol{selected_protocol = SelectedProtocol}, _Version) -> + PaddingLength = 32 - ((byte_size(SelectedProtocol) + 2) rem 32), + {?NEXT_PROTOCOL, <>}; + +encode_handshake(#server_hello{server_version = {Major, Minor}, + random = Random, + session_id = Session_ID, + cipher_suite = CipherSuite, + compression_method = Comp_method, + extensions = #hello_extensions{} = Extensions}, _Version) -> + SID_length = byte_size(Session_ID), + ExtensionsBin = encode_hello_extensions(Extensions), + {?SERVER_HELLO, <>}; +encode_handshake(#certificate{asn1_certificates = ASN1CertList}, _Version) -> + ASN1Certs = certs_from_list(ASN1CertList), + ACLen = erlang:iolist_size(ASN1Certs), + {?CERTIFICATE, <>}; +encode_handshake(#server_key_exchange{exchange_keys = Keys}, _Version) -> + {?SERVER_KEY_EXCHANGE, Keys}; +encode_handshake(#server_key_params{params_bin = Keys, hashsign = HashSign, + signature = Signature}, Version) -> + EncSign = enc_sign(HashSign, Signature, Version), + {?SERVER_KEY_EXCHANGE, <>}; +encode_handshake(#certificate_request{certificate_types = CertTypes, + hashsign_algorithms = #hash_sign_algos{hash_sign_algos = HashSignAlgos}, + certificate_authorities = CertAuths}, + {Major, Minor}) + when Major == 3, Minor >= 3 -> + HashSigns= << <<(ssl_cipher:hash_algorithm(Hash)):8, (ssl_cipher:sign_algorithm(Sign)):8>> || + {Hash, Sign} <- HashSignAlgos >>, + CertTypesLen = byte_size(CertTypes), + HashSignsLen = byte_size(HashSigns), + CertAuthsLen = byte_size(CertAuths), + {?CERTIFICATE_REQUEST, + <> + }; +encode_handshake(#certificate_request{certificate_types = CertTypes, + certificate_authorities = CertAuths}, + _Version) -> + CertTypesLen = byte_size(CertTypes), + CertAuthsLen = byte_size(CertAuths), + {?CERTIFICATE_REQUEST, + <> + }; +encode_handshake(#server_hello_done{}, _Version) -> + {?SERVER_HELLO_DONE, <<>>}; +encode_handshake(#client_key_exchange{exchange_keys = ExchangeKeys}, Version) -> + {?CLIENT_KEY_EXCHANGE, encode_client_key(ExchangeKeys, Version)}; +encode_handshake(#certificate_verify{signature = BinSig, hashsign_algorithm = HashSign}, Version) -> + EncSig = enc_sign(HashSign, BinSig, Version), + {?CERTIFICATE_VERIFY, EncSig}; +encode_handshake(#finished{verify_data = VerifyData}, _Version) -> + {?FINISHED, VerifyData}. + +encode_hello_extensions(#hello_extensions{} = Extensions) -> + encode_hello_extensions(hello_extensions_list(Extensions), <<>>). +encode_hello_extensions([], <<>>) -> + <<>>; +encode_hello_extensions([], Acc) -> + Size = byte_size(Acc), + <>; + +encode_hello_extensions([#next_protocol_negotiation{extension_data = ExtensionData} | Rest], Acc) -> + Len = byte_size(ExtensionData), + encode_hello_extensions(Rest, <>); +encode_hello_extensions([#renegotiation_info{renegotiated_connection = undefined} | Rest], Acc) -> + encode_hello_extensions(Rest, Acc); +encode_hello_extensions([#renegotiation_info{renegotiated_connection = ?byte(0) = Info} | Rest], Acc) -> + Len = byte_size(Info), + encode_hello_extensions(Rest, <>); + +encode_hello_extensions([#renegotiation_info{renegotiated_connection = Info} | Rest], Acc) -> + InfoLen = byte_size(Info), + Len = InfoLen +1, + encode_hello_extensions(Rest, <>); +encode_hello_extensions([#elliptic_curves{elliptic_curve_list = EllipticCurves} | Rest], Acc) -> + EllipticCurveList = << <<(tls_v1:oid_to_enum(X)):16>> || X <- EllipticCurves>>, + ListLen = byte_size(EllipticCurveList), + Len = ListLen + 2, + encode_hello_extensions(Rest, <>); +encode_hello_extensions([#ec_point_formats{ec_point_format_list = ECPointFormats} | Rest], Acc) -> + ECPointFormatList = list_to_binary(ECPointFormats), + ListLen = byte_size(ECPointFormatList), + Len = ListLen + 1, + encode_hello_extensions(Rest, <>); +encode_hello_extensions([#srp{username = UserName} | Rest], Acc) -> + SRPLen = byte_size(UserName), + Len = SRPLen + 2, + encode_hello_extensions(Rest, <>); +encode_hello_extensions([#hash_sign_algos{hash_sign_algos = HashSignAlgos} | Rest], Acc) -> + SignAlgoList = << <<(ssl_cipher:hash_algorithm(Hash)):8, (ssl_cipher:sign_algorithm(Sign)):8>> || + {Hash, Sign} <- HashSignAlgos >>, + ListLen = byte_size(SignAlgoList), + Len = ListLen + 2, + encode_hello_extensions(Rest, <>). + +enc_server_key_exchange(Version, Params, {HashAlgo, SignAlgo}, + ClientRandom, ServerRandom, PrivateKey) -> + EncParams = encode_server_key(Params), + case HashAlgo of + null -> + #server_key_params{params = Params, + params_bin = EncParams, + hashsign = {null, anon}, + signature = <<>>}; + _ -> + Hash = + server_key_exchange_hash(HashAlgo, <>), + Signature = digitally_signed(Version, Hash, HashAlgo, PrivateKey), + #server_key_params{params = Params, + params_bin = EncParams, + hashsign = {HashAlgo, SignAlgo}, + signature = Signature} + end. + +%%-------------------------------------------------------------------- +-spec decode_client_key(binary(), key_algo(), tls_version()) -> + #encrypted_premaster_secret{} + | #client_diffie_hellman_public{} + | #client_ec_diffie_hellman_public{} + | #client_psk_identity{} + | #client_dhe_psk_identity{} + | #client_rsa_psk_identity{} + | #client_srp_public{}. +%% +%% Description: Decode client_key data and return appropriate type +%%-------------------------------------------------------------------- +decode_client_key(ClientKey, Type, Version) -> + dec_client_key(ClientKey, key_exchange_alg(Type), Version). + +%%-------------------------------------------------------------------- +-spec decode_server_key(binary(), key_algo(), tls_version()) -> + #server_key_params{}. +%% +%% Description: Decode server_key data and return appropriate type +%%-------------------------------------------------------------------- +decode_server_key(ServerKey, Type, Version) -> + dec_server_key(ServerKey, key_exchange_alg(Type), Version). + +encode_client_protocol_negotiation(undefined, _) -> + undefined; +encode_client_protocol_negotiation(_, false) -> + #next_protocol_negotiation{extension_data = <<>>}; +encode_client_protocol_negotiation(_, _) -> + undefined. + +encode_protocols_advertised_on_server(undefined) -> + undefined; + +encode_protocols_advertised_on_server(Protocols) -> + #next_protocol_negotiation{extension_data = lists:foldl(fun encode_protocol/2, <<>>, Protocols)}. + +decode_handshake(_, ?HELLO_REQUEST, <<>>) -> + #hello_request{}; +decode_handshake(_, ?NEXT_PROTOCOL, <>) -> + #next_protocol{selected_protocol = SelectedProtocol}; + +decode_handshake(_Version, ?SERVER_HELLO, <>) -> + #server_hello{ + server_version = {Major,Minor}, + random = Random, + session_id = Session_ID, + cipher_suite = Cipher_suite, + compression_method = Comp_method, + extensions = #hello_extensions{}}; + +decode_handshake(_Version, ?SERVER_HELLO, <>) -> + + HelloExtensions = decode_hello_extensions(Extensions), + + #server_hello{ + server_version = {Major,Minor}, + random = Random, + session_id = Session_ID, + cipher_suite = Cipher_suite, + compression_method = Comp_method, + extensions = HelloExtensions}; + +decode_handshake(_Version, ?CERTIFICATE, <>) -> + #certificate{asn1_certificates = certs_to_list(ASN1Certs)}; +decode_handshake(_Version, ?SERVER_KEY_EXCHANGE, Keys) -> + #server_key_exchange{exchange_keys = Keys}; +decode_handshake({Major, Minor}, ?CERTIFICATE_REQUEST, + <>) + when Major == 3, Minor >= 3 -> + HashSignAlgos = [{ssl_cipher:hash_algorithm(Hash), ssl_cipher:sign_algorithm(Sign)} || + <> <= HashSigns], + #certificate_request{certificate_types = CertTypes, + hashsign_algorithms = #hash_sign_algos{hash_sign_algos = HashSignAlgos}, + certificate_authorities = CertAuths}; +decode_handshake(_Version, ?CERTIFICATE_REQUEST, + <>) -> + #certificate_request{certificate_types = CertTypes, + certificate_authorities = CertAuths}; +decode_handshake(_Version, ?SERVER_HELLO_DONE, <<>>) -> + #server_hello_done{}; +decode_handshake({Major, Minor}, ?CERTIFICATE_VERIFY,<>) + when Major == 3, Minor >= 3 -> + #certificate_verify{hashsign_algorithm = dec_hashsign(HashSign), signature = Signature}; +decode_handshake(_Version, ?CERTIFICATE_VERIFY,<>)-> + #certificate_verify{signature = Signature}; +decode_handshake(_Version, ?CLIENT_KEY_EXCHANGE, PKEPMS) -> + #client_key_exchange{exchange_keys = PKEPMS}; +decode_handshake(_Version, ?FINISHED, VerifyData) -> + #finished{verify_data = VerifyData}; +decode_handshake(_, _, _) -> + throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE)). + +%%-------------------------------------------------------------------- +-spec decode_hello_extensions(binary()) -> #hello_extensions{}. +%% +%% Description: Decodes TLS hello extensions +%%-------------------------------------------------------------------- +decode_hello_extensions({client, <<>>}) -> + #hello_extensions{}; +decode_hello_extensions({client, <>}) -> + decode_hello_extensions(Extensions); +decode_hello_extensions(Extensions) -> + dec_hello_extensions(Extensions, #hello_extensions{}). + +dec_server_key(<> = KeyStruct, + ?KEY_EXCHANGE_DIFFIE_HELLMAN, Version) -> + Params = #server_dh_params{dh_p = P, dh_g = G, dh_y = Y}, + {BinMsg, HashSign, Signature} = dec_server_key_params(PLen + GLen + YLen + 6, KeyStruct, Version), + #server_key_params{params = Params, + params_bin = BinMsg, + hashsign = HashSign, + signature = Signature}; +%% ECParameters with named_curve +%% TODO: explicit curve +dec_server_key(<> = KeyStruct, + ?KEY_EXCHANGE_EC_DIFFIE_HELLMAN, Version) -> + Params = #server_ecdh_params{curve = {namedCurve, ssl_tls1:enum_to_oid(CurveID)}, + public = ECPoint}, + {BinMsg, HashSign, Signature} = dec_server_key_params(PointLen + 4, KeyStruct, Version), + #server_key_params{params = Params, + params_bin = BinMsg, + hashsign = HashSign, + signature = Signature}; +dec_server_key(<> = KeyStruct, + KeyExchange, Version) + when KeyExchange == ?KEY_EXCHANGE_PSK; KeyExchange == ?KEY_EXCHANGE_RSA_PSK -> + Params = #server_psk_params{ + hint = PskIdentityHint}, + {BinMsg, HashSign, Signature} = dec_server_key_params(Len + 2, KeyStruct, Version), + #server_key_params{params = Params, + params_bin = BinMsg, + hashsign = HashSign, + signature = Signature}; +dec_server_key(<> = KeyStruct, + ?KEY_EXCHANGE_DHE_PSK, Version) -> + DHParams = #server_dh_params{dh_p = P, dh_g = G, dh_y = Y}, + Params = #server_dhe_psk_params{ + hint = IdentityHint, + dh_params = DHParams}, + {BinMsg, HashSign, Signature} = dec_server_key_params(Len + PLen + GLen + YLen + 8, KeyStruct, Version), + #server_key_params{params = Params, + params_bin = BinMsg, + hashsign = HashSign, + signature = Signature}; +dec_server_key(<> = KeyStruct, + ?KEY_EXCHANGE_SRP, Version) -> + Params = #server_srp_params{srp_n = N, srp_g = G, srp_s = S, srp_b = B}, + {BinMsg, HashSign, Signature} = dec_server_key_params(NLen + GLen + SLen + BLen + 7, KeyStruct, Version), + #server_key_params{params = Params, + params_bin = BinMsg, + hashsign = HashSign, + signature = Signature}; +dec_server_key(_, _, _) -> + throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE)). + +%%-------------------------------------------------------------------- +-spec decode_suites('2_bytes'|'3_bytes', binary()) -> list(). +%% +%% Description: +%%-------------------------------------------------------------------- +decode_suites('2_bytes', Dec) -> + from_2bytes(Dec); +decode_suites('3_bytes', Dec) -> + from_3bytes(Dec). + +%%-------------Cipeher suite handling -------------------------------- + +available_suites(UserSuites, Version) -> + case UserSuites of + [] -> + ssl_cipher:suites(Version); + _ -> + UserSuites + end. + +available_suites(ServerCert, UserSuites, Version) -> + ssl_cipher:filter(ServerCert, available_suites(UserSuites, Version)). + +cipher_suites(Suites, false) -> + [?TLS_EMPTY_RENEGOTIATION_INFO_SCSV | Suites]; +cipher_suites(Suites, true) -> + Suites. + +select_session(SuggestedSessionId, CipherSuites, Compressions, Port, Session, Version, + #ssl_options{ciphers = UserSuites} = SslOpts, Cache, CacheCb, Cert) -> + {SessionId, Resumed} = ssl_session:server_id(Port, SuggestedSessionId, + SslOpts, Cert, + Cache, CacheCb), + Suites = ssl_handshake:available_suites(Cert, UserSuites, Version), + case Resumed of + undefined -> + CipherSuite = select_cipher_suite(CipherSuites, Suites), + Compression = select_compression(Compressions), + {new, Session#session{session_id = SessionId, + cipher_suite = CipherSuite, + compression_method = Compression}}; + _ -> + {resumed, Resumed} + end. + +%%-------------certificate handling -------------------------------- + +certificate_types({KeyExchange, _, _, _}) + when KeyExchange == rsa; + KeyExchange == dhe_dss; + KeyExchange == dhe_rsa; + KeyExchange == ecdhe_rsa -> + <>; + +certificate_types({KeyExchange, _, _, _}) + when KeyExchange == dh_ecdsa; + KeyExchange == dhe_ecdsa -> + <>; + +certificate_types(_) -> + <>. + +certificate_authorities(CertDbHandle, CertDbRef) -> + Authorities = certificate_authorities_from_db(CertDbHandle, CertDbRef), + Enc = fun(#'OTPCertificate'{tbsCertificate=TBSCert}) -> + OTPSubj = TBSCert#'OTPTBSCertificate'.subject, + DNEncodedBin = public_key:pkix_encode('Name', OTPSubj, otp), + DNEncodedLen = byte_size(DNEncodedBin), + <> + end, + list_to_binary([Enc(Cert) || {_, Cert} <- Authorities]). + +certificate_authorities_from_db(CertDbHandle, CertDbRef) -> + ConnectionCerts = fun({{Ref, _, _}, Cert}, Acc) when Ref == CertDbRef -> + [Cert | Acc]; + (_, Acc) -> + Acc + end, + ssl_pkix_db:foldl(ConnectionCerts, [], CertDbHandle). + +%%-------------Extension handling -------------------------------- + +handle_client_hello_extensions(RecordCB, Random, + #hello_extensions{renegotiation_info = Info, + srp = SRP, + next_protocol_negotiation = NextProtocolNegotiation, + ec_point_formats = EcPointFormats0, + elliptic_curves = EllipticCurves0}, Version, + #ssl_options{secure_renegotiate = SecureRenegotation} = Opts, + #session{cipher_suite = CipherSuite, compression_method = Compression} = Session0, + ConnectionStates0, Renegotiation) -> + Session = handle_srp_extension(SRP, Session0), + ConnectionStates = handle_renegotiation_extension(server, RecordCB, Version, Info, + Random, CipherSuite, Compression, + ConnectionStates0, Renegotiation, SecureRenegotation), + ProtocolsToAdvertise = handle_next_protocol_extension(NextProtocolNegotiation, Renegotiation, Opts), + {EcPointFormats, EllipticCurves} = handle_ecc_extensions(Version, EcPointFormats0, EllipticCurves0), + ServerHelloExtensions = #hello_extensions{ + renegotiation_info = renegotiation_info(RecordCB, server, + ConnectionStates, Renegotiation), + ec_point_formats = EcPointFormats, + elliptic_curves = EllipticCurves, + next_protocol_negotiation = + encode_protocols_advertised_on_server(ProtocolsToAdvertise) + }, + {Session, ConnectionStates, ServerHelloExtensions}. + +handle_server_hello_extensions(RecordCB, Random, CipherSuite, Compression, + #hello_extensions{renegotiation_info = Info, + next_protocol_negotiation = NextProtocolNegotiation}, Version, + #ssl_options{secure_renegotiate = SecureRenegotation, + next_protocol_selector = NextProtoSelector}, + ConnectionStates0, Renegotiation) -> + ConnectionStates = handle_renegotiation_extension(client, RecordCB, Version, Info, Random, CipherSuite, + Compression, ConnectionStates0, + Renegotiation, SecureRenegotation), + case handle_next_protocol(NextProtocolNegotiation, NextProtoSelector, Renegotiation) of + #alert{} = Alert -> + Alert; + Protocol -> + {ConnectionStates, Protocol} + end. + +select_version(RecordCB, ClientVersion, Versions) -> + ServerVersion = RecordCB:highest_protocol_version(Versions), + RecordCB:lowest_protocol_version(ClientVersion, ServerVersion). + +renegotiation_info(_, client, _, false) -> + #renegotiation_info{renegotiated_connection = undefined}; +renegotiation_info(RecordCB, server, ConnectionStates, false) -> + CS = RecordCB: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), + case CS#connection_state.secure_renegotiation of + true -> + Data = CS#connection_state.client_verify_data, + #renegotiation_info{renegotiated_connection = Data}; + false -> + #renegotiation_info{renegotiated_connection = undefined} + end; + +renegotiation_info(RecordCB, server, ConnectionStates, true) -> + CS = RecordCB:current_connection_state(ConnectionStates, read), + case CS#connection_state.secure_renegotiation of + true -> + CData = CS#connection_state.client_verify_data, + SData =CS#connection_state.server_verify_data, + #renegotiation_info{renegotiated_connection = <>}; + false -> + #renegotiation_info{renegotiated_connection = undefined} + end. + +handle_renegotiation_info(RecordCB, _, #renegotiation_info{renegotiated_connection = ?byte(0)}, + ConnectionStates, false, _, _) -> + {ok, RecordCB:set_renegotiation_flag(true, ConnectionStates)}; + +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)}; + false -> + {ok, RecordCB:set_renegotiation_flag(false, ConnectionStates)} + end; + +handle_renegotiation_info(RecordCB, _, undefined, ConnectionStates, false, _, _) -> + {ok, RecordCB:set_renegotiation_flag(false, ConnectionStates)}; + +handle_renegotiation_info(RecordCB, client, #renegotiation_info{renegotiated_connection = ClientServerVerify}, + ConnectionStates, true, _, _) -> + CS = RecordCB:current_connection_state(ConnectionStates, read), + CData = CS#connection_state.client_verify_data, + SData = CS#connection_state.server_verify_data, + case <> == ClientServerVerify of + true -> + {ok, ConnectionStates}; + false -> + ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE) + end; +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), + Data = CS#connection_state.client_verify_data, + case Data == ClientVerify of + true -> + {ok, ConnectionStates}; + false -> + ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE) + end + end; + +handle_renegotiation_info(RecordCB, client, undefined, ConnectionStates, true, SecureRenegotation, _) -> + handle_renegotiation_info(RecordCB, ConnectionStates, SecureRenegotation); + +handle_renegotiation_info(RecordCB, server, undefined, ConnectionStates, true, SecureRenegotation, CipherSuites) -> + case is_member(?TLS_EMPTY_RENEGOTIATION_INFO_SCSV, CipherSuites) of + true -> + ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE); + false -> + handle_renegotiation_info(RecordCB, ConnectionStates, SecureRenegotation) + end. + +handle_renegotiation_info(RecordCB, ConnectionStates, SecureRenegotation) -> + CS = RecordCB:current_connection_state(ConnectionStates, read), + case {SecureRenegotation, CS#connection_state.secure_renegotiation} of + {_, true} -> + ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE); + {true, false} -> + ?ALERT_REC(?FATAL, ?NO_RENEGOTIATION); + {false, false} -> + {ok, ConnectionStates} + end. + +hello_extensions_list(#hello_extensions{renegotiation_info = RenegotiationInfo, + srp = SRP, + hash_signs = HashSigns, + ec_point_formats = EcPointFormats, + elliptic_curves = EllipticCurves, + next_protocol_negotiation = NextProtocolNegotiation}) -> + [Ext || Ext <- [RenegotiationInfo, SRP, HashSigns, + EcPointFormats,EllipticCurves, NextProtocolNegotiation], Ext =/= undefined]. + +srp_user(#ssl_options{srp_identity = {UserName, _}}) -> + #srp{username = UserName}; +srp_user(_) -> + undefined. + +ecc_extensions(Module, Version) -> + CryptoSupport = proplists:get_value(public_keys, crypto:supports()), + case proplists:get_bool(ecdh, CryptoSupport) of + true -> + EcPointFormats = #ec_point_formats{ec_point_format_list = [?ECPOINT_UNCOMPRESSED]}, + EllipticCurves = #elliptic_curves{elliptic_curve_list = Module:ecc_curves(Version)}, + {EcPointFormats, EllipticCurves}; + _ -> + {undefined, undefined} + end. + +handle_ecc_extensions(Version, EcPointFormats0, EllipticCurves0) -> + CryptoSupport = proplists:get_value(public_keys, crypto:supports()), + case proplists:get_bool(ecdh, CryptoSupport) of + true -> + EcPointFormats1 = handle_ecc_point_fmt_extension(EcPointFormats0), + EllipticCurves1 = handle_ecc_curves_extension(Version, EllipticCurves0), + {EcPointFormats1, EllipticCurves1}; + _ -> + {undefined, undefined} + end. + +handle_ecc_point_fmt_extension(undefined) -> + undefined; +handle_ecc_point_fmt_extension(_) -> + #ec_point_formats{ec_point_format_list = [?ECPOINT_UNCOMPRESSED]}. + +handle_ecc_curves_extension(_Version, undefined) -> + undefined; +handle_ecc_curves_extension(Version, _) -> + #elliptic_curves{elliptic_curve_list = tls_v1:ecc_curves(Version)}. + +-define(TLSEXT_SIGALG_RSA(MD), {MD, rsa}). +-define(TLSEXT_SIGALG_DSA(MD), {MD, dsa}). +-define(TLSEXT_SIGALG_ECDSA(MD), {MD, ecdsa}). + +-define(TLSEXT_SIGALG(MD), ?TLSEXT_SIGALG_ECDSA(MD), ?TLSEXT_SIGALG_RSA(MD)). + +default_hash_signs() -> + HashSigns = [?TLSEXT_SIGALG(sha512), + ?TLSEXT_SIGALG(sha384), + ?TLSEXT_SIGALG(sha256), + ?TLSEXT_SIGALG(sha224), + ?TLSEXT_SIGALG(sha), + ?TLSEXT_SIGALG_DSA(sha), + ?TLSEXT_SIGALG_RSA(md5)], + CryptoSupport = proplists:get_value(public_keys, crypto:supports()), + HasECC = proplists:get_bool(ecdsa, CryptoSupport), + #hash_sign_algos{hash_sign_algos = + lists:filter(fun({_, ecdsa}) -> HasECC; + (_) -> true end, HashSigns)}. + +advertises_ec_ciphers([]) -> + false; +advertises_ec_ciphers([{ecdh_ecdsa, _,_,_} | _]) -> + true; +advertises_ec_ciphers([{ecdhe_ecdsa, _,_,_} | _]) -> + true; +advertises_ec_ciphers([{ecdh_rsa, _,_,_} | _]) -> + true; +advertises_ec_ciphers([{ecdhe_rsa, _,_,_} | _]) -> + true; +advertises_ec_ciphers([{ecdh_anon, _,_,_} | _]) -> + true; +advertises_ec_ciphers([_| Rest]) -> + advertises_ec_ciphers(Rest). + +%%-------------------------------------------------------------------- +%%% Internal functions +%%-------------------------------------------------------------------- +apply_user_fun(Fun, OtpCert, ExtensionOrError, UserState0, SslState) -> + case Fun(OtpCert, ExtensionOrError, UserState0) of + {valid, UserState} -> + {valid, {SslState, UserState}}; + {fail, _} = Fail -> + Fail; + {unknown, UserState} -> + {unknown, {SslState, UserState}} + end. +path_validation_alert({bad_cert, cert_expired}) -> + ?ALERT_REC(?FATAL, ?CERTIFICATE_EXPIRED); +path_validation_alert({bad_cert, invalid_issuer}) -> + ?ALERT_REC(?FATAL, ?BAD_CERTIFICATE); +path_validation_alert({bad_cert, invalid_signature}) -> + ?ALERT_REC(?FATAL, ?BAD_CERTIFICATE); +path_validation_alert({bad_cert, name_not_permitted}) -> + ?ALERT_REC(?FATAL, ?BAD_CERTIFICATE); +path_validation_alert({bad_cert, unknown_critical_extension}) -> + ?ALERT_REC(?FATAL, ?UNSUPPORTED_CERTIFICATE); +path_validation_alert({bad_cert, cert_revoked}) -> + ?ALERT_REC(?FATAL, ?CERTIFICATE_REVOKED); +path_validation_alert({bad_cert, selfsigned_peer}) -> + ?ALERT_REC(?FATAL, ?BAD_CERTIFICATE); +path_validation_alert({bad_cert, unknown_ca}) -> + ?ALERT_REC(?FATAL, ?UNKNOWN_CA); +path_validation_alert(_) -> + ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE). + +encrypted_premaster_secret(Secret, RSAPublicKey) -> + try + PreMasterSecret = public_key:encrypt_public(Secret, RSAPublicKey, + [{rsa_pad, + rsa_pkcs1_padding}]), + #encrypted_premaster_secret{premaster_secret = PreMasterSecret} + catch + _:_-> + throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE)) + end. + +digitally_signed({3, Minor}, Hash, HashAlgo, Key) when Minor >= 3 -> + public_key:sign({digest, Hash}, HashAlgo, Key); +digitally_signed(_Version, Hash, HashAlgo, #'DSAPrivateKey'{} = Key) -> + public_key:sign({digest, Hash}, HashAlgo, Key); +digitally_signed(_Version, Hash, _HashAlgo, #'RSAPrivateKey'{} = Key) -> + public_key:encrypt_private(Hash, Key, + [{rsa_pad, rsa_pkcs1_padding}]); +digitally_signed(_Version, Hash, HashAlgo, Key) -> + public_key:sign({digest, Hash}, HashAlgo, Key). + +calc_certificate_verify({3, 0}, HashAlgo, MasterSecret, Handshake) -> + ssl_v3:certificate_verify(HashAlgo, MasterSecret, lists:reverse(Handshake)); +calc_certificate_verify({3, N}, HashAlgo, _MasterSecret, Handshake) -> + tls_v1:certificate_verify(HashAlgo, N, lists:reverse(Handshake)). + +calc_finished({3, 0}, Role, _PrfAlgo, MasterSecret, Handshake) -> + ssl_v3:finished(Role, MasterSecret, lists:reverse(Handshake)); +calc_finished({3, N}, Role, PrfAlgo, MasterSecret, Handshake) -> + tls_v1:finished(Role, N, PrfAlgo, MasterSecret, lists:reverse(Handshake)). + +master_secret(RecordCB, Version, MasterSecret, + #security_parameters{ + client_random = ClientRandom, + server_random = ServerRandom, + hash_size = HashSize, + prf_algorithm = PrfAlgo, + key_material_length = KML, + expanded_key_material_length = EKML, + iv_size = IVS}, + ConnectionStates, Role) -> + {ClientWriteMacSecret, ServerWriteMacSecret, ClientWriteKey, + ServerWriteKey, ClientIV, ServerIV} = + setup_keys(Version, PrfAlgo, MasterSecret, ServerRandom, + ClientRandom, HashSize, KML, EKML, IVS), + + ConnStates1 = RecordCB:set_master_secret(MasterSecret, ConnectionStates), + ConnStates2 = + RecordCB: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, + ServerCipherState, Role)}. + +setup_keys({3,0}, _PrfAlgo, MasterSecret, + ServerRandom, ClientRandom, HashSize, KML, EKML, IVS) -> + ssl_v3:setup_keys(MasterSecret, ServerRandom, + ClientRandom, HashSize, KML, EKML, IVS); + +setup_keys({3,N}, PrfAlgo, MasterSecret, + ServerRandom, ClientRandom, HashSize, KML, _EKML, IVS) -> + tls_v1:setup_keys(N, PrfAlgo, MasterSecret, ServerRandom, ClientRandom, HashSize, + KML, IVS). + +calc_master_secret({3,0}, _PrfAlgo, PremasterSecret, ClientRandom, ServerRandom) -> + ssl_v3:master_secret(PremasterSecret, ClientRandom, ServerRandom); + +calc_master_secret({3,_}, PrfAlgo, PremasterSecret, ClientRandom, ServerRandom) -> + tls_v1:master_secret(PrfAlgo, PremasterSecret, ClientRandom, ServerRandom). + +handle_renegotiation_extension(Role, RecordCB, Version, Info, Random, CipherSuite, Compression, + ConnectionStates0, Renegotiation, SecureRenegotation) -> + case handle_renegotiation_info(RecordCB, Role, Info, ConnectionStates0, + Renegotiation, SecureRenegotation, + [CipherSuite]) of + {ok, ConnectionStates} -> + hello_pending_connection_states(RecordCB, Role, + Version, + CipherSuite, + Random, + Compression, + ConnectionStates); + #alert{} = Alert -> + throw(Alert) + end. + +%% Update pending connection states with parameters exchanged via +%% 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, + ConnectionStates) -> + ReadState = + RecordCB:pending_connection_state(ConnectionStates, read), + WriteState = + RecordCB:pending_connection_state(ConnectionStates, write), + + NewReadSecParams = + hello_security_parameters(Role, Version, ReadState, CipherSuite, + Random, Compression), + + NewWriteSecParams = + hello_security_parameters(Role, Version, WriteState, CipherSuite, + Random, Compression), + + RecordCB:update_security_params(NewReadSecParams, + NewWriteSecParams, + ConnectionStates). + +hello_security_parameters(client, Version, ConnectionState, CipherSuite, Random, + Compression) -> + SecParams = ConnectionState#connection_state.security_parameters, + NewSecParams = ssl_cipher:security_parameters(Version, CipherSuite, SecParams), + NewSecParams#security_parameters{ + server_random = Random, + compression_algorithm = Compression + }; + +hello_security_parameters(server, Version, ConnectionState, CipherSuite, Random, + Compression) -> + SecParams = ConnectionState#connection_state.security_parameters, + NewSecParams = ssl_cipher:security_parameters(Version, CipherSuite, SecParams), + NewSecParams#security_parameters{ + client_random = Random, + compression_algorithm = Compression + }. + +%%-------------Encode/Decode -------------------------------- + +encode_server_key(#server_dh_params{dh_p = P, dh_g = G, dh_y = Y}) -> + PLen = byte_size(P), + GLen = byte_size(G), + YLen = byte_size(Y), + <>; +encode_server_key(#server_ecdh_params{curve = {namedCurve, ECCurve}, public = ECPubKey}) -> + %%TODO: support arbitrary keys + KLen = size(ECPubKey), + <>; +encode_server_key(#server_psk_params{hint = PskIdentityHint}) -> + Len = byte_size(PskIdentityHint), + <>; +encode_server_key(Params = #server_dhe_psk_params{hint = undefined}) -> + encode_server_key(Params#server_dhe_psk_params{hint = <<>>}); +encode_server_key(#server_dhe_psk_params{ + hint = PskIdentityHint, + dh_params = #server_dh_params{dh_p = P, dh_g = G, dh_y = Y}}) -> + Len = byte_size(PskIdentityHint), + PLen = byte_size(P), + GLen = byte_size(G), + YLen = byte_size(Y), + <>; +encode_server_key(#server_srp_params{srp_n = N, srp_g = G, srp_s = S, srp_b = B}) -> + NLen = byte_size(N), + GLen = byte_size(G), + SLen = byte_size(S), + BLen = byte_size(B), + <>. + +encode_client_key(#encrypted_premaster_secret{premaster_secret = PKEPMS},{3, 0}) -> + PKEPMS; +encode_client_key(#encrypted_premaster_secret{premaster_secret = PKEPMS}, _) -> + PKEPMSLen = byte_size(PKEPMS), + <>; +encode_client_key(#client_diffie_hellman_public{dh_public = DHPublic}, _) -> + Len = byte_size(DHPublic), + <>; +encode_client_key(#client_ec_diffie_hellman_public{dh_public = DHPublic}, _) -> + Len = byte_size(DHPublic), + <>; +encode_client_key(#client_psk_identity{identity = undefined}, _) -> + Id = <<"psk_identity">>, + Len = byte_size(Id), + <>; +encode_client_key(#client_psk_identity{identity = Id}, _) -> + Len = byte_size(Id), + <>; +encode_client_key(Identity = #client_dhe_psk_identity{identity = undefined}, Version) -> + encode_client_key(Identity#client_dhe_psk_identity{identity = <<"psk_identity">>}, Version); +encode_client_key(#client_dhe_psk_identity{identity = Id, dh_public = DHPublic}, _) -> + Len = byte_size(Id), + DHLen = byte_size(DHPublic), + <>; +encode_client_key(Identity = #client_rsa_psk_identity{identity = undefined}, Version) -> + encode_client_key(Identity#client_rsa_psk_identity{identity = <<"psk_identity">>}, Version); +encode_client_key(#client_rsa_psk_identity{identity = Id, exchange_keys = ExchangeKeys}, Version) -> + EncPMS = encode_client_key(ExchangeKeys, Version), + Len = byte_size(Id), + <>; +encode_client_key(#client_srp_public{srp_a = A}, _) -> + Len = byte_size(A), + <>. + +enc_sign({_, anon}, _Sign, _Version) -> + <<>>; +enc_sign({HashAlg, SignAlg}, Signature, _Version = {Major, Minor}) + when Major == 3, Minor >= 3-> + SignLen = byte_size(Signature), + HashSign = enc_hashsign(HashAlg, SignAlg), + <>; +enc_sign(_HashSign, Sign, _Version) -> + SignLen = byte_size(Sign), + <>. + +enc_hashsign(HashAlgo, SignAlgo) -> + Hash = ssl_cipher:hash_algorithm(HashAlgo), + Sign = ssl_cipher:sign_algorithm(SignAlgo), + <>. + +encode_protocol(Protocol, Acc) -> + Len = byte_size(Protocol), + <>. + +dec_client_key(PKEPMS, ?KEY_EXCHANGE_RSA, {3, 0}) -> + #encrypted_premaster_secret{premaster_secret = PKEPMS}; +dec_client_key(<>, ?KEY_EXCHANGE_RSA, _) -> + #encrypted_premaster_secret{premaster_secret = PKEPMS}; +dec_client_key(<<>>, ?KEY_EXCHANGE_DIFFIE_HELLMAN, _) -> + throw(?ALERT_REC(?FATAL, ?UNSUPPORTED_CERTIFICATE)); +dec_client_key(<>, + ?KEY_EXCHANGE_DIFFIE_HELLMAN, _) -> + #client_diffie_hellman_public{dh_public = DH_Y}; +dec_client_key(<<>>, ?KEY_EXCHANGE_EC_DIFFIE_HELLMAN, _) -> + throw(?ALERT_REC(?FATAL, ?UNSUPPORTED_CERTIFICATE)); +dec_client_key(<>, + ?KEY_EXCHANGE_EC_DIFFIE_HELLMAN, _) -> + #client_ec_diffie_hellman_public{dh_public = DH_Y}; +dec_client_key(<>, + ?KEY_EXCHANGE_PSK, _) -> + #client_psk_identity{identity = Id}; +dec_client_key(<>, + ?KEY_EXCHANGE_DHE_PSK, _) -> + #client_dhe_psk_identity{identity = Id, dh_public = DH_Y}; +dec_client_key(<>, + ?KEY_EXCHANGE_RSA_PSK, {3, 0}) -> + #client_rsa_psk_identity{identity = Id, + exchange_keys = #encrypted_premaster_secret{premaster_secret = PKEPMS}}; +dec_client_key(<>, + ?KEY_EXCHANGE_RSA_PSK, _) -> + #client_rsa_psk_identity{identity = Id, + exchange_keys = #encrypted_premaster_secret{premaster_secret = PKEPMS}}; +dec_client_key(<>, + ?KEY_EXCHANGE_SRP, _) -> + #client_srp_public{srp_a = A}. + +dec_server_key_params(Len, Keys, Version) -> + <> = Keys, + dec_server_key_signature(Params, Signature, Version). + +dec_server_key_signature(Params, <>, {Major, Minor}) + when Major == 3, Minor >= 3 -> + HashSign = {ssl_cipher:hash_algorithm(HashAlgo), ssl_cipher:sign_algorithm(SignAlgo)}, + {Params, HashSign, <<>>}; +dec_server_key_signature(Params, <>, {Major, Minor}) + when Major == 3, Minor >= 3 -> + HashSign = {ssl_cipher:hash_algorithm(HashAlgo), ssl_cipher:sign_algorithm(SignAlgo)}, + {Params, HashSign, Signature}; +dec_server_key_signature(Params, <<>>, _) -> + {Params, {null, anon}, <<>>}; +dec_server_key_signature(Params, <>, _) -> + {Params, {null, anon}, <<>>}; +dec_server_key_signature(Params, <>, _) -> + {Params, undefined, Signature}; +dec_server_key_signature(_, _, _) -> + throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE)). + + +dec_hello_extensions(<<>>, Acc) -> + Acc; +dec_hello_extensions(<>, Acc) -> + dec_hello_extensions(Rest, + Acc#hello_extensions{next_protocol_negotiation = + #next_protocol_negotiation{extension_data = ExtensionData}}); +dec_hello_extensions(<>, Acc) -> + RenegotiateInfo = case Len of + 1 -> % Initial handshake + Info; % should be <<0>> will be matched in handle_renegotiation_info + _ -> + VerifyLen = Len - 1, + <> = Info, + VerifyInfo + end, + dec_hello_extensions(Rest, + Acc#hello_extensions{renegotiation_info = + #renegotiation_info{renegotiated_connection = RenegotiateInfo}}); + +dec_hello_extensions(<>, Acc) + when Len == SRPLen + 2 -> + dec_hello_extensions(Rest, Acc#hello_extensions{srp = #srp{username = SRP}}); + +dec_hello_extensions(<>, Acc) -> + SignAlgoListLen = Len - 2, + <> = ExtData, + HashSignAlgos = [{ssl_cipher:hash_algorithm(Hash), ssl_cipher:sign_algorithm(Sign)} || + <> <= SignAlgoList], + dec_hello_extensions(Rest, Acc#hello_extensions{hash_signs = + #hash_sign_algos{hash_sign_algos = HashSignAlgos}}); + +dec_hello_extensions(<>, Acc) -> + EllipticCurveListLen = Len - 2, + <> = ExtData, + EllipticCurves = [tls_v1:enum_to_oid(X) || <> <= EllipticCurveList], + dec_hello_extensions(Rest, Acc#hello_extensions{elliptic_curves = + #elliptic_curves{elliptic_curve_list = + EllipticCurves}}); +dec_hello_extensions(<>, Acc) -> + %%ECPointFormatListLen = Len - 1, + <> = ExtData, + ECPointFormats = binary_to_list(ECPointFormatList), + dec_hello_extensions(Rest, + Acc#hello_extensions{ec_point_formats = + #ec_point_formats{ec_point_format_list = ECPointFormats}}); + +%% Ignore data following the ClientHello (i.e., +%% extensions) if not understood. + +dec_hello_extensions(<>, Acc) -> + dec_hello_extensions(Rest, Acc); +%% This theoretically should not happen if the protocol is followed, but if it does it is ignored. +dec_hello_extensions(_, Acc) -> + Acc. + +dec_hashsign(<>) -> + {ssl_cipher:hash_algorithm(HashAlgo), ssl_cipher:sign_algorithm(SignAlgo)}. + +decode_next_protocols({next_protocol_negotiation, Protocols}) -> + decode_next_protocols(Protocols, []). +decode_next_protocols(<<>>, Acc) -> + lists:reverse(Acc); +decode_next_protocols(<>, Acc) -> + case Len of + 0 -> + {error, invalid_next_protocols}; + _ -> + decode_next_protocols(Rest, [Protocol|Acc]) + end; +decode_next_protocols(_Bytes, _Acc) -> + {error, invalid_next_protocols}. + +%% encode/decode stream of certificate data to/from list of certificate data +certs_to_list(ASN1Certs) -> + certs_to_list(ASN1Certs, []). + +certs_to_list(<>, Acc) -> + certs_to_list(Rest, [Cert | Acc]); +certs_to_list(<<>>, Acc) -> + lists:reverse(Acc, []). + +certs_from_list(ACList) -> + list_to_binary([begin + CertLen = byte_size(Cert), + <> + end || Cert <- ACList]). +from_3bytes(Bin3) -> + from_3bytes(Bin3, []). + +from_3bytes(<<>>, Acc) -> + lists:reverse(Acc); +from_3bytes(<>, Acc) -> + from_3bytes(Rest, [?uint16(N) | Acc]). + +from_2bytes(Bin2) -> + from_2bytes(Bin2, []). + +from_2bytes(<<>>, Acc) -> + lists:reverse(Acc); +from_2bytes(<>, Acc) -> + from_2bytes(Rest, [?uint16(N) | Acc]). +key_exchange_alg(rsa) -> + ?KEY_EXCHANGE_RSA; +key_exchange_alg(Alg) when Alg == dhe_rsa; Alg == dhe_dss; + Alg == dh_dss; Alg == dh_rsa; Alg == dh_anon -> + ?KEY_EXCHANGE_DIFFIE_HELLMAN; +key_exchange_alg(Alg) when Alg == ecdhe_rsa; Alg == ecdh_rsa; + Alg == ecdhe_ecdsa; Alg == ecdh_ecdsa; + Alg == ecdh_anon -> + ?KEY_EXCHANGE_EC_DIFFIE_HELLMAN; +key_exchange_alg(psk) -> + ?KEY_EXCHANGE_PSK; +key_exchange_alg(dhe_psk) -> + ?KEY_EXCHANGE_DHE_PSK; +key_exchange_alg(rsa_psk) -> + ?KEY_EXCHANGE_RSA_PSK; +key_exchange_alg(Alg) + when Alg == srp_rsa; Alg == srp_dss; Alg == srp_anon -> + ?KEY_EXCHANGE_SRP; +key_exchange_alg(_) -> + ?NULL. + +%%-------------Extension handling -------------------------------- + +handle_next_protocol(undefined, + _NextProtocolSelector, _Renegotiating) -> + undefined; + +handle_next_protocol(#next_protocol_negotiation{} = NextProtocols, + NextProtocolSelector, Renegotiating) -> + + case next_protocol_extension_allowed(NextProtocolSelector, Renegotiating) of + true -> + select_next_protocol(decode_next_protocols(NextProtocols), NextProtocolSelector); + false -> + ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE) % unexpected next protocol extension + end. + + +handle_next_protocol_extension(NextProtocolNegotiation, Renegotiation, SslOpts)-> + case handle_next_protocol_on_server(NextProtocolNegotiation, Renegotiation, SslOpts) of + #alert{} = Alert -> + Alert; + ProtocolsToAdvertise -> + ProtocolsToAdvertise + end. + +handle_next_protocol_on_server(undefined, _Renegotiation, _SslOpts) -> + undefined; + +handle_next_protocol_on_server(#next_protocol_negotiation{extension_data = <<>>}, + false, #ssl_options{next_protocols_advertised = Protocols}) -> + Protocols; + +handle_next_protocol_on_server(_Hello, _Renegotiation, _SSLOpts) -> + ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE). % unexpected next protocol extension + +next_protocol_extension_allowed(NextProtocolSelector, Renegotiating) -> + NextProtocolSelector =/= undefined andalso not Renegotiating. + +select_next_protocol({error, _Reason}, _NextProtocolSelector) -> + ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE); +select_next_protocol(Protocols, NextProtocolSelector) -> + case NextProtocolSelector(Protocols) of + ?NO_PROTOCOL -> + ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE); + Protocol when is_binary(Protocol) -> + Protocol + end. + +handle_srp_extension(undefined, Session) -> + Session; +handle_srp_extension(#srp{username = Username}, Session) -> + Session#session{srp_username = Username}. + +%%-------------Misc -------------------------------- + +select_cipher_suite([], _) -> + no_suite; +select_cipher_suite([Suite | ClientSuites], SupportedSuites) -> + case is_member(Suite, SupportedSuites) of + true -> + Suite; + false -> + select_cipher_suite(ClientSuites, SupportedSuites) + end. + +int_to_bin(I) -> + L = (length(integer_to_list(I, 16)) + 1) div 2, + <>. + +is_member(Suite, SupportedSuites) -> + lists:member(Suite, SupportedSuites). + +select_compression(_CompressionMetodes) -> + ?NULL. diff --git a/lib/ssl/src/ssl_handshake.hrl b/lib/ssl/src/ssl_handshake.hrl index eb1a1dbf62..3a3ad8cf35 100644 --- a/lib/ssl/src/ssl_handshake.hrl +++ b/lib/ssl/src/ssl_handshake.hrl @@ -28,11 +28,6 @@ -include_lib("public_key/include/public_key.hrl"). --type oid() :: tuple(). --type public_key_params() :: #'Dss-Parms'{} | {namedCurve, oid()} | #'ECParameters'{} | term(). --type public_key_info() :: {oid(), #'RSAPublicKey'{} | integer() | #'ECPoint'{}, public_key_params()}. --type tls_handshake_history() :: {[binary()], [binary()]}. - -define(NO_PROTOCOL, <<>>). %% Signature algorithms @@ -96,17 +91,22 @@ %% client_hello defined in tls_handshake.hrl and dtls_handshake.hrl +-record(hello_extensions, { + renegotiation_info, + hash_signs, % supported combinations of hashes/signature algos + next_protocol_negotiation = undefined, % [binary()] + srp, + ec_point_formats, + elliptic_curves + }). + -record(server_hello, { server_version, random, session_id, % opaque SessionID<0..32> cipher_suite, % cipher_suites compression_method, % compression_method - renegotiation_info, - hash_signs, % supported combinations of hashes/signature algos - ec_point_formats, % supported ec point formats - elliptic_curves, % supported elliptic curver - next_protocol_negotiation = undefined % [binary()] + extensions }). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -337,6 +337,20 @@ -define(EXPLICIT_CHAR2, 2). -define(NAMED_CURVE, 3). +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Dialyzer types +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +-type oid() :: tuple(). +-type public_key_params() :: #'Dss-Parms'{} | {namedCurve, oid()} | #'ECParameters'{} | term(). +-type public_key_info() :: {oid(), #'RSAPublicKey'{} | integer() | #'ECPoint'{}, public_key_params()}. +-type tls_handshake_history() :: {[binary()], [binary()]}. + +-type ssl_handshake() :: #server_hello{} | #server_hello_done{} | #certificate{} | #certificate_request{} | + #client_key_exchange{} | #finished{} | #certificate_verify{} | + #hello_request{} | #next_protocol{}. + + -endif. % -ifdef(ssl_handshake). diff --git a/lib/ssl/src/ssl_internal.hrl b/lib/ssl/src/ssl_internal.hrl index de8d20d399..3d282dcdfc 100644 --- a/lib/ssl/src/ssl_internal.hrl +++ b/lib/ssl/src/ssl_internal.hrl @@ -50,6 +50,7 @@ -define(UINT16(X), X:16/unsigned-big-integer). -define(UINT24(X), X:24/unsigned-big-integer). -define(UINT32(X), X:32/unsigned-big-integer). +-define(UINT48(X), X:48/unsigned-big-integer). -define(UINT64(X), X:64/unsigned-big-integer). -define(STRING(X), ?UINT32((size(X))), (X)/binary). @@ -57,6 +58,7 @@ -define(uint16(X), << ?UINT16(X) >> ). -define(uint24(X), << ?UINT24(X) >> ). -define(uint32(X), << ?UINT32(X) >> ). +-define(uint48(X), << ?UINT48(X) >> ). -define(uint64(X), << ?UINT64(X) >> ). -define(CDR_MAGIC, "GIOP"). @@ -71,6 +73,8 @@ -define(ALL_SUPPORTED_VERSIONS, ['tlsv1.2', 'tlsv1.1', tlsv1, sslv3]). -define(MIN_SUPPORTED_VERSIONS, ['tlsv1.1', tlsv1, sslv3]). +-define(ALL_DATAGRAM_SUPPORTED_VERSIONS, ['dtlsv1.2', dtlsv1]). +-define(MIN_DATAGRAM_SUPPORTED_VERSIONS, ['dtlsv1.2', dtlsv1]). -record(ssl_options, { versions, % 'tlsv1.2' | 'tlsv1.1' | tlsv1 | sslv3 @@ -124,6 +128,10 @@ active = true }). +-type state_name() :: hello | abbreviated | certify | cipher | connection. +-type gen_fsm_state_return() :: {next_state, state_name(), term()} | + {next_state, state_name(), term(), timeout()} | + {stop, term(), term()}. -endif. % -ifdef(ssl_internal). diff --git a/lib/ssl/src/tls_connection.erl b/lib/ssl/src/tls_connection.erl index 0415ea6ecc..b6b0558965 100644 --- a/lib/ssl/src/tls_connection.erl +++ b/lib/ssl/src/tls_connection.erl @@ -106,10 +106,6 @@ base = ?DEFAULT_DIFFIE_HELLMAN_GENERATOR}). -define(WAIT_TO_ALLOW_RENEGOTIATION, 12000). --type state_name() :: hello | abbreviated | certify | cipher | connection. --type gen_fsm_state_return() :: {next_state, state_name(), #state{}} | - {next_state, state_name(), #state{}, timeout()} | - {stop, term(), #state{}}. %%==================================================================== %% Internal application API @@ -418,15 +414,15 @@ hello(Hello = #client_hello{client_version = ClientVersion, session_cache_cb = CacheCb, ssl_options = SslOpts}) -> - HashSign = tls_handshake:select_hashsign(HashSigns, Cert), + HashSign = ssl_handshake:select_hashsign(HashSigns, Cert), case tls_handshake:hello(Hello, SslOpts, {Port, Session0, Cache, CacheCb, ConnectionStates0, Cert}, Renegotiation) of - {Version, {Type, #session{cipher_suite = CipherSuite} = Session}, ConnectionStates, ProtocolsToAdvertise, - EcPointFormats, EllipticCurves} -> + {Version, {Type, #session{cipher_suite = CipherSuite} = Session}, ConnectionStates, + #hello_extensions{ec_point_formats = EcPointFormats, + elliptic_curves = EllipticCurves} = ServerHelloExt} -> {KeyAlgorithm, _, _, _} = ssl_cipher:suite_definition(CipherSuite), NH = negotiated_hashsign(HashSign, KeyAlgorithm, Version), - do_server_hello(Type, ProtocolsToAdvertise, - EcPointFormats, EllipticCurves, + do_server_hello(Type, ServerHelloExt, State#state{connection_states = ConnectionStates, negotiated_version = Version, session = Session, @@ -456,7 +452,7 @@ abbreviated(#finished{verify_data = Data} = Finished, session = #session{master_secret = MasterSecret}, connection_states = ConnectionStates0} = State) -> - case tls_handshake:verify_connection(Version, Finished, client, + case ssl_handshake:verify_connection(Version, Finished, client, get_current_connection_state_prf(ConnectionStates0, write), MasterSecret, Handshake) of verified -> @@ -472,7 +468,7 @@ abbreviated(#finished{verify_data = Data} = Finished, session = #session{master_secret = MasterSecret}, negotiated_version = Version, connection_states = ConnectionStates0} = State) -> - case tls_handshake:verify_connection(Version, Finished, server, + case ssl_handshake:verify_connection(Version, Finished, server, get_pending_connection_state_prf(ConnectionStates0, write), MasterSecret, Handshake0) of verified -> @@ -531,7 +527,7 @@ certify(#certificate{} = Cert, cert_db = CertDbHandle, cert_db_ref = CertDbRef, ssl_options = Opts} = State) -> - case tls_handshake:certify(Cert, CertDbHandle, CertDbRef, Opts#ssl_options.depth, + case ssl_handshake:certify(Cert, CertDbHandle, CertDbRef, Opts#ssl_options.depth, Opts#ssl_options.verify, Opts#ssl_options.verify_fun, Role) of {PeerCert, PublicKeyInfo} -> @@ -613,7 +609,7 @@ certify(#server_hello_done{}, negotiated_version = Version, premaster_secret = undefined, role = client} = State0) -> - case tls_handshake:master_secret(Version, Session, + case ssl_handshake:master_secret(tls_record, Version, Session, ConnectionStates0, client) of {MasterSecret, ConnectionStates} -> State = State0#state{connection_states = ConnectionStates}, @@ -629,7 +625,7 @@ certify(#server_hello_done{}, negotiated_version = Version, premaster_secret = PremasterSecret, role = client} = State0) -> - case tls_handshake:master_secret(Version, PremasterSecret, + case ssl_handshake:master_secret(tls_record, Version, PremasterSecret, ConnectionStates0, client) of {MasterSecret, ConnectionStates} -> Session = Session0#session{master_secret = MasterSecret}, @@ -650,7 +646,7 @@ certify(#client_key_exchange{} = Msg, certify(#client_key_exchange{exchange_keys = Keys}, State = #state{key_algorithm = KeyAlg, negotiated_version = Version}) -> try - certify_client_key_exchange(tls_handshake:decode_client_key(Keys, KeyAlg, Version), State) + certify_client_key_exchange(ssl_handshake:decode_client_key(Keys, KeyAlg, Version), State) catch #alert{} = Alert -> handle_own_alert(Alert, Version, certify, State) @@ -668,8 +664,8 @@ certify_client_key_exchange(#encrypted_premaster_secret{premaster_secret= EncPMS connection_states = ConnectionStates0, session = Session0, private_key = Key} = State0) -> - PremasterSecret = tls_handshake:decrypt_premaster_secret(EncPMS, Key), - case tls_handshake:master_secret(Version, PremasterSecret, + PremasterSecret = ssl_handshake:decrypt_premaster_secret(EncPMS, Key), + case ssl_handshake:master_secret(tls_record, Version, PremasterSecret, ConnectionStates0, server) of {MasterSecret, ConnectionStates} -> Session = Session0#session{master_secret = MasterSecret}, @@ -735,7 +731,7 @@ certify_client_key_exchange(#client_rsa_psk_identity{ #encrypted_premaster_secret{premaster_secret= EncPMS}}, #state{negotiated_version = Version, private_key = Key} = State0) -> - PremasterSecret = tls_handshake:decrypt_premaster_secret(EncPMS, Key), + PremasterSecret = ssl_handshake:decrypt_premaster_secret(EncPMS, Key), case server_rsa_psk_master_secret(PskIdentity, PremasterSecret, State0) of #state{} = State1 -> {Record, State} = next_record(State1), @@ -775,7 +771,7 @@ cipher(#certificate_verify{signature = Signature, hashsign_algorithm = CertHashS } = State0) -> HashSign = tls_handshake:select_cert_hashsign(CertHashSign, Algo, Version), - case tls_handshake:certificate_verify(Signature, PublicKeyInfo, + case ssl_handshake:certificate_verify(Signature, PublicKeyInfo, Version, HashSign, MasterSecret, Handshake) of valid -> {Record, State} = next_record(State0), @@ -798,7 +794,7 @@ cipher(#finished{verify_data = Data} = Finished, = Session0, connection_states = ConnectionStates0, tls_handshake_history = Handshake0} = State) -> - case tls_handshake:verify_connection(Version, Finished, + case ssl_handshake:verify_connection(Version, Finished, opposite_role(Role), get_current_connection_state_prf(ConnectionStates0, read), MasterSecret, Handshake0) of @@ -1048,7 +1044,7 @@ handle_sync_event({prf, Secret, Label, Seed, WantedLength}, _, StateName, (client_random, Acc) -> [ClientRandom|Acc]; (server_random, Acc) -> [ServerRandom|Acc] end, [], Seed)), - tls_handshake:prf(Version, SecretToUse, Label, SeedToUse, WantedLength) + ssl_handshake:prf(Version, SecretToUse, Label, SeedToUse, WantedLength) catch exit:_ -> {error, badarg}; error:Reason -> {error, Reason} @@ -1414,7 +1410,7 @@ certify_client(#state{client_certificate_requested = true, role = client, session = #session{own_certificate = OwnCert}, socket = Socket, tls_handshake_history = Handshake0} = State) -> - Certificate = tls_handshake:certificate(OwnCert, CertDbHandle, CertDbRef, client), + Certificate = ssl_handshake:certificate(OwnCert, CertDbHandle, CertDbRef, client), {BinCert, ConnectionStates, Handshake} = encode_handshake(Certificate, Version, ConnectionStates0, Handshake0), Transport:send(Socket, BinCert), @@ -1451,21 +1447,17 @@ verify_client_cert(#state{client_certificate_requested = true, role = client, verify_client_cert(#state{client_certificate_requested = false} = State) -> State. -do_server_hello(Type, NextProtocolsToSend, - EcPointFormats, EllipticCurves, +do_server_hello(Type, #hello_extensions{next_protocol_negotiation = NextProtocols} = ServerHelloExt, #state{negotiated_version = Version, session = #session{session_id = SessId}, - connection_states = ConnectionStates0, - renegotiation = {Renegotiation, _}} + connection_states = ConnectionStates0} = State0) when is_atom(Type) -> ServerHello = - tls_handshake:server_hello(SessId, Version, - ConnectionStates0, Renegotiation, - NextProtocolsToSend, EcPointFormats, EllipticCurves), + tls_handshake:server_hello(SessId, Version, ConnectionStates0, ServerHelloExt), State = server_hello(ServerHello, State0#state{expecting_next_protocol_negotiation = - NextProtocolsToSend =/= undefined}), + NextProtocols =/= undefined}), case Type of new -> new_server_hello(ServerHello, State); @@ -1496,7 +1488,7 @@ resumed_server_hello(#state{session = Session, connection_states = ConnectionStates0, negotiated_version = Version} = State0) -> - case tls_handshake:master_secret(Version, Session, + case ssl_handshake:master_secret(tls_record, Version, Session, ConnectionStates0, server) of {_, ConnectionStates1} -> State1 = State0#state{connection_states = ConnectionStates1, @@ -1525,7 +1517,7 @@ handle_resumed_session(SessId, #state{connection_states = ConnectionStates0, session_cache = Cache, session_cache_cb = CacheCb} = State0) -> Session = CacheCb:lookup(Cache, {{Host, Port}, SessId}), - case tls_handshake:master_secret(Version, Session, + case ssl_handshake:master_secret(tls_record, Version, Session, ConnectionStates0, client) of {_, ConnectionStates} -> {Record, State} = @@ -1584,7 +1576,7 @@ server_hello_done(#state{transport_cb = Transport, connection_states = ConnectionStates0, tls_handshake_history = Handshake0} = State) -> - HelloDone = tls_handshake:server_hello_done(), + HelloDone = ssl_handshake:server_hello_done(), {BinHelloDone, ConnectionStates, Handshake} = encode_handshake(HelloDone, Version, ConnectionStates0, Handshake0), @@ -1604,7 +1596,7 @@ certify_server(#state{transport_cb = Transport, cert_db = CertDbHandle, cert_db_ref = CertDbRef, session = #session{own_certificate = OwnCert}} = State) -> - case tls_handshake:certificate(OwnCert, CertDbHandle, CertDbRef, server) of + case ssl_handshake:certificate(OwnCert, CertDbHandle, CertDbRef, server) of CertMsg = #certificate{} -> {BinCertMsg, ConnectionStates, Handshake} = encode_handshake(CertMsg, Version, ConnectionStates0, Handshake0), @@ -1637,7 +1629,7 @@ key_exchange(#state{role = server, key_algorithm = Algo, SecParams = ConnectionState#connection_state.security_parameters, #security_parameters{client_random = ClientRandom, server_random = ServerRandom} = SecParams, - Msg = tls_handshake:key_exchange(server, Version, {dh, DHKeys, Params, + Msg = ssl_handshake:key_exchange(server, Version, {dh, DHKeys, Params, HashSignAlgo, ClientRandom, ServerRandom, PrivateKey}), @@ -1669,7 +1661,7 @@ key_exchange(#state{role = server, key_algorithm = Algo, SecParams = ConnectionState#connection_state.security_parameters, #security_parameters{client_random = ClientRandom, server_random = ServerRandom} = SecParams, - Msg = tls_handshake:key_exchange(server, Version, {ecdh, ECDHKeys, + Msg = ssl_handshake:key_exchange(server, Version, {ecdh, ECDHKeys, HashSignAlgo, ClientRandom, ServerRandom, PrivateKey}), @@ -1698,7 +1690,7 @@ key_exchange(#state{role = server, key_algorithm = psk, SecParams = ConnectionState#connection_state.security_parameters, #security_parameters{client_random = ClientRandom, server_random = ServerRandom} = SecParams, - Msg = tls_handshake:key_exchange(server, Version, {psk, PskIdentityHint, + Msg = ssl_handshake:key_exchange(server, Version, {psk, PskIdentityHint, HashSignAlgo, ClientRandom, ServerRandom, PrivateKey}), @@ -1725,7 +1717,7 @@ key_exchange(#state{role = server, key_algorithm = dhe_psk, SecParams = ConnectionState#connection_state.security_parameters, #security_parameters{client_random = ClientRandom, server_random = ServerRandom} = SecParams, - Msg = tls_handshake:key_exchange(server, Version, {dhe_psk, PskIdentityHint, DHKeys, Params, + Msg = ssl_handshake:key_exchange(server, Version, {dhe_psk, PskIdentityHint, DHKeys, Params, HashSignAlgo, ClientRandom, ServerRandom, PrivateKey}), @@ -1754,7 +1746,7 @@ key_exchange(#state{role = server, key_algorithm = rsa_psk, SecParams = ConnectionState#connection_state.security_parameters, #security_parameters{client_random = ClientRandom, server_random = ServerRandom} = SecParams, - Msg = tls_handshake:key_exchange(server, Version, {psk, PskIdentityHint, + Msg = ssl_handshake:key_exchange(server, Version, {psk, PskIdentityHint, HashSignAlgo, ClientRandom, ServerRandom, PrivateKey}), @@ -1790,7 +1782,7 @@ key_exchange(#state{role = server, key_algorithm = Algo, SecParams = ConnectionState#connection_state.security_parameters, #security_parameters{client_random = ClientRandom, server_random = ServerRandom} = SecParams, - Msg = tls_handshake:key_exchange(server, Version, {srp, Keys, SrpParams, + Msg = ssl_handshake:key_exchange(server, Version, {srp, Keys, SrpParams, HashSignAlgo, ClientRandom, ServerRandom, PrivateKey}), @@ -1826,7 +1818,7 @@ key_exchange(#state{role = client, when Algorithm == dhe_dss; Algorithm == dhe_rsa; Algorithm == dh_anon -> - Msg = tls_handshake:key_exchange(client, Version, {dh, DhPubKey}), + Msg = ssl_handshake:key_exchange(client, Version, {dh, DhPubKey}), {BinMsg, ConnectionStates, Handshake} = encode_handshake(Msg, Version, ConnectionStates0, Handshake0), Transport:send(Socket, BinMsg), @@ -1843,7 +1835,7 @@ key_exchange(#state{role = client, when Algorithm == ecdhe_ecdsa; Algorithm == ecdhe_rsa; Algorithm == ecdh_ecdsa; Algorithm == ecdh_rsa; Algorithm == ecdh_anon -> - Msg = tls_handshake:key_exchange(client, Version, {ecdh, Keys}), + Msg = ssl_handshake:key_exchange(client, Version, {ecdh, Keys}), {BinMsg, ConnectionStates, Handshake} = encode_handshake(Msg, Version, ConnectionStates0, Handshake0), Transport:send(Socket, BinMsg), @@ -1857,7 +1849,7 @@ key_exchange(#state{role = client, negotiated_version = Version, socket = Socket, transport_cb = Transport, tls_handshake_history = Handshake0} = State) -> - Msg = tls_handshake:key_exchange(client, Version, {psk, SslOpts#ssl_options.psk_identity}), + Msg = ssl_handshake:key_exchange(client, Version, {psk, SslOpts#ssl_options.psk_identity}), {BinMsg, ConnectionStates, Handshake} = encode_handshake(Msg, Version, ConnectionStates0, Handshake0), Transport:send(Socket, BinMsg), @@ -1872,7 +1864,7 @@ key_exchange(#state{role = client, diffie_hellman_keys = {DhPubKey, _}, socket = Socket, transport_cb = Transport, tls_handshake_history = Handshake0} = State) -> - Msg = tls_handshake:key_exchange(client, Version, {dhe_psk, SslOpts#ssl_options.psk_identity, DhPubKey}), + Msg = ssl_handshake:key_exchange(client, Version, {dhe_psk, SslOpts#ssl_options.psk_identity, DhPubKey}), {BinMsg, ConnectionStates, Handshake} = encode_handshake(Msg, Version, ConnectionStates0, Handshake0), Transport:send(Socket, BinMsg), @@ -1905,7 +1897,7 @@ key_exchange(#state{role = client, when Algorithm == srp_dss; Algorithm == srp_rsa; Algorithm == srp_anon -> - Msg = tls_handshake:key_exchange(client, Version, {srp, ClientPubKey}), + Msg = ssl_handshake:key_exchange(client, Version, {srp, ClientPubKey}), {BinMsg, ConnectionStates, Handshake} = encode_handshake(Msg, Version, ConnectionStates0, Handshake0), Transport:send(Socket, BinMsg), @@ -1922,7 +1914,7 @@ rsa_key_exchange(Version, PremasterSecret, PublicKeyInfo = {Algorithm, _, _}) Algorithm == ?sha384WithRSAEncryption; Algorithm == ?sha512WithRSAEncryption -> - tls_handshake:key_exchange(client, Version, + ssl_handshake:key_exchange(client, Version, {premaster_secret, PremasterSecret, PublicKeyInfo}); rsa_key_exchange(_, _, _) -> @@ -1938,7 +1930,7 @@ rsa_psk_key_exchange(Version, PskIdentity, PremasterSecret, PublicKeyInfo = {Alg Algorithm == ?sha384WithRSAEncryption; Algorithm == ?sha512WithRSAEncryption -> - tls_handshake:key_exchange(client, Version, + ssl_handshake:key_exchange(client, Version, {psk_premaster_secret, PskIdentity, PremasterSecret, PublicKeyInfo}); rsa_psk_key_exchange(_, _, _, _) -> @@ -1952,7 +1944,11 @@ request_client_cert(#state{ssl_options = #ssl_options{verify = verify_peer}, negotiated_version = Version, socket = Socket, transport_cb = Transport} = State) -> - Msg = tls_handshake:certificate_request(ConnectionStates0, CertDbHandle, CertDbRef, Version), + #connection_state{security_parameters = + #security_parameters{cipher_suite = CipherSuite}} = + tls_record:pending_connection_state(ConnectionStates0, read), + Msg = ssl_handshake:certificate_request(CipherSuite, CertDbHandle, CertDbRef), + {BinMsg, ConnectionStates, Handshake} = encode_handshake(Msg, Version, ConnectionStates0, Handshake0), Transport:send(Socket, BinMsg), @@ -1985,7 +1981,7 @@ next_protocol(#state{transport_cb = Transport, socket = Socket, next_protocol = NextProtocol, connection_states = ConnectionStates0, tls_handshake_history = Handshake0} = State) -> - NextProtocolMessage = tls_handshake:next_protocol(NextProtocol), + NextProtocolMessage = ssl_handshake:next_protocol(NextProtocol), {BinMsg, ConnectionStates, Handshake} = encode_handshake(NextProtocolMessage, Version, ConnectionStates0, Handshake0), Transport:send(Socket, BinMsg), State#state{connection_states = ConnectionStates, @@ -2007,7 +2003,7 @@ finished(#state{role = Role, socket = Socket, negotiated_version = Version, connection_states = ConnectionStates0, tls_handshake_history = Handshake0}, StateName) -> MasterSecret = Session#session.master_secret, - Finished = tls_handshake:finished(Version, Role, + Finished = ssl_handshake:finished(Version, Role, get_current_connection_state_prf(ConnectionStates0, write), MasterSecret, Handshake0), ConnectionStates1 = save_verify_data(Role, Finished, ConnectionStates0, StateName), @@ -2028,7 +2024,8 @@ save_verify_data(server, #finished{verify_data = Data}, ConnectionStates, abbrev handle_server_key(#server_key_exchange{exchange_keys = Keys}, #state{key_algorithm = KeyAlg, negotiated_version = Version} = State) -> - Params = tls_handshake:decode_server_key(Keys, KeyAlg, Version), + + Params = ssl_handshake:decode_server_key(Keys, KeyAlg, Version), HashSign = negotiated_hashsign(Params#server_key_params.hashsign, KeyAlg, Version), case is_anonymous(KeyAlg) of true -> @@ -2050,11 +2047,11 @@ verify_server_key(#server_key_params{params = Params, SecParams = ConnectionState#connection_state.security_parameters, #security_parameters{client_random = ClientRandom, server_random = ServerRandom} = SecParams, - Hash = tls_handshake:server_key_exchange_hash(HashAlgo, + Hash = ssl_handshake:server_key_exchange_hash(HashAlgo, <>), - case tls_handshake:verify_signature(Version, Hash, HashSign, Signature, PubKeyInfo) of + case ssl_handshake:verify_signature(Version, Hash, HashSign, Signature, PubKeyInfo) of true -> server_master_secret(Params, State); false -> @@ -2090,7 +2087,7 @@ master_from_premaster_secret(PremasterSecret, #state{session = Session, negotiated_version = Version, role = Role, connection_states = ConnectionStates0} = State) -> - case tls_handshake:master_secret(Version, PremasterSecret, + case ssl_handshake:master_secret(tls_record, Version, PremasterSecret, ConnectionStates0, Role) of {MasterSecret, ConnectionStates} -> State#state{ @@ -2926,7 +2923,7 @@ renegotiate(#state{role = server, transport_cb = Transport, negotiated_version = Version, connection_states = ConnectionStates0} = State0) -> - HelloRequest = tls_handshake:hello_request(), + HelloRequest = ssl_handshake:hello_request(), Frag = tls_handshake:encode_handshake(HelloRequest, Version), Hs0 = tls_handshake:init_handshake_history(), {BinMsg, ConnectionStates} = diff --git a/lib/ssl/src/tls_handshake.erl b/lib/ssl/src/tls_handshake.erl index 6cc6e9e885..066590afb1 100644 --- a/lib/ssl/src/tls_handshake.erl +++ b/lib/ssl/src/tls_handshake.erl @@ -18,7 +18,8 @@ %% %%---------------------------------------------------------------------- -%% Purpose: Help funtions for handling the SSL-handshake protocol +%% Purpose: Help funtions for handling the TLS (specific parts of) +%%% SSL/TLS/DTLS handshake protocol %%---------------------------------------------------------------------- -module(tls_handshake). @@ -31,24 +32,9 @@ -include("ssl_srp.hrl"). -include_lib("public_key/include/public_key.hrl"). --export([master_secret/4, client_hello/8, server_hello/7, hello/4, - hello_request/0, certify/7, certificate/4, - client_certificate_verify/6, certificate_verify/6, verify_signature/5, - certificate_request/4, key_exchange/3, server_key_exchange_hash/2, - finished/5, verify_connection/6, get_tls_handshake/3, - decode_client_key/3, decode_server_key/3, server_hello_done/0, - encode_handshake/2, init_handshake_history/0, update_handshake_history/2, - decrypt_premaster_secret/2, prf/5, next_protocol/1, select_hashsign/2, - select_cert_hashsign/3]). - --export([dec_hello_extensions/2]). - --type tls_handshake() :: #client_hello{} | #server_hello{} | - #server_hello_done{} | #certificate{} | #certificate_request{} | - #client_key_exchange{} | #finished{} | #certificate_verify{} | - #hello_request{} | #next_protocol{}. - --define(NAMED_CURVE_TYPE, 3). +-export([client_hello/8, server_hello/4, hello/4, + get_tls_handshake/3, encode_handshake/2, + init_handshake_history/0, update_handshake_history/2]). %%==================================================================== %% Internal application API @@ -68,513 +54,103 @@ client_hello(Host, Port, ConnectionStates, Version = tls_record:highest_protocol_version(Versions), Pending = tls_record:pending_connection_state(ConnectionStates, read), SecParams = Pending#connection_state.security_parameters, - Ciphers = available_suites(UserSuites, Version), - SRP = srp_user(SslOpts), - {EcPointFormats, EllipticCurves} = default_ecc_extensions(Version), + CipherSuites = ssl_handshake:available_suites(UserSuites, Version), + + Extensions = ssl_handshake:client_hello_extensions(Version, CipherSuites, + SslOpts, ConnectionStates, Renegotiation), Id = ssl_session:client_id({Host, Port, SslOpts}, Cache, CacheCb, OwnCert), #client_hello{session_id = Id, client_version = Version, - cipher_suites = cipher_suites(Ciphers, Renegotiation), + cipher_suites = ssl_handshake:cipher_suites(CipherSuites, Renegotiation), compression_methods = tls_record:compressions(), random = SecParams#security_parameters.client_random, - - renegotiation_info = - renegotiation_info(client, ConnectionStates, Renegotiation), - srp = SRP, - hash_signs = advertised_hash_signs(Version), - ec_point_formats = EcPointFormats, - elliptic_curves = EllipticCurves, - next_protocol_negotiation = - encode_client_protocol_negotiation(SslOpts#ssl_options.next_protocol_selector, Renegotiation) + extensions = Extensions }. -encode_protocol(Protocol, Acc) -> - Len = byte_size(Protocol), - <>. - -encode_protocols_advertised_on_server(undefined) -> - undefined; - -encode_protocols_advertised_on_server(Protocols) -> - #next_protocol_negotiation{extension_data = lists:foldl(fun encode_protocol/2, <<>>, Protocols)}. - %%-------------------------------------------------------------------- --spec server_hello(session_id(), tls_version(), #connection_states{}, - boolean(), [binary()] | undefined, - #ec_point_formats{} | undefined, - #elliptic_curves{} | undefined) -> #server_hello{}. +-spec server_hello(#session{}, tls_version(), #connection_states{}, + #hello_extensions{}) -> #server_hello{}. %% %% Description: Creates a server hello message. %%-------------------------------------------------------------------- -server_hello(SessionId, Version, ConnectionStates, Renegotiation, - ProtocolsAdvertisedOnServer, EcPointFormats, EllipticCurves) -> +server_hello(SessionId, Version, ConnectionStates, Extensions) -> Pending = tls_record:pending_connection_state(ConnectionStates, read), SecParams = Pending#connection_state.security_parameters, + #server_hello{server_version = Version, cipher_suite = SecParams#security_parameters.cipher_suite, compression_method = SecParams#security_parameters.compression_algorithm, random = SecParams#security_parameters.server_random, session_id = SessionId, - renegotiation_info = - renegotiation_info(server, ConnectionStates, Renegotiation), - ec_point_formats = EcPointFormats, - elliptic_curves = EllipticCurves, - next_protocol_negotiation = encode_protocols_advertised_on_server(ProtocolsAdvertisedOnServer) + extensions = Extensions }. -%%-------------------------------------------------------------------- --spec hello_request() -> #hello_request{}. -%% -%% Description: Creates a hello request message sent by server to -%% trigger renegotiation. -%%-------------------------------------------------------------------- -hello_request() -> - #hello_request{}. - %%-------------------------------------------------------------------- -spec hello(#server_hello{} | #client_hello{}, #ssl_options{}, #connection_states{} | {inet:port_number(), #session{}, db_handle(), atom(), #connection_states{}, binary()}, boolean()) -> {tls_version(), session_id(), #connection_states{}, binary() | undefined}| - {tls_version(), {resumed | new, #session{}}, #connection_states{}, [binary()] | undefined, + {tls_version(), {resumed | new, #session{}}, #connection_states{}, + [binary()] | undefined, [oid()] | undefined, [oid()] | undefined} | #alert{}. %% %% Description: Handles a recieved hello message %%-------------------------------------------------------------------- -hello(#server_hello{cipher_suite = CipherSuite, server_version = Version, - compression_method = Compression, random = Random, - session_id = SessionId, renegotiation_info = Info, - hash_signs = _HashSigns} = Hello, - #ssl_options{secure_renegotiate = SecureRenegotation, next_protocol_selector = NextProtocolSelector, - versions = SupportedVersions}, +hello(#server_hello{server_version = Version, random = Random, + cipher_suite = CipherSuite, + compression_method = Compression, + session_id = SessionId, extensions = HelloExt}, + #ssl_options{versions = SupportedVersions} = SslOpt, ConnectionStates0, Renegotiation) -> case tls_record:is_acceptable_version(Version, SupportedVersions) of true -> - case handle_renegotiation_info(client, Info, ConnectionStates0, - Renegotiation, SecureRenegotation, []) of - {ok, ConnectionStates1} -> - ConnectionStates = - hello_pending_connection_states(client, Version, CipherSuite, Random, - Compression, ConnectionStates1), - case handle_next_protocol(Hello, NextProtocolSelector, Renegotiation) of - #alert{} = Alert -> - Alert; - Protocol -> - {Version, SessionId, ConnectionStates, Protocol} - end; - #alert{} = Alert -> - Alert - end; + handle_hello_extensions(Version, SessionId, Random, CipherSuite, + Compression, HelloExt, SslOpt, ConnectionStates0, Renegotiation); false -> ?ALERT_REC(?FATAL, ?PROTOCOL_VERSION) end; -hello(#client_hello{client_version = ClientVersion} = Hello, +hello(#client_hello{client_version = ClientVersion, + session_id = SugesstedId, + cipher_suites = CipherSuites, + compression_methods = Compressions, + random = Random, + extensions = HelloExt}, #ssl_options{versions = Versions} = SslOpts, {Port, Session0, Cache, CacheCb, ConnectionStates0, Cert}, Renegotiation) -> - Version = select_version(ClientVersion, Versions), + Version = ssl_handshake:select_version(tls_record, ClientVersion, Versions), case tls_record:is_acceptable_version(Version, Versions) of true -> %% TODO: need to take supported Curves into Account when selecting the CipherSuite.... %% if whe have an ECDSA cert with an unsupported curve, we need to drop ECDSA ciphers {Type, #session{cipher_suite = CipherSuite} = Session1} - = select_session(Hello, Port, Session0, Version, - SslOpts, Cache, CacheCb, Cert), + = ssl_handshake:select_session(SugesstedId, CipherSuites, Compressions, + Port, Session0, Version, + SslOpts, Cache, CacheCb, Cert), case CipherSuite of no_suite -> ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY); _ -> - try handle_hello_extensions(Hello, Version, SslOpts, Session1, ConnectionStates0, Renegotiation) of - {Session, ConnectionStates, ProtocolsToAdvertise, ECPointFormats, EllipticCurves} -> - {Version, {Type, Session}, ConnectionStates, - ProtocolsToAdvertise, ECPointFormats, EllipticCurves} - catch throw:Alert -> - Alert - end + handle_hello_extensions(Version, Type, Random, HelloExt, + SslOpts, Session1, ConnectionStates0, + Renegotiation) end; false -> ?ALERT_REC(?FATAL, ?PROTOCOL_VERSION) end. -%%-------------------------------------------------------------------- --spec certify(#certificate{}, db_handle(), certdb_ref(), integer() | nolimit, - verify_peer | verify_none, {fun(), term}, - client | server) -> {der_cert(), public_key_info()} | #alert{}. -%% -%% Description: Handles a certificate handshake message -%%-------------------------------------------------------------------- -certify(#certificate{asn1_certificates = ASN1Certs}, CertDbHandle, CertDbRef, - MaxPathLen, _Verify, VerifyFunAndState, Role) -> - [PeerCert | _] = ASN1Certs, - - ValidationFunAndState = - case VerifyFunAndState of - undefined -> - {fun(OtpCert, ExtensionOrVerifyResult, SslState) -> - ssl_certificate:validate_extension(OtpCert, - ExtensionOrVerifyResult, SslState) - end, Role}; - {Fun, UserState0} -> - {fun(OtpCert, {extension, _} = Extension, {SslState, UserState}) -> - case ssl_certificate:validate_extension(OtpCert, - Extension, - SslState) of - {valid, NewSslState} -> - {valid, {NewSslState, UserState}}; - {fail, Reason} -> - apply_user_fun(Fun, OtpCert, Reason, UserState, - SslState); - {unknown, _} -> - apply_user_fun(Fun, OtpCert, - Extension, UserState, SslState) - end; - (OtpCert, VerifyResult, {SslState, UserState}) -> - apply_user_fun(Fun, OtpCert, VerifyResult, UserState, - SslState) - end, {Role, UserState0}} - end, - - try - {TrustedErlCert, CertPath} = - ssl_certificate:trusted_cert_and_path(ASN1Certs, CertDbHandle, CertDbRef), - case public_key:pkix_path_validation(TrustedErlCert, - CertPath, - [{max_path_length, - MaxPathLen}, - {verify_fun, ValidationFunAndState}]) of - {ok, {PublicKeyInfo,_}} -> - {PeerCert, PublicKeyInfo}; - {error, Reason} -> - path_validation_alert(Reason) - end - catch - error:_ -> - %% ASN-1 decode of certificate somehow failed - ?ALERT_REC(?FATAL, ?CERTIFICATE_UNKNOWN) - end. - -%%-------------------------------------------------------------------- --spec certificate(der_cert(), db_handle(), certdb_ref(), client | server) -> #certificate{} | #alert{}. -%% -%% Description: Creates a certificate message. -%%-------------------------------------------------------------------- -certificate(OwnCert, CertDbHandle, CertDbRef, client) -> - Chain = - case ssl_certificate:certificate_chain(OwnCert, CertDbHandle, CertDbRef) of - {ok, CertChain} -> - CertChain; - {error, _} -> - %% If no suitable certificate is available, the client - %% SHOULD send a certificate message containing no - %% certificates. (chapter 7.4.6. RFC 4346) - [] - end, - #certificate{asn1_certificates = Chain}; - -certificate(OwnCert, CertDbHandle, CertDbRef, server) -> - case ssl_certificate:certificate_chain(OwnCert, CertDbHandle, CertDbRef) of - {ok, Chain} -> - #certificate{asn1_certificates = Chain}; - {error, _} -> - ?ALERT_REC(?FATAL, ?INTERNAL_ERROR) - end. - -%%-------------------------------------------------------------------- --spec client_certificate_verify(undefined | der_cert(), binary(), - tls_version(), term(), private_key(), - tls_handshake_history()) -> - #certificate_verify{} | ignore | #alert{}. -%% -%% Description: Creates a certificate_verify message, called by the client. -%%-------------------------------------------------------------------- -client_certificate_verify(undefined, _, _, _, _, _) -> - ignore; -client_certificate_verify(_, _, _, _, undefined, _) -> - ignore; -client_certificate_verify(OwnCert, MasterSecret, Version, - {HashAlgo, _} = HashSign, - PrivateKey, {Handshake, _}) -> - case public_key:pkix_is_fixed_dh_cert(OwnCert) of - true -> - ?ALERT_REC(?FATAL, ?UNSUPPORTED_CERTIFICATE); - false -> - Hashes = - calc_certificate_verify(Version, HashAlgo, MasterSecret, Handshake), - Signed = digitally_signed(Version, Hashes, HashAlgo, PrivateKey), - #certificate_verify{signature = Signed, hashsign_algorithm = HashSign} - end. - -%%-------------------------------------------------------------------- --spec certificate_verify(binary(), public_key_info(), tls_version(), term(), - binary(), tls_handshake_history()) -> valid | #alert{}. -%% -%% Description: Checks that the certificate_verify message is valid. -%%-------------------------------------------------------------------- -certificate_verify(Signature, PublicKeyInfo, Version, - HashSign = {HashAlgo, _}, MasterSecret, {_, Handshake}) -> - Hash = calc_certificate_verify(Version, HashAlgo, MasterSecret, Handshake), - case verify_signature(Version, Hash, HashSign, Signature, PublicKeyInfo) of - true -> - valid; - _ -> - ?ALERT_REC(?FATAL, ?BAD_CERTIFICATE) - end. - -%%-------------------------------------------------------------------- --spec verify_signature(tls_version(), binary(), {term(), term()}, binary(), - public_key_info()) -> true | false. -%% -%% Description: Checks that a public_key signature is valid. -%%-------------------------------------------------------------------- -verify_signature(_Version, _Hash, {_HashAlgo, anon}, _Signature, _) -> - true; -verify_signature({3, Minor}, Hash, {HashAlgo, rsa}, Signature, {?rsaEncryption, PubKey, _PubKeyParams}) - when Minor >= 3 -> - public_key:verify({digest, Hash}, HashAlgo, Signature, PubKey); -verify_signature(_Version, Hash, _HashAlgo, Signature, {?rsaEncryption, PubKey, _PubKeyParams}) -> - case public_key:decrypt_public(Signature, PubKey, - [{rsa_pad, rsa_pkcs1_padding}]) of - Hash -> true; - _ -> false - end; -verify_signature(_Version, Hash, {HashAlgo, dsa}, Signature, {?'id-dsa', PublicKey, PublicKeyParams}) -> - public_key:verify({digest, Hash}, HashAlgo, Signature, {PublicKey, PublicKeyParams}); -verify_signature(_Version, Hash, {HashAlgo, ecdsa}, Signature, {?'id-ecPublicKey', PublicKey, PublicKeyParams}) -> - public_key:verify({digest, Hash}, HashAlgo, Signature, {PublicKey, PublicKeyParams}). - -%%-------------------------------------------------------------------- --spec certificate_request(#connection_states{}, db_handle(), certdb_ref(), tls_version()) -> - #certificate_request{}. -%% -%% Description: Creates a certificate_request message, called by the server. -%%-------------------------------------------------------------------- -certificate_request(ConnectionStates, CertDbHandle, CertDbRef, Version) -> - #connection_state{security_parameters = - #security_parameters{cipher_suite = CipherSuite}} = - tls_record:pending_connection_state(ConnectionStates, read), - Types = certificate_types(CipherSuite), - HashSigns = advertised_hash_signs(Version), - Authorities = certificate_authorities(CertDbHandle, CertDbRef), - #certificate_request{ - certificate_types = Types, - hashsign_algorithms = HashSigns, - certificate_authorities = Authorities - }. - -%%-------------------------------------------------------------------- --spec key_exchange(client | server, tls_version(), - {premaster_secret, binary(), public_key_info()} | - {dh, binary()} | - {dh, {binary(), binary()}, #'DHParameter'{}, {HashAlgo::atom(), SignAlgo::atom()}, - binary(), binary(), private_key()} | - {ecdh, #'ECPrivateKey'{}} | - {psk, binary()} | - {dhe_psk, binary(), binary()} | - {srp, {binary(), binary()}, #srp_user{}, {HashAlgo::atom(), SignAlgo::atom()}, - binary(), binary(), private_key()}) -> - #client_key_exchange{} | #server_key_exchange{}. -%% -%% Description: Creates a keyexchange message. -%%-------------------------------------------------------------------- -key_exchange(client, _Version, {premaster_secret, Secret, {_, PublicKey, _}}) -> - EncPremasterSecret = - encrypted_premaster_secret(Secret, PublicKey), - #client_key_exchange{exchange_keys = EncPremasterSecret}; - -key_exchange(client, _Version, {dh, PublicKey}) -> - #client_key_exchange{ - exchange_keys = #client_diffie_hellman_public{ - dh_public = PublicKey} - }; - -key_exchange(client, _Version, {ecdh, #'ECPrivateKey'{publicKey = {0, ECPublicKey}}}) -> - #client_key_exchange{ - exchange_keys = #client_ec_diffie_hellman_public{ - dh_public = ECPublicKey} - }; - -key_exchange(client, _Version, {psk, Identity}) -> - #client_key_exchange{ - exchange_keys = #client_psk_identity{ - identity = Identity} - }; - -key_exchange(client, _Version, {dhe_psk, Identity, PublicKey}) -> - #client_key_exchange{ - exchange_keys = #client_dhe_psk_identity{ - identity = Identity, - dh_public = PublicKey} - }; - -key_exchange(client, _Version, {psk_premaster_secret, PskIdentity, Secret, {_, PublicKey, _}}) -> - EncPremasterSecret = - encrypted_premaster_secret(Secret, PublicKey), - #client_key_exchange{ - exchange_keys = #client_rsa_psk_identity{ - identity = PskIdentity, - exchange_keys = EncPremasterSecret}}; - -key_exchange(client, _Version, {srp, PublicKey}) -> - #client_key_exchange{ - exchange_keys = #client_srp_public{ - srp_a = PublicKey} - }; - -key_exchange(server, Version, {dh, {PublicKey, _}, - #'DHParameter'{prime = P, base = G}, - HashSign, ClientRandom, ServerRandom, PrivateKey}) -> - ServerDHParams = #server_dh_params{dh_p = int_to_bin(P), - dh_g = int_to_bin(G), dh_y = PublicKey}, - enc_server_key_exchange(Version, ServerDHParams, HashSign, - ClientRandom, ServerRandom, PrivateKey); - -key_exchange(server, Version, {ecdh, #'ECPrivateKey'{publicKey = {0, ECPublicKey}, - parameters = ECCurve}, HashSign, ClientRandom, ServerRandom, - PrivateKey}) -> - ServerECParams = #server_ecdh_params{curve = ECCurve, public = ECPublicKey}, - enc_server_key_exchange(Version, ServerECParams, HashSign, - ClientRandom, ServerRandom, PrivateKey); - -key_exchange(server, Version, {psk, PskIdentityHint, - HashSign, ClientRandom, ServerRandom, PrivateKey}) -> - ServerPSKParams = #server_psk_params{hint = PskIdentityHint}, - enc_server_key_exchange(Version, ServerPSKParams, HashSign, - ClientRandom, ServerRandom, PrivateKey); - -key_exchange(server, Version, {dhe_psk, PskIdentityHint, {PublicKey, _}, - #'DHParameter'{prime = P, base = G}, - HashSign, ClientRandom, ServerRandom, PrivateKey}) -> - ServerEDHPSKParams = #server_dhe_psk_params{ - hint = PskIdentityHint, - dh_params = #server_dh_params{dh_p = int_to_bin(P), - dh_g = int_to_bin(G), dh_y = PublicKey} - }, - enc_server_key_exchange(Version, ServerEDHPSKParams, - HashSign, ClientRandom, ServerRandom, PrivateKey); - -key_exchange(server, Version, {srp, {PublicKey, _}, - #srp_user{generator = Generator, prime = Prime, - salt = Salt}, - HashSign, ClientRandom, ServerRandom, PrivateKey}) -> - ServerSRPParams = #server_srp_params{srp_n = Prime, srp_g = Generator, - srp_s = Salt, srp_b = PublicKey}, - enc_server_key_exchange(Version, ServerSRPParams, HashSign, - ClientRandom, ServerRandom, PrivateKey). - -enc_server_key_exchange(Version, Params, {HashAlgo, SignAlgo}, - ClientRandom, ServerRandom, PrivateKey) -> - EncParams = enc_server_key(Params), - case HashAlgo of - null -> - #server_key_params{params = Params, - params_bin = EncParams, - hashsign = {null, anon}, - signature = <<>>}; - _ -> - Hash = - server_key_exchange_hash(HashAlgo, <>), - Signature = digitally_signed(Version, Hash, HashAlgo, PrivateKey), - #server_key_params{params = Params, - params_bin = EncParams, - hashsign = {HashAlgo, SignAlgo}, - signature = Signature} - end. - -%%-------------------------------------------------------------------- --spec master_secret(tls_version(), #session{} | binary(), #connection_states{}, - client | server) -> {binary(), #connection_states{}} | #alert{}. -%% -%% Description: Sets or calculates the master secret and calculate keys, -%% updating the pending connection states. The Mastersecret and the update -%% connection states are returned or an alert if the calculation fails. -%%------------------------------------------------------------------- -master_secret(Version, #session{master_secret = Mastersecret}, - ConnectionStates, Role) -> - ConnectionState = - tls_record:pending_connection_state(ConnectionStates, read), - SecParams = ConnectionState#connection_state.security_parameters, - try master_secret(Version, Mastersecret, SecParams, - ConnectionStates, Role) - catch - exit:Reason -> - Report = io_lib:format("Key calculation failed due to ~p", - [Reason]), - error_logger:error_report(Report), - ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE) - end; - -master_secret(Version, PremasterSecret, ConnectionStates, Role) -> - ConnectionState = - tls_record:pending_connection_state(ConnectionStates, read), - SecParams = ConnectionState#connection_state.security_parameters, - #security_parameters{prf_algorithm = PrfAlgo, - client_random = ClientRandom, - server_random = ServerRandom} = SecParams, - try master_secret(Version, - calc_master_secret(Version,PrfAlgo,PremasterSecret, - ClientRandom, ServerRandom), - SecParams, ConnectionStates, Role) - catch - exit:Reason -> - Report = io_lib:format("Master secret calculation failed" - " due to ~p", [Reason]), - error_logger:error_report(Report), - ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE) - end. - --spec next_protocol(binary()) -> #next_protocol{}. - -next_protocol(SelectedProtocol) -> - #next_protocol{selected_protocol = SelectedProtocol}. - -%%-------------------------------------------------------------------- --spec finished(tls_version(), client | server, integer(), binary(), tls_handshake_history()) -> - #finished{}. -%% -%% Description: Creates a handshake finished message -%%------------------------------------------------------------------- -finished(Version, Role, PrfAlgo, MasterSecret, {Handshake, _}) -> % use the current handshake - #finished{verify_data = - calc_finished(Version, Role, PrfAlgo, MasterSecret, Handshake)}. - -%%-------------------------------------------------------------------- --spec verify_connection(tls_version(), #finished{}, client | server, integer(), binary(), - tls_handshake_history()) -> verified | #alert{}. -%% -%% Description: Checks the ssl handshake finished message to verify -%% the connection. -%%------------------------------------------------------------------- -verify_connection(Version, #finished{verify_data = Data}, - Role, PrfAlgo, MasterSecret, {_, Handshake}) -> - %% use the previous hashes - case calc_finished(Version, Role, PrfAlgo, MasterSecret, Handshake) of - Data -> - verified; - _ -> - ?ALERT_REC(?FATAL, ?DECRYPT_ERROR) - end. -%%-------------------------------------------------------------------- --spec server_hello_done() -> #server_hello_done{}. -%% -%% Description: Creates a server hello done message. -%%-------------------------------------------------------------------- -server_hello_done() -> - #server_hello_done{}. - %%-------------------------------------------------------------------- -spec encode_handshake(tls_handshake(), tls_version()) -> iolist(). %% -%% Description: Encode a handshake packet to binary +%% Description: Encode a handshake packet %%--------------------------------------------------------------------x encode_handshake(Package, Version) -> - {MsgType, Bin} = enc_hs(Package, Version), + {MsgType, Bin} = enc_handshake(Package, Version), Len = byte_size(Bin), [MsgType, ?uint24(Len), Bin]. @@ -591,30 +167,6 @@ get_tls_handshake(Version, Data, <<>>) -> get_tls_handshake(Version, Data, Buffer) -> get_tls_handshake_aux(Version, list_to_binary([Buffer, Data]), []). -%%-------------------------------------------------------------------- --spec decode_client_key(binary(), key_algo(), tls_version()) -> - #encrypted_premaster_secret{} - | #client_diffie_hellman_public{} - | #client_ec_diffie_hellman_public{} - | #client_psk_identity{} - | #client_dhe_psk_identity{} - | #client_rsa_psk_identity{} - | #client_srp_public{}. -%% -%% Description: Decode client_key data and return appropriate type -%%-------------------------------------------------------------------- -decode_client_key(ClientKey, Type, Version) -> - dec_client_key(ClientKey, key_exchange_alg(Type), Version). - -%%-------------------------------------------------------------------- --spec decode_server_key(binary(), key_algo(), tls_version()) -> - #server_key_params{}. -%% -%% Description: Decode server_key data and return appropriate type -%%-------------------------------------------------------------------- -decode_server_key(ServerKey, Type, Version) -> - dec_server_key(ServerKey, key_exchange_alg(Type), Version). - %%-------------------------------------------------------------------- -spec init_handshake_history() -> tls_handshake_history(). @@ -645,442 +197,21 @@ update_handshake_history(Handshake, % special-case SSL2 client hello update_handshake_history({Handshake0, _Prev}, Data) -> {[Data|Handshake0], Handshake0}. -%%-------------------------------------------------------------------- --spec decrypt_premaster_secret(binary(), #'RSAPrivateKey'{}) -> binary(). - -%% -%% Description: Public key decryption using the private key. -%%-------------------------------------------------------------------- -decrypt_premaster_secret(Secret, RSAPrivateKey) -> - try public_key:decrypt_private(Secret, RSAPrivateKey, - [{rsa_pad, rsa_pkcs1_padding}]) - catch - _:_ -> - io:format("decrypt_premaster_secret error"), - throw(?ALERT_REC(?FATAL, ?DECRYPT_ERROR)) - end. - -%%-------------------------------------------------------------------- --spec server_key_exchange_hash(md5sha | md5 | sha | sha224 |sha256 | sha384 | sha512, binary()) -> binary(). -%% -%% Description: Calculate server key exchange hash -%%-------------------------------------------------------------------- -server_key_exchange_hash(md5sha, Value) -> - MD5 = crypto:hash(md5, Value), - SHA = crypto:hash(sha, Value), - <>; - -server_key_exchange_hash(Hash, Value) -> - crypto:hash(Hash, Value). - -%%-------------------------------------------------------------------- --spec prf(tls_version(), binary(), binary(), [binary()], non_neg_integer()) -> - {ok, binary()} | {error, undefined}. -%% -%% Description: use the TLS PRF to generate key material -%%-------------------------------------------------------------------- -prf({3,0}, _, _, _, _) -> - {error, undefined}; -prf({3,1}, Secret, Label, Seed, WantedLength) -> - {ok, ssl_tls1:prf(?MD5SHA, Secret, Label, Seed, WantedLength)}; -prf({3,_N}, Secret, Label, Seed, WantedLength) -> - {ok, ssl_tls1:prf(?SHA256, Secret, Label, Seed, WantedLength)}. - - -%%-------------------------------------------------------------------- --spec select_hashsign(#hash_sign_algos{}| undefined, undefined | term()) -> - [{atom(), atom()}] | undefined. - -%% -%% Description: -%%-------------------------------------------------------------------- -select_hashsign(_, undefined) -> - {null, anon}; -select_hashsign(undefined, Cert) -> - #'OTPCertificate'{tbsCertificate = TBSCert} = public_key:pkix_decode_cert(Cert, otp), - #'OTPSubjectPublicKeyInfo'{algorithm = {_,Algo, _}} = TBSCert#'OTPTBSCertificate'.subjectPublicKeyInfo, - select_cert_hashsign(undefined, Algo, {undefined, undefined}); -select_hashsign(#hash_sign_algos{hash_sign_algos = HashSigns}, Cert) -> - #'OTPCertificate'{tbsCertificate = TBSCert} =public_key:pkix_decode_cert(Cert, otp), - #'OTPSubjectPublicKeyInfo'{algorithm = {_,Algo, _}} = TBSCert#'OTPTBSCertificate'.subjectPublicKeyInfo, - DefaultHashSign = {_, Sign} = select_cert_hashsign(undefined, Algo, {undefined, undefined}), - case lists:filter(fun({sha, dsa}) -> - true; - ({_, dsa}) -> - false; - ({Hash, S}) when S == Sign -> - ssl_cipher:is_acceptable_hash(Hash, proplists:get_value(hashs, crypto:supports())); - (_) -> - false - end, HashSigns) of - [] -> - DefaultHashSign; - [HashSign| _] -> - HashSign - end. -%%-------------------------------------------------------------------- --spec select_cert_hashsign(#hash_sign_algos{}| undefined, oid(), tls_version()) -> - [{atom(), atom()}]. - -%% -%% Description: -%%-------------------------------------------------------------------- -select_cert_hashsign(HashSign, _, {Major, Minor}) when HashSign =/= undefined andalso Major >= 3 andalso Minor >= 3 -> - HashSign; -select_cert_hashsign(undefined,?'id-ecPublicKey', _) -> - {sha, ecdsa}; -select_cert_hashsign(undefined, ?rsaEncryption, _) -> - {md5sha, rsa}; -select_cert_hashsign(undefined, ?'id-dsa', _) -> - {sha, dsa}. - %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- get_tls_handshake_aux(Version, <>, Acc) -> Raw = <>, - H = dec_hs(Version, Type, Body), + H = decode_handshake(Version, Type, Body), get_tls_handshake_aux(Version, Rest, [{H,Raw} | Acc]); get_tls_handshake_aux(_Version, Data, Acc) -> {lists:reverse(Acc), Data}. -path_validation_alert({bad_cert, cert_expired}) -> - ?ALERT_REC(?FATAL, ?CERTIFICATE_EXPIRED); -path_validation_alert({bad_cert, invalid_issuer}) -> - ?ALERT_REC(?FATAL, ?BAD_CERTIFICATE); -path_validation_alert({bad_cert, invalid_signature}) -> - ?ALERT_REC(?FATAL, ?BAD_CERTIFICATE); -path_validation_alert({bad_cert, name_not_permitted}) -> - ?ALERT_REC(?FATAL, ?BAD_CERTIFICATE); -path_validation_alert({bad_cert, unknown_critical_extension}) -> - ?ALERT_REC(?FATAL, ?UNSUPPORTED_CERTIFICATE); -path_validation_alert({bad_cert, cert_revoked}) -> - ?ALERT_REC(?FATAL, ?CERTIFICATE_REVOKED); -path_validation_alert({bad_cert, selfsigned_peer}) -> - ?ALERT_REC(?FATAL, ?BAD_CERTIFICATE); -path_validation_alert({bad_cert, unknown_ca}) -> - ?ALERT_REC(?FATAL, ?UNKNOWN_CA); -path_validation_alert(_) -> - ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE). - -select_session(Hello, Port, Session, Version, - #ssl_options{ciphers = UserSuites} = SslOpts, Cache, CacheCb, Cert) -> - SuggestedSessionId = Hello#client_hello.session_id, - {SessionId, Resumed} = ssl_session:server_id(Port, SuggestedSessionId, - SslOpts, Cert, - Cache, CacheCb), - Suites = available_suites(Cert, UserSuites, Version), - case Resumed of - undefined -> - CipherSuite = select_cipher_suite(Hello#client_hello.cipher_suites, Suites), - Compressions = Hello#client_hello.compression_methods, - Compression = select_compression(Compressions), - {new, Session#session{session_id = SessionId, - cipher_suite = CipherSuite, - compression_method = Compression}}; - _ -> - {resumed, Resumed} - end. - -available_suites(UserSuites, Version) -> - case UserSuites of - [] -> - ssl_cipher:suites(Version); - _ -> - UserSuites - end. - -available_suites(ServerCert, UserSuites, Version) -> - ssl_cipher:filter(ServerCert, available_suites(UserSuites, Version)). - -cipher_suites(Suites, false) -> - [?TLS_EMPTY_RENEGOTIATION_INFO_SCSV | Suites]; -cipher_suites(Suites, true) -> - Suites. - -srp_user(#ssl_options{srp_identity = {UserName, _}}) -> - #srp{username = UserName}; -srp_user(_) -> - undefined. - -renegotiation_info(client, _, false) -> - #renegotiation_info{renegotiated_connection = undefined}; -renegotiation_info(server, ConnectionStates, false) -> - CS = tls_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(client, ConnectionStates, true) -> - CS = tls_record:current_connection_state(ConnectionStates, read), - case CS#connection_state.secure_renegotiation of - true -> - Data = CS#connection_state.client_verify_data, - #renegotiation_info{renegotiated_connection = Data}; - false -> - #renegotiation_info{renegotiated_connection = undefined} - end; - -renegotiation_info(server, ConnectionStates, true) -> - CS = tls_record:current_connection_state(ConnectionStates, read), - case CS#connection_state.secure_renegotiation of - true -> - CData = CS#connection_state.client_verify_data, - SData =CS#connection_state.server_verify_data, - #renegotiation_info{renegotiated_connection = <>}; - false -> - #renegotiation_info{renegotiated_connection = undefined} - end. - -decode_next_protocols({next_protocol_negotiation, Protocols}) -> - decode_next_protocols(Protocols, []). -decode_next_protocols(<<>>, Acc) -> - lists:reverse(Acc); -decode_next_protocols(<>, Acc) -> - case Len of - 0 -> - {error, invalid_next_protocols}; - _ -> - decode_next_protocols(Rest, [Protocol|Acc]) - end; -decode_next_protocols(_Bytes, _Acc) -> - {error, invalid_next_protocols}. - -next_protocol_extension_allowed(NextProtocolSelector, Renegotiating) -> - NextProtocolSelector =/= undefined andalso not Renegotiating. - -handle_next_protocol_on_server(#client_hello{next_protocol_negotiation = undefined}, _Renegotiation, _SslOpts) -> - undefined; - -handle_next_protocol_on_server(#client_hello{next_protocol_negotiation = {next_protocol_negotiation, <<>>}}, - false, #ssl_options{next_protocols_advertised = Protocols}) -> - Protocols; - -handle_next_protocol_on_server(_Hello, _Renegotiation, _SSLOpts) -> - ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE). % unexpected next protocol extension - -handle_next_protocol(#server_hello{next_protocol_negotiation = undefined}, - _NextProtocolSelector, _Renegotiating) -> - undefined; - -handle_next_protocol(#server_hello{next_protocol_negotiation = Protocols}, - NextProtocolSelector, Renegotiating) -> - - case next_protocol_extension_allowed(NextProtocolSelector, Renegotiating) of - true -> - select_next_protocol(decode_next_protocols(Protocols), NextProtocolSelector); - false -> - ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE) % unexpected next protocol extension - end. - -select_next_protocol({error, _Reason}, _NextProtocolSelector) -> - ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE); -select_next_protocol(Protocols, NextProtocolSelector) -> - case NextProtocolSelector(Protocols) of - ?NO_PROTOCOL -> - ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE); - Protocol when is_binary(Protocol) -> - Protocol - end. - -default_ecc_extensions(Version) -> - CryptoSupport = proplists:get_value(public_keys, crypto:supports()), - case proplists:get_bool(ecdh, CryptoSupport) of - true -> - EcPointFormats = #ec_point_formats{ec_point_format_list = [?ECPOINT_UNCOMPRESSED]}, - EllipticCurves = #elliptic_curves{elliptic_curve_list = ssl_tls1:ecc_curves(Version)}, - {EcPointFormats, EllipticCurves}; - _ -> - {undefined, undefined} - end. - -handle_ecc_extensions(Version, EcPointFormats0, EllipticCurves0) -> - CryptoSupport = proplists:get_value(public_keys, crypto:supports()), - case proplists:get_bool(ecdh, CryptoSupport) of - true -> - EcPointFormats1 = handle_ecc_point_fmt_extension(EcPointFormats0), - EllipticCurves1 = handle_ecc_curves_extension(Version, EllipticCurves0), - {EcPointFormats1, EllipticCurves1}; - _ -> - {undefined, undefined} - end. - -handle_ecc_point_fmt_extension(undefined) -> - undefined; -handle_ecc_point_fmt_extension(_) -> - #ec_point_formats{ec_point_format_list = [?ECPOINT_UNCOMPRESSED]}. - -handle_ecc_curves_extension(_Version, undefined) -> - undefined; -handle_ecc_curves_extension(Version, _) -> - #elliptic_curves{elliptic_curve_list = ssl_tls1:ecc_curves(Version)}. - -handle_renegotiation_info(_, #renegotiation_info{renegotiated_connection = ?byte(0)}, - ConnectionStates, false, _, _) -> - {ok, tls_record:set_renegotiation_flag(true, ConnectionStates)}; - -handle_renegotiation_info(server, undefined, ConnectionStates, _, _, CipherSuites) -> - case is_member(?TLS_EMPTY_RENEGOTIATION_INFO_SCSV, CipherSuites) of - true -> - {ok, tls_record:set_renegotiation_flag(true, ConnectionStates)}; - false -> - {ok, tls_record:set_renegotiation_flag(false, ConnectionStates)} - end; - -handle_renegotiation_info(_, undefined, ConnectionStates, false, _, _) -> - {ok, tls_record:set_renegotiation_flag(false, ConnectionStates)}; - -handle_renegotiation_info(client, #renegotiation_info{renegotiated_connection = ClientServerVerify}, - ConnectionStates, true, _, _) -> - CS = tls_record:current_connection_state(ConnectionStates, read), - CData = CS#connection_state.client_verify_data, - SData = CS#connection_state.server_verify_data, - case <> == ClientServerVerify of - true -> - {ok, ConnectionStates}; - false -> - ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE) - end; -handle_renegotiation_info(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 = tls_record:current_connection_state(ConnectionStates, read), - Data = CS#connection_state.client_verify_data, - case Data == ClientVerify of - true -> - {ok, ConnectionStates}; - false -> - ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE) - end - end; - -handle_renegotiation_info(client, undefined, ConnectionStates, true, SecureRenegotation, _) -> - handle_renegotiation_info(ConnectionStates, SecureRenegotation); - -handle_renegotiation_info(server, undefined, ConnectionStates, true, SecureRenegotation, CipherSuites) -> - case is_member(?TLS_EMPTY_RENEGOTIATION_INFO_SCSV, CipherSuites) of - true -> - ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE); - false -> - handle_renegotiation_info(ConnectionStates, SecureRenegotation) - end. - -handle_renegotiation_info(ConnectionStates, SecureRenegotation) -> - CS = tls_record:current_connection_state(ConnectionStates, read), - case {SecureRenegotation, CS#connection_state.secure_renegotiation} of - {_, true} -> - ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE); - {true, false} -> - ?ALERT_REC(?FATAL, ?NO_RENEGOTIATION); - {false, false} -> - {ok, ConnectionStates} - end. - -%% Update pending connection states with parameters exchanged via -%% hello messages -%% NOTE : Role is the role of the receiver of the hello message -%% currently being processed. -hello_pending_connection_states(Role, Version, CipherSuite, Random, Compression, - ConnectionStates) -> - ReadState = - tls_record:pending_connection_state(ConnectionStates, read), - WriteState = - tls_record:pending_connection_state(ConnectionStates, write), - - NewReadSecParams = - hello_security_parameters(Role, Version, ReadState, CipherSuite, - Random, Compression), - - NewWriteSecParams = - hello_security_parameters(Role, Version, WriteState, CipherSuite, - Random, Compression), - - tls_record:update_security_params(NewReadSecParams, - NewWriteSecParams, - ConnectionStates). - -hello_security_parameters(client, Version, ConnectionState, CipherSuite, Random, - Compression) -> - SecParams = ConnectionState#connection_state.security_parameters, - NewSecParams = ssl_cipher:security_parameters(Version, CipherSuite, SecParams), - NewSecParams#security_parameters{ - server_random = Random, - compression_algorithm = Compression - }; - -hello_security_parameters(server, Version, ConnectionState, CipherSuite, Random, - Compression) -> - SecParams = ConnectionState#connection_state.security_parameters, - NewSecParams = ssl_cipher:security_parameters(Version, CipherSuite, SecParams), - NewSecParams#security_parameters{ - client_random = Random, - compression_algorithm = Compression - }. - -select_version(ClientVersion, Versions) -> - ServerVersion = tls_record:highest_protocol_version(Versions), - tls_record:lowest_protocol_version(ClientVersion, ServerVersion). - -select_cipher_suite([], _) -> - no_suite; -select_cipher_suite([Suite | ClientSuites], SupportedSuites) -> - case is_member(Suite, SupportedSuites) of - true -> - Suite; - false -> - select_cipher_suite(ClientSuites, SupportedSuites) - end. - -is_member(Suite, SupportedSuites) -> - lists:member(Suite, SupportedSuites). - -select_compression(_CompressionMetodes) -> - ?NULL. - -master_secret(Version, MasterSecret, #security_parameters{ - client_random = ClientRandom, - server_random = ServerRandom, - hash_size = HashSize, - prf_algorithm = PrfAlgo, - key_material_length = KML, - expanded_key_material_length = EKML, - iv_size = IVS}, - ConnectionStates, Role) -> - {ClientWriteMacSecret, ServerWriteMacSecret, ClientWriteKey, - ServerWriteKey, ClientIV, ServerIV} = - setup_keys(Version, PrfAlgo, MasterSecret, ServerRandom, - ClientRandom, HashSize, KML, EKML, IVS), - - ConnStates1 = tls_record:set_master_secret(MasterSecret, ConnectionStates), - ConnStates2 = - tls_record:set_mac_secret(ClientWriteMacSecret, ServerWriteMacSecret, - Role, ConnStates1), - - ClientCipherState = #cipher_state{iv = ClientIV, key = ClientWriteKey}, - ServerCipherState = #cipher_state{iv = ServerIV, key = ServerWriteKey}, - {MasterSecret, - tls_record:set_pending_cipher_state(ConnStates2, ClientCipherState, - ServerCipherState, Role)}. - - -dec_hs(_, ?NEXT_PROTOCOL, <>) -> - #next_protocol{selected_protocol = SelectedProtocol}; - -dec_hs(_, ?HELLO_REQUEST, <<>>) -> - #hello_request{}; - %% Client hello v2. %% 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. -dec_hs(_Version, ?CLIENT_HELLO, <>) -> - DecodedExtensions = dec_hello_extensions(Extensions), - RenegotiationInfo = proplists:get_value(renegotiation_info, DecodedExtensions, undefined), - SRP = proplists:get_value(srp, DecodedExtensions, undefined), - HashSigns = proplists:get_value(hash_signs, DecodedExtensions, undefined), - EllipticCurves = proplists:get_value(elliptic_curves, DecodedExtensions, - undefined), - NextProtocolNegotiation = proplists:get_value(next_protocol_negotiation, DecodedExtensions, undefined), + DecodedExtensions = ssl_handshake:decode_hello_extensions({client, Extensions}), #client_hello{ client_version = {Major,Minor}, random = Random, session_id = Session_ID, - cipher_suites = from_2bytes(CipherSuites), + cipher_suites = ssl_handshake:decode_suites('2_bytes', CipherSuites), compression_methods = Comp_methods, - renegotiation_info = RenegotiationInfo, - srp = SRP, - hash_signs = HashSigns, - elliptic_curves = EllipticCurves, - next_protocol_negotiation = NextProtocolNegotiation + extensions = DecodedExtensions }; -dec_hs(_Version, ?SERVER_HELLO, <>) -> - #server_hello{ - server_version = {Major,Minor}, - random = Random, - session_id = Session_ID, - cipher_suite = Cipher_suite, - compression_method = Comp_method, - renegotiation_info = undefined, - hash_signs = undefined, - elliptic_curves = undefined}; - -dec_hs(_Version, ?SERVER_HELLO, <>) -> - - HelloExtensions = dec_hello_extensions(Extensions, []), - RenegotiationInfo = proplists:get_value(renegotiation_info, HelloExtensions, - undefined), - HashSigns = proplists:get_value(hash_signs, HelloExtensions, - undefined), - EllipticCurves = proplists:get_value(elliptic_curves, HelloExtensions, - undefined), - NextProtocolNegotiation = proplists:get_value(next_protocol_negotiation, HelloExtensions, undefined), - - #server_hello{ - server_version = {Major,Minor}, - random = Random, - session_id = Session_ID, - cipher_suite = Cipher_suite, - compression_method = Comp_method, - renegotiation_info = RenegotiationInfo, - hash_signs = HashSigns, - elliptic_curves = EllipticCurves, - next_protocol_negotiation = NextProtocolNegotiation}; -dec_hs(_Version, ?CERTIFICATE, <>) -> - #certificate{asn1_certificates = certs_to_list(ASN1Certs)}; -dec_hs(_Version, ?SERVER_KEY_EXCHANGE, Keys) -> - #server_key_exchange{exchange_keys = Keys}; -dec_hs({Major, Minor}, ?CERTIFICATE_REQUEST, - <>) - when Major == 3, Minor >= 3 -> - HashSignAlgos = [{ssl_cipher:hash_algorithm(Hash), ssl_cipher:sign_algorithm(Sign)} || - <> <= HashSigns], - #certificate_request{certificate_types = CertTypes, - hashsign_algorithms = #hash_sign_algos{hash_sign_algos = HashSignAlgos}, - certificate_authorities = CertAuths}; -dec_hs(_Version, ?CERTIFICATE_REQUEST, - <>) -> - #certificate_request{certificate_types = CertTypes, - certificate_authorities = CertAuths}; -dec_hs(_Version, ?SERVER_HELLO_DONE, <<>>) -> - #server_hello_done{}; -dec_hs({Major, Minor}, ?CERTIFICATE_VERIFY,<>) - when Major == 3, Minor >= 3 -> - #certificate_verify{hashsign_algorithm = hashsign_dec(HashSign), signature = Signature}; -dec_hs(_Version, ?CERTIFICATE_VERIFY,<>)-> - #certificate_verify{signature = Signature}; -dec_hs(_Version, ?CLIENT_KEY_EXCHANGE, PKEPMS) -> - #client_key_exchange{exchange_keys = PKEPMS}; -dec_hs(_Version, ?FINISHED, VerifyData) -> - #finished{verify_data = VerifyData}; -dec_hs(_, _, _) -> - throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE)). - -dec_client_key(PKEPMS, ?KEY_EXCHANGE_RSA, {3, 0}) -> - #encrypted_premaster_secret{premaster_secret = PKEPMS}; -dec_client_key(<>, ?KEY_EXCHANGE_RSA, _) -> - #encrypted_premaster_secret{premaster_secret = PKEPMS}; -dec_client_key(<<>>, ?KEY_EXCHANGE_DIFFIE_HELLMAN, _) -> - throw(?ALERT_REC(?FATAL, ?UNSUPPORTED_CERTIFICATE)); -dec_client_key(<>, - ?KEY_EXCHANGE_DIFFIE_HELLMAN, _) -> - #client_diffie_hellman_public{dh_public = DH_Y}; -dec_client_key(<<>>, ?KEY_EXCHANGE_EC_DIFFIE_HELLMAN, _) -> - throw(?ALERT_REC(?FATAL, ?UNSUPPORTED_CERTIFICATE)); -dec_client_key(<>, - ?KEY_EXCHANGE_EC_DIFFIE_HELLMAN, _) -> - #client_ec_diffie_hellman_public{dh_public = DH_Y}; -dec_client_key(<>, - ?KEY_EXCHANGE_PSK, _) -> - #client_psk_identity{identity = Id}; -dec_client_key(<>, - ?KEY_EXCHANGE_DHE_PSK, _) -> - #client_dhe_psk_identity{identity = Id, dh_public = DH_Y}; -dec_client_key(<>, - ?KEY_EXCHANGE_RSA_PSK, {3, 0}) -> - #client_rsa_psk_identity{identity = Id, exchange_keys = #encrypted_premaster_secret{premaster_secret = PKEPMS}}; -dec_client_key(<>, - ?KEY_EXCHANGE_RSA_PSK, _) -> - #client_rsa_psk_identity{identity = Id, exchange_keys = #encrypted_premaster_secret{premaster_secret = PKEPMS}}; -dec_client_key(<>, - ?KEY_EXCHANGE_SRP, _) -> - #client_srp_public{srp_a = A}. - -dec_ske_params(Len, Keys, Version) -> - <> = Keys, - dec_ske_signature(Params, Signature, Version). - -dec_ske_signature(Params, <>, {Major, Minor}) - when Major >= 3, Minor >= 3 -> - HashSign = {ssl_cipher:hash_algorithm(HashAlgo), ssl_cipher:sign_algorithm(SignAlgo)}, - {Params, HashSign, <<>>}; -dec_ske_signature(Params, <>, {Major, Minor}) - when Major >= 3, Minor >= 3 -> - HashSign = {ssl_cipher:hash_algorithm(HashAlgo), ssl_cipher:sign_algorithm(SignAlgo)}, - {Params, HashSign, Signature}; -dec_ske_signature(Params, <<>>, _) -> - {Params, {null, anon}, <<>>}; -dec_ske_signature(Params, <>, _) -> - {Params, {null, anon}, <<>>}; -dec_ske_signature(Params, <>, _) -> - {Params, undefined, Signature}; -dec_ske_signature(_, _, _) -> - throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE)). - -dec_server_key(<> = KeyStruct, - ?KEY_EXCHANGE_DIFFIE_HELLMAN, Version) -> - Params = #server_dh_params{dh_p = P, dh_g = G, dh_y = Y}, - {BinMsg, HashSign, Signature} = dec_ske_params(PLen + GLen + YLen + 6, KeyStruct, Version), - #server_key_params{params = Params, - params_bin = BinMsg, - hashsign = HashSign, - signature = Signature}; -%% ECParameters with named_curve -%% TODO: explicit curve -dec_server_key(<> = KeyStruct, - ?KEY_EXCHANGE_EC_DIFFIE_HELLMAN, Version) -> - Params = #server_ecdh_params{curve = {namedCurve, ssl_tls1:enum_to_oid(CurveID)}, - public = ECPoint}, - {BinMsg, HashSign, Signature} = dec_ske_params(PointLen + 4, KeyStruct, Version), - #server_key_params{params = Params, - params_bin = BinMsg, - hashsign = HashSign, - signature = Signature}; -dec_server_key(<> = KeyStruct, - KeyExchange, Version) - when KeyExchange == ?KEY_EXCHANGE_PSK; KeyExchange == ?KEY_EXCHANGE_RSA_PSK -> - Params = #server_psk_params{ - hint = PskIdentityHint}, - {BinMsg, HashSign, Signature} = dec_ske_params(Len + 2, KeyStruct, Version), - #server_key_params{params = Params, - params_bin = BinMsg, - hashsign = HashSign, - signature = Signature}; -dec_server_key(<> = KeyStruct, - ?KEY_EXCHANGE_DHE_PSK, Version) -> - DHParams = #server_dh_params{dh_p = P, dh_g = G, dh_y = Y}, - Params = #server_dhe_psk_params{ - hint = IdentityHint, - dh_params = DHParams}, - {BinMsg, HashSign, Signature} = dec_ske_params(Len + PLen + GLen + YLen + 8, KeyStruct, Version), - #server_key_params{params = Params, - params_bin = BinMsg, - hashsign = HashSign, - signature = Signature}; -dec_server_key(<> = KeyStruct, - ?KEY_EXCHANGE_SRP, Version) -> - Params = #server_srp_params{srp_n = N, srp_g = G, srp_s = S, srp_b = B}, - {BinMsg, HashSign, Signature} = dec_ske_params(NLen + GLen + SLen + BLen + 7, KeyStruct, Version), - #server_key_params{params = Params, - params_bin = BinMsg, - hashsign = HashSign, - signature = Signature}; -dec_server_key(_, _, _) -> - throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE)). +decode_handshake(Version, Tag, Msg) -> + ssl_handshake:decode_handshake(Version, Tag, Msg). -dec_hello_extensions(<<>>) -> - []; -dec_hello_extensions(<>) -> - dec_hello_extensions(Extensions, []); -dec_hello_extensions(_) -> - []. - -dec_hello_extensions(<<>>, Acc) -> - Acc; -dec_hello_extensions(<>, Acc) -> - Prop = {next_protocol_negotiation, #next_protocol_negotiation{extension_data = ExtensionData}}, - dec_hello_extensions(Rest, [Prop | Acc]); -dec_hello_extensions(<>, Acc) -> - RenegotiateInfo = case Len of - 1 -> % Initial handshake - Info; % should be <<0>> will be matched in handle_renegotiation_info - _ -> - VerifyLen = Len - 1, - <> = Info, - VerifyInfo - end, - dec_hello_extensions(Rest, [{renegotiation_info, - #renegotiation_info{renegotiated_connection = RenegotiateInfo}} | Acc]); - -dec_hello_extensions(<>, Acc) - when Len == SRPLen + 2 -> - dec_hello_extensions(Rest, [{srp, - #srp{username = SRP}} | Acc]); - -dec_hello_extensions(<>, Acc) -> - SignAlgoListLen = Len - 2, - <> = ExtData, - HashSignAlgos = [{ssl_cipher:hash_algorithm(Hash), ssl_cipher:sign_algorithm(Sign)} || - <> <= SignAlgoList], - dec_hello_extensions(Rest, [{hash_signs, - #hash_sign_algos{hash_sign_algos = HashSignAlgos}} | Acc]); - -dec_hello_extensions(<>, Acc) -> - <> = ExtData, - EllipticCurves = [ssl_tls1:enum_to_oid(X) || <> <= EllipticCurveList], - dec_hello_extensions(Rest, [{elliptic_curves, - #elliptic_curves{elliptic_curve_list = EllipticCurves}} | Acc]); - -dec_hello_extensions(<>, Acc) -> - <> = ExtData, - ECPointFormats = binary_to_list(ECPointFormatList), - dec_hello_extensions(Rest, [{ec_point_formats, - #ec_point_formats{ec_point_format_list = ECPointFormats}} | Acc]); - -%% Ignore data following the ClientHello (i.e., -%% extensions) if not understood. - -dec_hello_extensions(<>, Acc) -> - dec_hello_extensions(Rest, Acc); -%% This theoretically should not happen if the protocol is followed, but if it does it is ignored. -dec_hello_extensions(_, Acc) -> - Acc. - -encrypted_premaster_secret(Secret, RSAPublicKey) -> - try - PreMasterSecret = public_key:encrypt_public(Secret, RSAPublicKey, - [{rsa_pad, - rsa_pkcs1_padding}]), - #encrypted_premaster_secret{premaster_secret = PreMasterSecret} - catch - _:_-> - throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE)) - end. - -%% encode/decode stream of certificate data to/from list of certificate data -certs_to_list(ASN1Certs) -> - certs_to_list(ASN1Certs, []). - -certs_to_list(<>, Acc) -> - certs_to_list(Rest, [Cert | Acc]); -certs_to_list(<<>>, Acc) -> - lists:reverse(Acc, []). - -certs_from_list(ACList) -> - list_to_binary([begin - CertLen = byte_size(Cert), - <> - end || Cert <- ACList]). - -enc_hs(#next_protocol{selected_protocol = SelectedProtocol}, _Version) -> - PaddingLength = 32 - ((byte_size(SelectedProtocol) + 2) rem 32), - - {?NEXT_PROTOCOL, <>}; -enc_hs(#hello_request{}, _Version) -> +enc_handshake(#hello_request{}, _Version) -> {?HELLO_REQUEST, <<>>}; -enc_hs(#client_hello{client_version = {Major, Minor}, +enc_handshake(#client_hello{client_version = {Major, Minor}, random = Random, session_id = SessionID, cipher_suites = CipherSuites, compression_methods = CompMethods, - renegotiation_info = RenegotiationInfo, - srp = SRP, - hash_signs = HashSigns, - ec_point_formats = EcPointFormats, - elliptic_curves = EllipticCurves, - next_protocol_negotiation = NextProtocolNegotiation}, _Version) -> + extensions = HelloExtensions}, _Version) -> SIDLength = byte_size(SessionID), BinCompMethods = list_to_binary(CompMethods), CmLength = byte_size(BinCompMethods), BinCipherSuites = list_to_binary(CipherSuites), CsLength = byte_size(BinCipherSuites), - Extensions0 = hello_extensions(RenegotiationInfo, SRP, NextProtocolNegotiation) - ++ ec_hello_extensions(lists:map(fun ssl_cipher:suite_definition/1, CipherSuites), EcPointFormats) - ++ ec_hello_extensions(lists:map(fun ssl_cipher:suite_definition/1, CipherSuites), EllipticCurves), - Extensions1 = if - Major == 3, Minor >=3 -> Extensions0 ++ hello_extensions(HashSigns); - true -> Extensions0 - end, - ExtensionsBin = enc_hello_extensions(Extensions1), + ExtensionsBin = ssl_handshake:encode_hello_extensions(HelloExtensions), - {?CLIENT_HELLO, <>}; - -enc_hs(#server_hello{server_version = {Major, Minor}, - random = Random, - session_id = Session_ID, - cipher_suite = CipherSuite, - compression_method = Comp_method, - renegotiation_info = RenegotiationInfo, - ec_point_formats = EcPointFormats, - elliptic_curves = EllipticCurves, - next_protocol_negotiation = NextProtocolNegotiation}, _Version) -> - SID_length = byte_size(Session_ID), - CipherSuites = [ssl_cipher:suite_definition(CipherSuite)], - Extensions = hello_extensions(RenegotiationInfo, NextProtocolNegotiation) - ++ ec_hello_extensions(CipherSuites, EcPointFormats) - ++ ec_hello_extensions(CipherSuites, EllipticCurves), - ExtensionsBin = enc_hello_extensions(Extensions), - {?SERVER_HELLO, <>}; -enc_hs(#certificate{asn1_certificates = ASN1CertList}, _Version) -> - ASN1Certs = certs_from_list(ASN1CertList), - ACLen = erlang:iolist_size(ASN1Certs), - {?CERTIFICATE, <>}; -enc_hs(#server_key_exchange{exchange_keys = Keys}, _Version) -> - {?SERVER_KEY_EXCHANGE, Keys}; -enc_hs(#server_key_params{params_bin = Keys, hashsign = HashSign, - signature = Signature}, Version) -> - EncSign = enc_sign(HashSign, Signature, Version), - {?SERVER_KEY_EXCHANGE, <>}; -enc_hs(#certificate_request{certificate_types = CertTypes, - hashsign_algorithms = #hash_sign_algos{hash_sign_algos = HashSignAlgos}, - certificate_authorities = CertAuths}, - {Major, Minor}) - when Major == 3, Minor >= 3 -> - HashSigns= << <<(ssl_cipher:hash_algorithm(Hash)):8, (ssl_cipher:sign_algorithm(Sign)):8>> || - {Hash, Sign} <- HashSignAlgos >>, - CertTypesLen = byte_size(CertTypes), - HashSignsLen = byte_size(HashSigns), - CertAuthsLen = byte_size(CertAuths), - {?CERTIFICATE_REQUEST, - <> - }; -enc_hs(#certificate_request{certificate_types = CertTypes, - certificate_authorities = CertAuths}, - _Version) -> - CertTypesLen = byte_size(CertTypes), - CertAuthsLen = byte_size(CertAuths), - {?CERTIFICATE_REQUEST, - <> - }; -enc_hs(#server_hello_done{}, _Version) -> - {?SERVER_HELLO_DONE, <<>>}; -enc_hs(#client_key_exchange{exchange_keys = ExchangeKeys}, Version) -> - {?CLIENT_KEY_EXCHANGE, enc_cke(ExchangeKeys, Version)}; -enc_hs(#certificate_verify{signature = BinSig, hashsign_algorithm = HashSign}, Version) -> - EncSig = enc_sign(HashSign, BinSig, Version), - {?CERTIFICATE_VERIFY, EncSig}; -enc_hs(#finished{verify_data = VerifyData}, _Version) -> - {?FINISHED, VerifyData}. - -enc_cke(#encrypted_premaster_secret{premaster_secret = PKEPMS},{3, 0}) -> - PKEPMS; -enc_cke(#encrypted_premaster_secret{premaster_secret = PKEPMS}, _) -> - PKEPMSLen = byte_size(PKEPMS), - <>; -enc_cke(#client_diffie_hellman_public{dh_public = DHPublic}, _) -> - Len = byte_size(DHPublic), - <>; -enc_cke(#client_ec_diffie_hellman_public{dh_public = DHPublic}, _) -> - Len = byte_size(DHPublic), - <>; -enc_cke(#client_psk_identity{identity = undefined}, _) -> - Id = <<"psk_identity">>, - Len = byte_size(Id), - <>; -enc_cke(#client_psk_identity{identity = Id}, _) -> - Len = byte_size(Id), - <>; -enc_cke(Identity = #client_dhe_psk_identity{identity = undefined}, Version) -> - enc_cke(Identity#client_dhe_psk_identity{identity = <<"psk_identity">>}, Version); -enc_cke(#client_dhe_psk_identity{identity = Id, dh_public = DHPublic}, _) -> - Len = byte_size(Id), - DHLen = byte_size(DHPublic), - <>; -enc_cke(Identity = #client_rsa_psk_identity{identity = undefined}, Version) -> - enc_cke(Identity#client_rsa_psk_identity{identity = <<"psk_identity">>}, Version); -enc_cke(#client_rsa_psk_identity{identity = Id, exchange_keys = ExchangeKeys}, Version) -> - EncPMS = enc_cke(ExchangeKeys, Version), - Len = byte_size(Id), - <>; -enc_cke(#client_srp_public{srp_a = A}, _) -> - Len = byte_size(A), - <>. - -enc_server_key(#server_dh_params{dh_p = P, dh_g = G, dh_y = Y}) -> - PLen = byte_size(P), - GLen = byte_size(G), - YLen = byte_size(Y), - <>; -enc_server_key(#server_ecdh_params{curve = {namedCurve, ECCurve}, public = ECPubKey}) -> - %%TODO: support arbitrary keys - KLen = size(ECPubKey), - <>; -enc_server_key(#server_psk_params{hint = PskIdentityHint}) -> - Len = byte_size(PskIdentityHint), - <>; -enc_server_key(Params = #server_dhe_psk_params{hint = undefined}) -> - enc_server_key(Params#server_dhe_psk_params{hint = <<>>}); -enc_server_key(#server_dhe_psk_params{ - hint = PskIdentityHint, - dh_params = #server_dh_params{dh_p = P, dh_g = G, dh_y = Y}}) -> - Len = byte_size(PskIdentityHint), - PLen = byte_size(P), - GLen = byte_size(G), - YLen = byte_size(Y), - <>; -enc_server_key(#server_srp_params{srp_n = N, srp_g = G, srp_s = S, srp_b = B}) -> - NLen = byte_size(N), - GLen = byte_size(G), - SLen = byte_size(S), - BLen = byte_size(B), - <>. - -enc_sign({_, anon}, _Sign, _Version) -> - <<>>; -enc_sign({HashAlg, SignAlg}, Signature, _Version = {Major, Minor}) - when Major == 3, Minor >= 3-> - SignLen = byte_size(Signature), - HashSign = hashsign_enc(HashAlg, SignAlg), - <>; -enc_sign(_HashSign, Sign, _Version) -> - SignLen = byte_size(Sign), - <>. - - -ec_hello_extensions(CipherSuites, #elliptic_curves{} = Info) -> - case advertises_ec_ciphers(CipherSuites) of - true -> - [Info]; - false -> - [] - end; -ec_hello_extensions(CipherSuites, #ec_point_formats{} = Info) -> - case advertises_ec_ciphers(CipherSuites) of - true -> - [Info]; - false -> - [] - end; -ec_hello_extensions(_, undefined) -> - []. - -hello_extensions(RenegotiationInfo, NextProtocolNegotiation) -> - hello_extensions(RenegotiationInfo) ++ next_protocol_extension(NextProtocolNegotiation). - -hello_extensions(RenegotiationInfo, SRP, NextProtocolNegotiation) -> - hello_extensions(RenegotiationInfo) - ++ hello_extensions(SRP) - ++ next_protocol_extension(NextProtocolNegotiation). - -advertises_ec_ciphers([]) -> - false; -advertises_ec_ciphers([{ecdh_ecdsa, _,_,_} | _]) -> - true; -advertises_ec_ciphers([{ecdhe_ecdsa, _,_,_} | _]) -> - true; -advertises_ec_ciphers([{ecdh_rsa, _,_,_} | _]) -> - true; -advertises_ec_ciphers([{ecdhe_rsa, _,_,_} | _]) -> - true; -advertises_ec_ciphers([{ecdh_anon, _,_,_} | _]) -> - true; -advertises_ec_ciphers([_| Rest]) -> - advertises_ec_ciphers(Rest). - -%% Renegotiation info -hello_extensions(#renegotiation_info{renegotiated_connection = undefined}) -> - []; -hello_extensions(#renegotiation_info{} = Info) -> - [Info]; -hello_extensions(#srp{} = Info) -> - [Info]; -hello_extensions(#hash_sign_algos{} = Info) -> - [Info]; -hello_extensions(undefined) -> - []. + ?UINT16(CsLength), BinCipherSuites/binary, + ?BYTE(CmLength), BinCompMethods/binary, ExtensionsBin/binary>>}; -next_protocol_extension(undefined) -> - []; -next_protocol_extension(#next_protocol_negotiation{} = Info) -> - [Info]. +enc_handshake(HandshakeMsg, Version) -> + ssl_handshake:encode_handshake(HandshakeMsg, Version). -enc_hello_extensions(Extensions) -> - enc_hello_extensions(Extensions, <<>>). -enc_hello_extensions([], <<>>) -> - <<>>; -enc_hello_extensions([], Acc) -> - Size = byte_size(Acc), - <>; -enc_hello_extensions([#next_protocol_negotiation{extension_data = ExtensionData} | Rest], Acc) -> - Len = byte_size(ExtensionData), - enc_hello_extensions(Rest, <>); -enc_hello_extensions([#renegotiation_info{renegotiated_connection = ?byte(0) = Info} | Rest], Acc) -> - Len = byte_size(Info), - enc_hello_extensions(Rest, <>); - -enc_hello_extensions([#renegotiation_info{renegotiated_connection = Info} | Rest], Acc) -> - InfoLen = byte_size(Info), - Len = InfoLen +1, - enc_hello_extensions(Rest, <>); -enc_hello_extensions([#elliptic_curves{elliptic_curve_list = EllipticCurves} | Rest], Acc) -> - EllipticCurveList = << <<(ssl_tls1:oid_to_enum(X)):16>> || X <- EllipticCurves>>, - ListLen = byte_size(EllipticCurveList), - Len = ListLen + 2, - enc_hello_extensions(Rest, <>); -enc_hello_extensions([#ec_point_formats{ec_point_format_list = ECPointFormats} | Rest], Acc) -> - ECPointFormatList = list_to_binary(ECPointFormats), - ListLen = byte_size(ECPointFormatList), - Len = ListLen + 1, - enc_hello_extensions(Rest, <>); -enc_hello_extensions([#srp{username = UserName} | Rest], Acc) -> - SRPLen = byte_size(UserName), - Len = SRPLen + 2, - enc_hello_extensions(Rest, <>); -enc_hello_extensions([#hash_sign_algos{hash_sign_algos = HashSignAlgos} | Rest], Acc) -> - SignAlgoList = << <<(ssl_cipher:hash_algorithm(Hash)):8, (ssl_cipher:sign_algorithm(Sign)):8>> || - {Hash, Sign} <- HashSignAlgos >>, - ListLen = byte_size(SignAlgoList), - Len = ListLen + 2, - enc_hello_extensions(Rest, <>). - -encode_client_protocol_negotiation(undefined, _) -> - undefined; -encode_client_protocol_negotiation(_, false) -> - #next_protocol_negotiation{extension_data = <<>>}; -encode_client_protocol_negotiation(_, _) -> - undefined. - -from_3bytes(Bin3) -> - from_3bytes(Bin3, []). - -from_3bytes(<<>>, Acc) -> - lists:reverse(Acc); -from_3bytes(<>, Acc) -> - from_3bytes(Rest, [?uint16(N) | Acc]). - -from_2bytes(Bin2) -> - from_2bytes(Bin2, []). - -from_2bytes(<<>>, Acc) -> - lists:reverse(Acc); -from_2bytes(<>, Acc) -> - from_2bytes(Rest, [?uint16(N) | Acc]). - -certificate_types({KeyExchange, _, _, _}) - when KeyExchange == rsa; - KeyExchange == dhe_dss; - KeyExchange == dhe_rsa; - KeyExchange == ecdhe_rsa -> - <>; - -certificate_types({KeyExchange, _, _, _}) - when KeyExchange == dh_ecdsa; - KeyExchange == dhe_ecdsa -> - <>; - -certificate_types(_) -> - <>. - -hashsign_dec(<>) -> - {ssl_cipher:hash_algorithm(HashAlgo), ssl_cipher:sign_algorithm(SignAlgo)}. - -hashsign_enc(HashAlgo, SignAlgo) -> - Hash = ssl_cipher:hash_algorithm(HashAlgo), - Sign = ssl_cipher:sign_algorithm(SignAlgo), - <>. - -certificate_authorities(CertDbHandle, CertDbRef) -> - Authorities = certificate_authorities_from_db(CertDbHandle, CertDbRef), - Enc = fun(#'OTPCertificate'{tbsCertificate=TBSCert}) -> - OTPSubj = TBSCert#'OTPTBSCertificate'.subject, - DNEncodedBin = public_key:pkix_encode('Name', OTPSubj, otp), - DNEncodedLen = byte_size(DNEncodedBin), - <> - end, - list_to_binary([Enc(Cert) || {_, Cert} <- Authorities]). - -certificate_authorities_from_db(CertDbHandle, CertDbRef) -> - ConnectionCerts = fun({{Ref, _, _}, Cert}, Acc) when Ref == CertDbRef -> - [Cert | Acc]; - (_, Acc) -> - Acc - end, - ssl_pkix_db:foldl(ConnectionCerts, [], CertDbHandle). - - -digitally_signed({3, Minor}, Hash, HashAlgo, Key) when Minor >= 3 -> - public_key:sign({digest, Hash}, HashAlgo, Key); -digitally_signed(_Version, Hash, HashAlgo, #'DSAPrivateKey'{} = Key) -> - public_key:sign({digest, Hash}, HashAlgo, Key); -digitally_signed(_Version, Hash, _HashAlgo, #'RSAPrivateKey'{} = Key) -> - public_key:encrypt_private(Hash, Key, - [{rsa_pad, rsa_pkcs1_padding}]); -digitally_signed(_Version, Hash, HashAlgo, Key) -> - public_key:sign({digest, Hash}, HashAlgo, Key). - -calc_master_secret({3,0}, _PrfAlgo, PremasterSecret, ClientRandom, ServerRandom) -> - ssl_ssl3:master_secret(PremasterSecret, ClientRandom, ServerRandom); - -calc_master_secret({3,_}, PrfAlgo, PremasterSecret, ClientRandom, ServerRandom) -> - ssl_tls1:master_secret(PrfAlgo, PremasterSecret, ClientRandom, ServerRandom). - -setup_keys({3,0}, _PrfAlgo, MasterSecret, - ServerRandom, ClientRandom, HashSize, KML, EKML, IVS) -> - ssl_ssl3:setup_keys(MasterSecret, ServerRandom, - ClientRandom, HashSize, KML, EKML, IVS); - -setup_keys({3,N}, PrfAlgo, MasterSecret, - ServerRandom, ClientRandom, HashSize, KML, _EKML, IVS) -> - ssl_tls1:setup_keys(N, PrfAlgo, MasterSecret, ServerRandom, ClientRandom, HashSize, - KML, IVS). - -calc_finished({3, 0}, Role, _PrfAlgo, MasterSecret, Handshake) -> - ssl_ssl3:finished(Role, MasterSecret, lists:reverse(Handshake)); -calc_finished({3, N}, Role, PrfAlgo, MasterSecret, Handshake) -> - ssl_tls1:finished(Role, N, PrfAlgo, MasterSecret, lists:reverse(Handshake)). - -calc_certificate_verify({3, 0}, HashAlgo, MasterSecret, Handshake) -> - ssl_ssl3:certificate_verify(HashAlgo, MasterSecret, lists:reverse(Handshake)); -calc_certificate_verify({3, N}, HashAlgo, _MasterSecret, Handshake) -> - ssl_tls1:certificate_verify(HashAlgo, N, lists:reverse(Handshake)). - -key_exchange_alg(rsa) -> - ?KEY_EXCHANGE_RSA; -key_exchange_alg(Alg) when Alg == dhe_rsa; Alg == dhe_dss; - Alg == dh_dss; Alg == dh_rsa; Alg == dh_anon -> - ?KEY_EXCHANGE_DIFFIE_HELLMAN; -key_exchange_alg(Alg) when Alg == ecdhe_rsa; Alg == ecdh_rsa; - Alg == ecdhe_ecdsa; Alg == ecdh_ecdsa; - Alg == ecdh_anon -> - ?KEY_EXCHANGE_EC_DIFFIE_HELLMAN; -key_exchange_alg(psk) -> - ?KEY_EXCHANGE_PSK; -key_exchange_alg(dhe_psk) -> - ?KEY_EXCHANGE_DHE_PSK; -key_exchange_alg(rsa_psk) -> - ?KEY_EXCHANGE_RSA_PSK; -key_exchange_alg(Alg) - when Alg == srp_rsa; Alg == srp_dss; Alg == srp_anon -> - ?KEY_EXCHANGE_SRP; -key_exchange_alg(_) -> - ?NULL. - -apply_user_fun(Fun, OtpCert, ExtensionOrError, UserState0, SslState) -> - case Fun(OtpCert, ExtensionOrError, UserState0) of - {valid, UserState} -> - {valid, {SslState, UserState}}; - {fail, _} = Fail -> - Fail; - {unknown, UserState} -> - {unknown, {SslState, UserState}} +handle_hello_extensions(Version, Type, Random, HelloExt, SslOpts, Session0, ConnectionStates0, Renegotiation) -> + try ssl_handshake:handle_client_hello_extensions(tls_record, Random, HelloExt, Version, SslOpts, + Session0, ConnectionStates0, Renegotiation) of + {Session, ConnectionStates, ServerHelloExt} -> + {Version, {Type, Session}, ConnectionStates, ServerHelloExt} + catch throw:Alert -> + Alert end. -handle_hello_extensions(#client_hello{random = Random, - cipher_suites = CipherSuites, - renegotiation_info = Info, - srp = SRP, - ec_point_formats = EcPointFormats0, - elliptic_curves = EllipticCurves0} = Hello, Version, - #ssl_options{secure_renegotiate = SecureRenegotation} = Opts, - Session0, ConnectionStates0, Renegotiation) -> - Session = handle_srp_extension(SRP, Session0), - ConnectionStates = handle_renegotiation_extension(Version, Info, Random, Session, ConnectionStates0, - Renegotiation, SecureRenegotation, CipherSuites), - ProtocolsToAdvertise = handle_next_protocol_extension(Hello, Renegotiation, Opts), - {EcPointFormats, EllipticCurves} = handle_ecc_extensions(Version, EcPointFormats0, EllipticCurves0), - %%TODO make extensions compund data structure - {Session, ConnectionStates, ProtocolsToAdvertise, EcPointFormats, EllipticCurves}. - -handle_renegotiation_extension(Version, Info, Random, #session{cipher_suite = CipherSuite, - compression_method = Compression}, - ConnectionStates0, Renegotiation, SecureRenegotation, CipherSuites) -> - case handle_renegotiation_info(server, Info, ConnectionStates0, - Renegotiation, SecureRenegotation, - CipherSuites) of - {ok, ConnectionStates1} -> - hello_pending_connection_states(server, - Version, - CipherSuite, - Random, - Compression, - ConnectionStates1); +handle_hello_extensions(Version, SessionId, Random, CipherSuite, + Compression, HelloExt, SslOpt, ConnectionStates0, Renegotiation) -> + case ssl_handshake:handle_server_hello_extensions(tls_record, Random, CipherSuite, + Compression, HelloExt, Version, + SslOpt, ConnectionStates0, Renegotiation) of #alert{} = Alert -> - throw(Alert) + Alert; + {ConnectionStates, Protocol} -> + {Version, SessionId, ConnectionStates, Protocol} end. -handle_next_protocol_extension(Hello, Renegotiation, SslOpts)-> - case handle_next_protocol_on_server(Hello, Renegotiation, SslOpts) of - #alert{} = Alert -> - throw(Alert); - ProtocolsToAdvertise -> - ProtocolsToAdvertise - end. - -handle_srp_extension(undefined, Session) -> - Session; -handle_srp_extension(#srp{username = Username}, Session) -> - Session#session{srp_username = Username}. - -int_to_bin(I) -> - L = (length(integer_to_list(I, 16)) + 1) div 2, - <>. - --define(TLSEXT_SIGALG_RSA(MD), {MD, rsa}). --define(TLSEXT_SIGALG_DSA(MD), {MD, dsa}). --define(TLSEXT_SIGALG_ECDSA(MD), {MD, ecdsa}). - --define(TLSEXT_SIGALG(MD), ?TLSEXT_SIGALG_ECDSA(MD), ?TLSEXT_SIGALG_RSA(MD)). - -advertised_hash_signs({Major, Minor}) when Major >= 3 andalso Minor >= 3 -> - HashSigns = [?TLSEXT_SIGALG(sha512), - ?TLSEXT_SIGALG(sha384), - ?TLSEXT_SIGALG(sha256), - ?TLSEXT_SIGALG(sha224), - ?TLSEXT_SIGALG(sha), - ?TLSEXT_SIGALG_DSA(sha), - ?TLSEXT_SIGALG_RSA(md5)], - CryptoSupport = crypto:supports(), - HasECC = proplists:get_bool(ecdsa, proplists:get_value(public_keys, CryptoSupport)), - Hashs = proplists:get_value(hashs, CryptoSupport), - #hash_sign_algos{hash_sign_algos = - lists:filter(fun({Hash, ecdsa}) -> HasECC andalso proplists:get_bool(Hash, Hashs); - ({Hash, _}) -> proplists:get_bool(Hash, Hashs) end, HashSigns)}; -advertised_hash_signs(_) -> - undefined. diff --git a/lib/ssl/src/tls_handshake.hrl b/lib/ssl/src/tls_handshake.hrl index abf1b5abb6..dbe930cb90 100644 --- a/lib/ssl/src/tls_handshake.hrl +++ b/lib/ssl/src/tls_handshake.hrl @@ -34,12 +34,9 @@ cipher_suites, % cipher_suites<2..2^16-1> compression_methods, % compression_methods<1..2^8-1>, %% Extensions - renegotiation_info, - hash_signs, % supported combinations of hashes/signature algos - next_protocol_negotiation = undefined, % [binary()] - srp, - ec_point_formats, - elliptic_curves + extensions }). +-type tls_handshake() :: #client_hello{} | ssl_handshake(). + -endif. % -ifdef(tls_handshake). -- cgit v1.2.3 From fb6ac178ac437fcc04f1675df75b0583c1d24ad7 Mon Sep 17 00:00:00 2001 From: Andreas Schultz Date: Wed, 12 Jun 2013 16:26:24 +0200 Subject: ssl: Add DTLS record primitives This code is to 99 % written by Andreas Schultz only some small changes to start integrating with OTPs DTLS solution. --- lib/ssl/src/dtls_record.erl | 322 ++++++++++++++++++++++++++++++++++++++++++++ lib/ssl/src/dtls_record.hrl | 12 ++ lib/ssl/src/ssl_record.hrl | 29 ++-- lib/ssl/src/tls_record.hrl | 6 + 4 files changed, 351 insertions(+), 18 deletions(-) (limited to 'lib/ssl/src') diff --git a/lib/ssl/src/dtls_record.erl b/lib/ssl/src/dtls_record.erl index 2469a7d26c..98c60f599c 100644 --- a/lib/ssl/src/dtls_record.erl +++ b/lib/ssl/src/dtls_record.erl @@ -16,3 +16,325 @@ %% %% %CopyrightEnd% -module(dtls_record). + +-include("dtls_record.hrl"). +-include("ssl_internal.hrl"). +-include("ssl_alert.hrl"). + +-export([init_connection_state_seq/2, current_connection_state_epoch/2, + set_connection_state_by_epoch/3, connection_state_by_epoch/3]). + +%% Handling of incoming data +-export([get_dtls_records/2]). + +%% Misc. +-export([protocol_version/1, lowest_protocol_version/2, + highest_protocol_version/1, supported_protocol_versions/0, + is_acceptable_version/2]). + +%%-------------------------------------------------------------------- +-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{previous_read = CS}, Epoch, read) + when CS#connection_state.epoch == Epoch -> + CS; +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{previous_write = CS}, Epoch, write) + 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{previous_read = CS}, + NewCS = #connection_state{epoch = Epoch}, read) + when CS#connection_state.epoch == Epoch -> + ConnectionStates0#connection_states{previous_read = NewCS}; + +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{previous_write = CS}, + NewCS = #connection_state{epoch = Epoch}, write) + when CS#connection_state.epoch == Epoch -> + ConnectionStates0#connection_states{previous_write = 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}. + + +%%-------------------------------------------------------------------- +-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('dtlsv1.2') -> + {254, 253}; +protocol_version(dtlsv1) -> + {254, 255}; +protocol_version({254, 253}) -> + 'dtlsv1.2'; +protocol_version({254, 255}) -> + dtlsv1. +%%-------------------------------------------------------------------- +-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([Ver | Vers]) -> + 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 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()]. +%% +%% Description: Protocol versions supported +%%-------------------------------------------------------------------- +supported_protocol_versions() -> + Fun = fun(Version) -> + protocol_version(Version) + end, + case application:get_env(ssl, dtls_protocol_version) of + undefined -> + lists:map(Fun, supported_protocol_versions([])); + {ok, []} -> + lists:map(Fun, supported_protocol_versions([])); + {ok, Vsns} when is_list(Vsns) -> + supported_protocol_versions(Vsns); + {ok, Vsn} -> + supported_protocol_versions([Vsn]) + end. + +supported_protocol_versions([]) -> + Vsns = supported_connection_protocol_versions([]), + application:set_env(ssl, dtls_protocol_version, Vsns), + Vsns; + +supported_protocol_versions([_|_] = Vsns) -> + Vsns. + +supported_connection_protocol_versions([]) -> + ?ALL_DATAGRAM_SUPPORTED_VERSIONS. + +%%-------------------------------------------------------------------- +-spec is_acceptable_version(tls_version(), Supported :: [tls_version()]) -> boolean(). +%% +%% Description: ssl version 2 is not acceptable security risks are too big. +%% +%%-------------------------------------------------------------------- +is_acceptable_version(Version, Versions) -> + lists:member(Version, Versions). + +%%-------------------------------------------------------------------- +-spec clear_previous_epoch(#connection_states{}) -> + #connection_states{}. +%% +%% Description: Advance to min_read_epoch to the current read epoch. +%%-------------------------------------------------------------------- +clear_previous_epoch(States = + #connection_states{current_read = Current}) -> + States#connection_states{min_read_epoch = Current#connection_state.epoch}. + + + +decipher(TLS=#ssl_tls{type=Type, version=Version={254, _}, + epoch = Epoch, sequence = SeqNo, + 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 = hash_with_seqno(CS1, Type, Version, Epoch, SeqNo, TLength, T), + case is_correct_mac(Mac, MacHash) of + true -> + {TLS#ssl_tls{fragment = T}, CS1}; + false -> + ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC) + end; + #alert{} = Alert -> + Alert + end. + +hash_with_seqno(#connection_state{mac_secret = MacSecret, + security_parameters = + SecPars}, + Type, Version = {254, _}, + Epoch, SeqNo, Length, Fragment) -> + mac_hash(Version, + SecPars#security_parameters.mac_algorithm, + MacSecret, (Epoch bsl 48) + SeqNo, Type, + Length, Fragment). + +hash_and_bump_seqno(#connection_state{epoch = Epoch, + sequence_number = SeqNo, + mac_secret = MacSecret, + security_parameters = + SecPars} = CS0, + Type, Version = {254, _}, Length, Fragment) -> + Hash = mac_hash(Version, + SecPars#security_parameters.mac_algorithm, + MacSecret, (Epoch bsl 48) + SeqNo, Type, + Length, Fragment), + {Hash, CS0#connection_state{sequence_number = SeqNo+1}}. diff --git a/lib/ssl/src/dtls_record.hrl b/lib/ssl/src/dtls_record.hrl index e935d84bdf..c50550cc28 100644 --- a/lib/ssl/src/dtls_record.hrl +++ b/lib/ssl/src/dtls_record.hrl @@ -28,6 +28,18 @@ -include("ssl_record.hrl"). %% Common TLS and DTLS records and Constantes +-define(INITIAL_BYTES, 5). + +-record(connection_states, { + min_read_epoch, + previous_read, + current_read, + pending_read, + previous_write, + current_write, + pending_write + }). + %% Used to handle tls_plain_text, tls_compressed and tls_cipher_text -record(ssl_tls, { diff --git a/lib/ssl/src/ssl_record.hrl b/lib/ssl/src/ssl_record.hrl index 2fd17f9c35..8a6a211553 100644 --- a/lib/ssl/src/ssl_record.hrl +++ b/lib/ssl/src/ssl_record.hrl @@ -29,12 +29,17 @@ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%% Connection states - RFC 4346 section 6.1 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - --record(connection_states, { - current_read, - pending_read, - current_write, - pending_write +-record(connection_state, { + security_parameters, + compression_state, + cipher_state, + mac_secret, + epoch, %% Only used by DTLS + sequence_number, + %% RFC 5746 + secure_renegotiation, + client_verify_data, + server_verify_data }). -record(security_parameters, { @@ -56,18 +61,6 @@ exportable % boolean }). --record(connection_state, { - security_parameters, - compression_state, - cipher_state, - mac_secret, - sequence_number, - %% RFC 5746 - secure_renegotiation, - client_verify_data, - server_verify_data - }). - -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_record.hrl b/lib/ssl/src/tls_record.hrl index c9350fa137..0e3552bafa 100644 --- a/lib/ssl/src/tls_record.hrl +++ b/lib/ssl/src/tls_record.hrl @@ -27,6 +27,12 @@ -define(tls_record, true). -include("ssl_record.hrl"). %% Common TLS and DTLS records and Constantes +-record(connection_states, { + current_read, + pending_read, + current_write, + pending_write + }). %% Used to handle tls_plain_text, tls_compressed and tls_cipher_text -- cgit v1.2.3 From b9a31f24053c84d9a7ffa4281bc11f47b3be5905 Mon Sep 17 00:00:00 2001 From: Ingela Anderton Andin Date: Tue, 18 Jun 2013 12:30:38 +0200 Subject: ssl: DTLS record handling Also refactor so that TLS and DTLS can have common functions when possible. --- lib/ssl/src/Makefile | 7 +- lib/ssl/src/dtls_record.erl | 51 ++--- lib/ssl/src/dtls_record.hrl | 10 - lib/ssl/src/dtls_v1.erl | 2 +- lib/ssl/src/ssl.app.src | 8 +- lib/ssl/src/ssl_cipher.erl | 4 +- lib/ssl/src/ssl_handshake.erl | 12 +- lib/ssl/src/ssl_record.erl | 89 +++++++++ lib/ssl/src/ssl_record.hrl | 7 + lib/ssl/src/ssl_ssl2.erl | 37 ---- lib/ssl/src/ssl_ssl3.erl | 203 -------------------- lib/ssl/src/ssl_tls1.erl | 432 ------------------------------------------ lib/ssl/src/ssl_v2.erl | 37 ++++ lib/ssl/src/ssl_v3.erl | 203 ++++++++++++++++++++ lib/ssl/src/tls_handshake.erl | 19 +- lib/ssl/src/tls_record.erl | 31 +-- lib/ssl/src/tls_record.hrl | 7 - lib/ssl/src/tls_v1.erl | 432 ++++++++++++++++++++++++++++++++++++++++++ 18 files changed, 822 insertions(+), 769 deletions(-) create mode 100644 lib/ssl/src/ssl_record.erl delete mode 100644 lib/ssl/src/ssl_ssl2.erl delete mode 100644 lib/ssl/src/ssl_ssl3.erl delete mode 100644 lib/ssl/src/ssl_tls1.erl create mode 100644 lib/ssl/src/ssl_v2.erl create mode 100644 lib/ssl/src/ssl_v3.erl create mode 100644 lib/ssl/src/tls_v1.erl (limited to 'lib/ssl/src') diff --git a/lib/ssl/src/Makefile b/lib/ssl/src/Makefile index a467b3f608..a5af451244 100644 --- a/lib/ssl/src/Makefile +++ b/lib/ssl/src/Makefile @@ -65,9 +65,10 @@ MODULES= \ ssl_socket \ tls_record \ dtls_record \ - ssl_ssl2 \ - ssl_ssl3 \ - ssl_tls1 \ + ssl_record \ + ssl_v2 \ + ssl_v3 \ + tls_v1 \ ssl_tls_dist_proxy INTERNAL_HRL_FILES = \ diff --git a/lib/ssl/src/dtls_record.erl b/lib/ssl/src/dtls_record.erl index 98c60f599c..daadae0725 100644 --- a/lib/ssl/src/dtls_record.erl +++ b/lib/ssl/src/dtls_record.erl @@ -30,7 +30,7 @@ %% Misc. -export([protocol_version/1, lowest_protocol_version/2, highest_protocol_version/1, supported_protocol_versions/0, - is_acceptable_version/2]). + is_acceptable_version/2, cipher/4, decipher/2]). %%-------------------------------------------------------------------- -spec init_connection_state_seq(tls_version(), #connection_states{}) -> @@ -71,25 +71,18 @@ current_connection_state_epoch(#connection_states{current_write = Current}, %% Description: Returns the instance of the connection_state record %% that is defined by the Epoch. %%-------------------------------------------------------------------- -connection_state_by_epoch(#connection_states{previous_read = CS}, Epoch, read) - when CS#connection_state.epoch == Epoch -> - CS; 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{previous_write = CS}, Epoch, write) - 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. @@ -97,12 +90,6 @@ connection_state_by_epoch(#connection_states{pending_write = CS}, Epoch, write) %% Description: Returns the instance of the connection_state record %% that is defined by the Epoch. %%-------------------------------------------------------------------- -set_connection_state_by_epoch(ConnectionStates0 = - #connection_states{previous_read = CS}, - NewCS = #connection_state{epoch = Epoch}, read) - when CS#connection_state.epoch == Epoch -> - ConnectionStates0#connection_states{previous_read = NewCS}; - set_connection_state_by_epoch(ConnectionStates0 = #connection_states{current_read = CS}, NewCS = #connection_state{epoch = Epoch}, read) @@ -115,12 +102,6 @@ set_connection_state_by_epoch(ConnectionStates0 = when CS#connection_state.epoch == Epoch -> ConnectionStates0#connection_states{pending_read = NewCS}; -set_connection_state_by_epoch(ConnectionStates0 = - #connection_states{previous_write = CS}, - NewCS = #connection_state{epoch = Epoch}, write) - when CS#connection_state.epoch == Epoch -> - ConnectionStates0#connection_states{previous_write = NewCS}; - set_connection_state_by_epoch(ConnectionStates0 = #connection_states{current_write = CS}, NewCS = #connection_state{epoch = Epoch}, write) @@ -133,7 +114,6 @@ set_connection_state_by_epoch(ConnectionStates0 = when CS#connection_state.epoch == Epoch -> ConnectionStates0#connection_states{pending_write = NewCS}. - %%-------------------------------------------------------------------- -spec protocol_version(tls_atom_version() | tls_version()) -> tls_version() | tls_atom_version(). @@ -283,20 +263,21 @@ supported_connection_protocol_versions([]) -> is_acceptable_version(Version, Versions) -> lists:member(Version, Versions). -%%-------------------------------------------------------------------- --spec clear_previous_epoch(#connection_states{}) -> - #connection_states{}. -%% -%% Description: Advance to min_read_epoch to the current read epoch. -%%-------------------------------------------------------------------- -clear_previous_epoch(States = - #connection_states{current_read = Current}) -> - States#connection_states{min_read_epoch = Current#connection_state.epoch}. - +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={254, _}, - epoch = Epoch, sequence = SeqNo, + epoch = Epoch, record_seq = SeqNo, fragment=Fragment}, CS0) -> SP = CS0#connection_state.security_parameters, BCA = SP#security_parameters.bulk_cipher_algorithm, @@ -307,7 +288,7 @@ decipher(TLS=#ssl_tls{type=Type, version=Version={254, _}, CS1 = CS0#connection_state{cipher_state = CipherS1}, TLength = size(T), MacHash = hash_with_seqno(CS1, Type, Version, Epoch, SeqNo, TLength, T), - case is_correct_mac(Mac, MacHash) of + case ssl_record:is_correct_mac(Mac, MacHash) of true -> {TLS#ssl_tls{fragment = T}, CS1}; false -> @@ -338,3 +319,7 @@ hash_and_bump_seqno(#connection_state{epoch = Epoch, MacSecret, (Epoch bsl 48) + SeqNo, Type, Length, Fragment), {Hash, CS0#connection_state{sequence_number = SeqNo+1}}. + +mac_hash(Version, MacAlg, MacSecret, SeqNo, Type, Length, Fragment) -> + dtls_v1:mac_hash(MacAlg, MacSecret, SeqNo, Type, Version, + Length, Fragment). diff --git a/lib/ssl/src/dtls_record.hrl b/lib/ssl/src/dtls_record.hrl index c50550cc28..b72c14c2d7 100644 --- a/lib/ssl/src/dtls_record.hrl +++ b/lib/ssl/src/dtls_record.hrl @@ -30,16 +30,6 @@ -define(INITIAL_BYTES, 5). --record(connection_states, { - min_read_epoch, - previous_read, - current_read, - pending_read, - previous_write, - current_write, - pending_write - }). - %% 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 930998b460..a9fcf575af 100644 --- a/lib/ssl/src/dtls_v1.erl +++ b/lib/ssl/src/dtls_v1.erl @@ -25,7 +25,7 @@ -spec suites(Minor:: 253|255) -> [cipher_suite()]. suites(Minor) -> - tls_v1:suites(corresponding_minor_tls_version(Minor)); + tls_v1:suites(corresponding_minor_tls_version(Minor)). mac_hash(Version, MacAlg, MacSecret, SeqNo, Type, Length, Fragment) -> tls_v1:mac_hash(MacAlg, MacSecret, SeqNo, Type, Version, diff --git a/lib/ssl/src/ssl.app.src b/lib/ssl/src/ssl.app.src index 0a2ae92e9d..677f5fdd07 100644 --- a/lib/ssl/src/ssl.app.src +++ b/lib/ssl/src/ssl.app.src @@ -20,14 +20,14 @@ inet_tls_dist, ssl_tls_dist_proxy, ssl_dist_sup, - ssl_tls1, - ssl_ssl3, - ssl_ssl2, + tls_v1, + ssl_v3, + ssl_v2, ssl_session, ssl_session_cache_api, ssl_session_cache, ssl_socket, - %%ssl_record, + ssl_record, ssl_manager, ssl_handshake, ssl_connection_sup, diff --git a/lib/ssl/src/ssl_cipher.erl b/lib/ssl/src/ssl_cipher.erl index 09aad8e414..d958b74e9f 100644 --- a/lib/ssl/src/ssl_cipher.erl +++ b/lib/ssl/src/ssl_cipher.erl @@ -195,9 +195,9 @@ block_decipher(Fun, #cipher_state{key=Key, iv=IV} = CipherState0, %% Description: Returns a list of supported cipher suites. %%-------------------------------------------------------------------- suites({3, 0}) -> - ssl_ssl3:suites(); + ssl_v3:suites(); suites({3, N}) -> - ssl_tls1:suites(N). + tls_v1:suites(N). %%-------------------------------------------------------------------- -spec anonymous_suites() -> [cipher_suite()]. diff --git a/lib/ssl/src/ssl_handshake.erl b/lib/ssl/src/ssl_handshake.erl index 0f5d5def48..5084c46571 100644 --- a/lib/ssl/src/ssl_handshake.erl +++ b/lib/ssl/src/ssl_handshake.erl @@ -575,6 +575,7 @@ encode_hello_extensions([#renegotiation_info{renegotiated_connection = Info} | R encode_hello_extensions(Rest, <>); encode_hello_extensions([#elliptic_curves{elliptic_curve_list = EllipticCurves} | Rest], Acc) -> + EllipticCurveList = << <<(tls_v1:oid_to_enum(X)):16>> || X <- EllipticCurves>>, ListLen = byte_size(EllipticCurveList), Len = ListLen + 2, @@ -752,7 +753,7 @@ dec_server_key(<> = KeyStruct, ?KEY_EXCHANGE_EC_DIFFIE_HELLMAN, Version) -> - Params = #server_ecdh_params{curve = {namedCurve, ssl_tls1:enum_to_oid(CurveID)}, + Params = #server_ecdh_params{curve = {namedCurve, tls_v1:enum_to_oid(CurveID)}, public = ECPoint}, {BinMsg, HashSign, Signature} = dec_server_key_params(PointLen + 4, KeyStruct, Version), #server_key_params{params = Params, @@ -1445,10 +1446,11 @@ dec_hello_extensions(<> = ExtData, EllipticCurves = [tls_v1:enum_to_oid(X) || <> <= EllipticCurveList], - dec_hello_extensions(Rest, Acc#hello_extensions{elliptic_curves = - #elliptic_curves{elliptic_curve_list = - EllipticCurves}}); -dec_hello_extensions(<>, Acc) -> %%ECPointFormatListLen = Len - 1, <> = ExtData, diff --git a/lib/ssl/src/ssl_record.erl b/lib/ssl/src/ssl_record.erl new file mode 100644 index 0000000000..ac56e3ab29 --- /dev/null +++ b/lib/ssl/src/ssl_record.erl @@ -0,0 +1,89 @@ +%% +%% %CopyrightBegin% +%% +%% 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 +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% + +-module(ssl_record). + +-include("ssl_internal.hrl"). +-include("ssl_record.hrl"). + +-export([empty_connection_state/1, activate_pending_connection_state/2, is_correct_mac/2]). + +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), + <>. + +%%-------------------------------------------------------------------- +-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{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), + 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) -> + %% Next epoch is a noop for SSL/TLS only uesed by DTLS + NewCurrent = Pending#connection_state{epoch = connection_state_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 + }. + +connection_state_next_epoch(#connection_state{epoch = undefined}) -> + undefined; +connection_state_next_epoch(State) -> + State#connection_state.epoch + 1. + +is_correct_mac(Mac, Mac) -> + true; +is_correct_mac(_M,_H) -> + false. diff --git a/lib/ssl/src/ssl_record.hrl b/lib/ssl/src/ssl_record.hrl index 8a6a211553..34893ce699 100644 --- a/lib/ssl/src/ssl_record.hrl +++ b/lib/ssl/src/ssl_record.hrl @@ -42,6 +42,13 @@ server_verify_data }). +-record(connection_states, { + current_read, + pending_read, + current_write, + pending_write + }). + -record(security_parameters, { cipher_suite, connection_end, diff --git a/lib/ssl/src/ssl_ssl2.erl b/lib/ssl/src/ssl_ssl2.erl deleted file mode 100644 index a9ab6a2678..0000000000 --- a/lib/ssl/src/ssl_ssl2.erl +++ /dev/null @@ -1,37 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2007-2011. 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 -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. -%% -%% %CopyrightEnd% -%% - -%% -%%---------------------------------------------------------------------- -%% Purpose: Handles sslv2 hello as clients supporting sslv2 and higher -%% will send an sslv2 hello. -%%---------------------------------------------------------------------- - --module(ssl_ssl2). - --export([client_random/2]). - -client_random(ChallengeData, 32) -> - ChallengeData; -client_random(ChallengeData, N) when N > 32 -> - <> = ChallengeData, - NewChallengeData; -client_random(ChallengeData, N) -> - Pad = list_to_binary(lists:duplicate(N, 0)), - <>. diff --git a/lib/ssl/src/ssl_ssl3.erl b/lib/ssl/src/ssl_ssl3.erl deleted file mode 100644 index 013c27ebb5..0000000000 --- a/lib/ssl/src/ssl_ssl3.erl +++ /dev/null @@ -1,203 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2007-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 -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. -%% -%% %CopyrightEnd% -%% - -%% -%%---------------------------------------------------------------------- -%% Purpose: Handles sslv3 encryption. -%%---------------------------------------------------------------------- - --module(ssl_ssl3). - --include("ssl_cipher.hrl"). --include("ssl_internal.hrl"). --include("ssl_record.hrl"). % MD5 and SHA - --export([master_secret/3, finished/3, certificate_verify/3, - mac_hash/6, setup_keys/7, - suites/0]). --compile(inline). - -%%==================================================================== -%% Internal application API -%%==================================================================== - --spec master_secret(binary(), binary(), binary()) -> binary(). - -master_secret(PremasterSecret, ClientRandom, ServerRandom) -> - %% draft-ietf-tls-ssl-version3-00 - 6.2.2 - %% key_block = - %% MD5(master_secret + SHA(`A' + master_secret + - %% ServerHello.random + - %% ClientHello.random)) + - %% MD5(master_secret + SHA(`BB' + master_secret + - %% ServerHello.random + - %% ClientHello.random)) + - %% MD5(master_secret + SHA(`CCC' + master_secret + - %% ServerHello.random + - %% ClientHello.random)) + [...]; - Block = generate_keyblock(PremasterSecret, ClientRandom, ServerRandom, 48), - Block. - --spec finished(client | server, binary(), [binary()]) -> binary(). - -finished(Role, MasterSecret, Handshake) -> - %% draft-ietf-tls-ssl-version3-00 - 5.6.9 Finished - %% struct { - %% opaque md5_hash[16]; - %% opaque sha_hash[20]; - %% } Finished; - %% - %% md5_hash MD5(master_secret + pad2 + - %% MD5(handshake_messages + Sender + - %% master_secret + pad1)); - %% sha_hash SHA(master_secret + pad2 + - %% SHA(handshake_messages + Sender + - %% master_secret + pad1)); - Sender = get_sender(Role), - MD5 = handshake_hash(?MD5, MasterSecret, Sender, Handshake), - SHA = handshake_hash(?SHA, MasterSecret, Sender, Handshake), - <>. - --spec certificate_verify(md5sha | sha, binary(), [binary()]) -> binary(). - -certificate_verify(md5sha, MasterSecret, Handshake) -> - %% md5_hash - %% MD5(master_secret + pad_2 + - %% MD5(handshake_messages + master_secret + pad_1)); - %% sha_hash - %% SHA(master_secret + pad_2 + - %% SHA(handshake_messages + master_secret + pad_1)); - - MD5 = handshake_hash(?MD5, MasterSecret, undefined, Handshake), - SHA = handshake_hash(?SHA, MasterSecret, undefined, Handshake), - <>; - -certificate_verify(sha, MasterSecret, Handshake) -> - %% sha_hash - %% SHA(master_secret + pad_2 + - %% SHA(handshake_messages + master_secret + pad_1)); - - handshake_hash(?SHA, MasterSecret, undefined, Handshake). - --spec mac_hash(integer(), binary(), integer(), integer(), integer(), binary()) -> binary(). - -mac_hash(Method, Mac_write_secret, Seq_num, Type, Length, Fragment) -> - %% draft-ietf-tls-ssl-version3-00 - 5.2.3.1 - %% hash(MAC_write_secret + pad_2 + - %% hash(MAC_write_secret + pad_1 + seq_num + - %% SSLCompressed.type + SSLCompressed.length + - %% SSLCompressed.fragment)); - Mac = mac_hash(Method, Mac_write_secret, - [<>, Fragment]), - Mac. - --spec setup_keys(binary(), binary(), binary(), - integer(), integer(), term(), integer()) -> - {binary(), binary(), binary(), - binary(), binary(), binary()}. - -setup_keys(MasterSecret, ServerRandom, ClientRandom, HS, KML, _EKML, IVS) -> - KeyBlock = generate_keyblock(MasterSecret, ServerRandom, ClientRandom, - 2*(HS+KML+IVS)), - %% draft-ietf-tls-ssl-version3-00 - 6.2.2 - %% The key_block is partitioned as follows. - %% client_write_MAC_secret[CipherSpec.hash_size] - %% server_write_MAC_secret[CipherSpec.hash_size] - %% client_write_key[CipherSpec.key_material] - %% server_write_key[CipherSpec.key_material] - %% client_write_IV[CipherSpec.IV_size] /* non-export ciphers */ - %% server_write_IV[CipherSpec.IV_size] /* non-export ciphers */ - <> = KeyBlock, - {ClientWriteMacSecret, ServerWriteMacSecret, ClientWriteKey, - ServerWriteKey, ClientIV, ServerIV}. - --spec suites() -> [cipher_suite()]. - -suites() -> - [ - ?TLS_DHE_RSA_WITH_AES_256_CBC_SHA, - ?TLS_DHE_DSS_WITH_AES_256_CBC_SHA, - ?TLS_RSA_WITH_AES_256_CBC_SHA, - ?TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA, - ?TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA, - ?TLS_RSA_WITH_3DES_EDE_CBC_SHA, - ?TLS_DHE_RSA_WITH_AES_128_CBC_SHA, - ?TLS_DHE_DSS_WITH_AES_128_CBC_SHA, - ?TLS_RSA_WITH_AES_128_CBC_SHA, - %%?TLS_RSA_WITH_IDEA_CBC_SHA, - ?TLS_RSA_WITH_RC4_128_SHA, - ?TLS_RSA_WITH_RC4_128_MD5, - ?TLS_RSA_WITH_DES_CBC_SHA - ]. - -%%-------------------------------------------------------------------- -%%% Internal functions -%%-------------------------------------------------------------------- - -hash(?MD5, Data) -> - crypto:hash(md5, Data); -hash(?SHA, Data) -> - crypto:hash(sha, Data). - -%%pad_1(?NULL) -> -%% ""; -pad_1(?MD5) -> - <<"666666666666666666666666666666666666666666666666">>; -pad_1(?SHA) -> - <<"6666666666666666666666666666666666666666">>. -%%pad_2(?NULL) -> -%% ""; -pad_2(?MD5) -> - <<"\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\" - "\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\">>; -pad_2(?SHA) -> - <<"\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\" - "\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\">>. - -mac_hash(?NULL, _Secret, _Data) -> - <<>>; -mac_hash(Method, Secret, Data) -> - InnerHash = hash(Method, [Secret, pad_1(Method), Data]), - hash(Method, [Secret, pad_2(Method), InnerHash]). - -handshake_hash(Method, MasterSecret, undefined, Handshake) -> - InnerHash = hash(Method, [Handshake, MasterSecret, pad_1(Method)]), - hash(Method, [MasterSecret, pad_2(Method), InnerHash]); -handshake_hash(Method, MasterSecret, Sender, Handshake) -> - InnerHash = hash(Method, [Handshake, Sender, MasterSecret, pad_1(Method)]), - hash(Method, [MasterSecret, pad_2(Method), InnerHash]). - -get_sender(client) -> "CLNT"; -get_sender(server) -> "SRVR". - -generate_keyblock(MasterSecret, ServerRandom, ClientRandom, WantedLength) -> - gen(MasterSecret, [MasterSecret, ServerRandom, ClientRandom], - WantedLength, 0, $A, 1, []). - -gen(_Secret, _All, Wanted, Len, _C, _N, Acc) when Wanted =< Len -> - <> = list_to_binary(lists:reverse(Acc)), - Block; -gen(Secret, All, Wanted, Len, C, N, Acc) -> - Prefix = lists:duplicate(N, C), - SHA = crypto:hash(sha, [Prefix, All]), - MD5 = crypto:hash(md5, [Secret, SHA]), - gen(Secret, All, Wanted, Len + 16, C+1, N+1, [MD5 | Acc]). diff --git a/lib/ssl/src/ssl_tls1.erl b/lib/ssl/src/ssl_tls1.erl deleted file mode 100644 index 8ab66d0627..0000000000 --- a/lib/ssl/src/ssl_tls1.erl +++ /dev/null @@ -1,432 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2007-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 -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. -%% -%% %CopyrightEnd% -%% - -%% -%%---------------------------------------------------------------------- -%% Purpose: Handles tls1 encryption. -%%---------------------------------------------------------------------- - --module(ssl_tls1). - --include("ssl_cipher.hrl"). --include("ssl_internal.hrl"). --include("ssl_record.hrl"). - --export([master_secret/4, finished/5, certificate_verify/3, mac_hash/7, - setup_keys/8, suites/1, prf/5, - ecc_curves/1, oid_to_enum/1, enum_to_oid/1]). - -%%==================================================================== -%% Internal application API -%%==================================================================== - --spec master_secret(integer(), binary(), binary(), binary()) -> binary(). - -master_secret(PrfAlgo, PreMasterSecret, ClientRandom, ServerRandom) -> - %% RFC 2246 & 4346 && RFC 5246 - 8.1 %% master_secret = PRF(pre_master_secret, - %% "master secret", ClientHello.random + - %% ServerHello.random)[0..47]; - - prf(PrfAlgo, PreMasterSecret, <<"master secret">>, - [ClientRandom, ServerRandom], 48). - --spec finished(client | server, integer(), integer(), binary(), [binary()]) -> binary(). - -finished(Role, Version, PrfAlgo, MasterSecret, Handshake) - when Version == 1; Version == 2; PrfAlgo == ?MD5SHA -> - %% RFC 2246 & 4346 - 7.4.9. Finished - %% struct { - %% opaque verify_data[12]; - %% } Finished; - %% - %% verify_data - %% PRF(master_secret, finished_label, MD5(handshake_messages) + - %% SHA-1(handshake_messages)) [0..11]; - MD5 = crypto:hash(md5, Handshake), - SHA = crypto:hash(sha, Handshake), - prf(?MD5SHA, MasterSecret, finished_label(Role), [MD5, SHA], 12); - -finished(Role, Version, PrfAlgo, MasterSecret, Handshake) - when Version == 3 -> - %% RFC 5246 - 7.4.9. Finished - %% struct { - %% opaque verify_data[12]; - %% } Finished; - %% - %% verify_data - %% PRF(master_secret, finished_label, Hash(handshake_messages)) [0..11]; - Hash = crypto:hash(mac_algo(PrfAlgo), Handshake), - prf(PrfAlgo, MasterSecret, finished_label(Role), Hash, 12). - --spec certificate_verify(md5sha | sha, integer(), [binary()]) -> binary(). - -certificate_verify(md5sha, _Version, Handshake) -> - MD5 = crypto:hash(md5, Handshake), - SHA = crypto:hash(sha, Handshake), - <>; - -certificate_verify(HashAlgo, _Version, Handshake) -> - crypto:hash(HashAlgo, Handshake). - --spec setup_keys(integer(), integer(), binary(), binary(), binary(), integer(), - integer(), integer()) -> {binary(), binary(), binary(), - binary(), binary(), binary()}. - -setup_keys(Version, _PrfAlgo, MasterSecret, ServerRandom, ClientRandom, HashSize, - KeyMatLen, IVSize) - when Version == 1 -> - %% RFC 2246 - 6.3. Key calculation - %% key_block = PRF(SecurityParameters.master_secret, - %% "key expansion", - %% SecurityParameters.server_random + - %% SecurityParameters.client_random); - %% Then the key_block is partitioned as follows: - %% client_write_MAC_secret[SecurityParameters.hash_size] - %% server_write_MAC_secret[SecurityParameters.hash_size] - %% client_write_key[SecurityParameters.key_material_length] - %% server_write_key[SecurityParameters.key_material_length] - %% client_write_IV[SecurityParameters.IV_size] - %% server_write_IV[SecurityParameters.IV_size] - WantedLength = 2 * (HashSize + KeyMatLen + IVSize), - KeyBlock = prf(?MD5SHA, MasterSecret, "key expansion", - [ServerRandom, ClientRandom], WantedLength), - <> = KeyBlock, - {ClientWriteMacSecret, ServerWriteMacSecret, ClientWriteKey, - ServerWriteKey, ClientIV, ServerIV}; - -%% TLS v1.1 -setup_keys(Version, _PrfAlgo, MasterSecret, ServerRandom, ClientRandom, HashSize, - KeyMatLen, IVSize) - when Version == 2 -> - %% RFC 4346 - 6.3. Key calculation - %% key_block = PRF(SecurityParameters.master_secret, - %% "key expansion", - %% SecurityParameters.server_random + - %% SecurityParameters.client_random); - %% Then the key_block is partitioned as follows: - %% client_write_MAC_secret[SecurityParameters.hash_size] - %% server_write_MAC_secret[SecurityParameters.hash_size] - %% client_write_key[SecurityParameters.key_material_length] - %% server_write_key[SecurityParameters.key_material_length] - %% - %% RFC 4346 is incomplete, the client and server IVs have to - %% be generated just like for TLS 1.0 - WantedLength = 2 * (HashSize + KeyMatLen + IVSize), - KeyBlock = prf(?MD5SHA, MasterSecret, "key expansion", - [ServerRandom, ClientRandom], WantedLength), - <> = KeyBlock, - {ClientWriteMacSecret, ServerWriteMacSecret, ClientWriteKey, - ServerWriteKey, ClientIV, ServerIV}; - -%% TLS v1.2 -setup_keys(Version, PrfAlgo, MasterSecret, ServerRandom, ClientRandom, HashSize, - KeyMatLen, IVSize) - when Version == 3 -> - %% RFC 5246 - 6.3. Key calculation - %% key_block = PRF(SecurityParameters.master_secret, - %% "key expansion", - %% SecurityParameters.server_random + - %% SecurityParameters.client_random); - %% Then the key_block is partitioned as follows: - %% client_write_MAC_secret[SecurityParameters.hash_size] - %% server_write_MAC_secret[SecurityParameters.hash_size] - %% client_write_key[SecurityParameters.key_material_length] - %% server_write_key[SecurityParameters.key_material_length] - %% client_write_IV[SecurityParameters.fixed_iv_length] - %% server_write_IV[SecurityParameters.fixed_iv_length] - WantedLength = 2 * (HashSize + KeyMatLen + IVSize), - KeyBlock = prf(PrfAlgo, MasterSecret, "key expansion", - [ServerRandom, ClientRandom], WantedLength), - <> = KeyBlock, - {ClientWriteMacSecret, ServerWriteMacSecret, ClientWriteKey, - ServerWriteKey, ClientIV, ServerIV}. - --spec mac_hash(integer(), binary(), integer(), integer(), tls_version(), - integer(), binary()) -> binary(). - -mac_hash(Method, Mac_write_secret, Seq_num, Type, {Major, Minor}, - Length, Fragment) -> - %% RFC 2246 & 4346 - 6.2.3.1. - %% HMAC_hash(MAC_write_secret, seq_num + TLSCompressed.type + - %% TLSCompressed.version + TLSCompressed.length + - %% TLSCompressed.fragment)); - Mac = hmac_hash(Method, Mac_write_secret, - [<>, - Fragment]), - Mac. - --spec suites(1|2|3) -> [cipher_suite()]. - -suites(Minor) when Minor == 1; Minor == 2-> - case sufficent_ec_support() of - true -> - all_suites(Minor); - false -> - no_ec_suites(Minor) - end; - -suites(Minor) when Minor == 3 -> - case sufficent_ec_support() of - true -> - all_suites(3) ++ all_suites(2); - false -> - no_ec_suites(3) ++ no_ec_suites(2) - end. - -all_suites(Minor) when Minor == 1; Minor == 2-> - [ - ?TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, - ?TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, - ?TLS_DHE_RSA_WITH_AES_256_CBC_SHA, - ?TLS_DHE_DSS_WITH_AES_256_CBC_SHA, - ?TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA, - ?TLS_ECDH_RSA_WITH_AES_256_CBC_SHA, - ?TLS_RSA_WITH_AES_256_CBC_SHA, - - ?TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA, - ?TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, - ?TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA, - ?TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA, - ?TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA, - ?TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA, - ?TLS_RSA_WITH_3DES_EDE_CBC_SHA, - - ?TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, - ?TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, - ?TLS_DHE_RSA_WITH_AES_128_CBC_SHA, - ?TLS_DHE_DSS_WITH_AES_128_CBC_SHA, - ?TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA, - ?TLS_ECDH_RSA_WITH_AES_128_CBC_SHA, - ?TLS_RSA_WITH_AES_128_CBC_SHA, - - ?TLS_ECDHE_ECDSA_WITH_RC4_128_SHA, - ?TLS_ECDHE_RSA_WITH_RC4_128_SHA, - ?TLS_RSA_WITH_RC4_128_SHA, - ?TLS_RSA_WITH_RC4_128_MD5, - ?TLS_DHE_RSA_WITH_DES_CBC_SHA, - ?TLS_ECDH_ECDSA_WITH_RC4_128_SHA, - ?TLS_ECDH_RSA_WITH_RC4_128_SHA, - - ?TLS_RSA_WITH_DES_CBC_SHA - ]; -all_suites(3) -> - [ - ?TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384, - ?TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384, - ?TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384, - ?TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384, - - ?TLS_DHE_RSA_WITH_AES_256_CBC_SHA256, - ?TLS_DHE_DSS_WITH_AES_256_CBC_SHA256, - ?TLS_RSA_WITH_AES_256_CBC_SHA256, - - ?TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, - ?TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, - ?TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256, - ?TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256, - - ?TLS_DHE_RSA_WITH_AES_128_CBC_SHA256, - ?TLS_DHE_DSS_WITH_AES_128_CBC_SHA256, - ?TLS_RSA_WITH_AES_128_CBC_SHA256 - ]. - -no_ec_suites(Minor) when Minor == 1; Minor == 2-> - [ - ?TLS_DHE_RSA_WITH_AES_256_CBC_SHA, - ?TLS_DHE_DSS_WITH_AES_256_CBC_SHA, - ?TLS_RSA_WITH_AES_256_CBC_SHA, - ?TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA, - ?TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA, - ?TLS_RSA_WITH_3DES_EDE_CBC_SHA, - ?TLS_DHE_RSA_WITH_AES_128_CBC_SHA, - ?TLS_DHE_DSS_WITH_AES_128_CBC_SHA, - ?TLS_RSA_WITH_AES_128_CBC_SHA, - ?TLS_RSA_WITH_RC4_128_SHA, - ?TLS_RSA_WITH_RC4_128_MD5, - ?TLS_DHE_RSA_WITH_DES_CBC_SHA, - ?TLS_RSA_WITH_DES_CBC_SHA - ]; -no_ec_suites(3) -> - [ - ?TLS_DHE_RSA_WITH_AES_256_CBC_SHA256, - ?TLS_DHE_DSS_WITH_AES_256_CBC_SHA256, - ?TLS_RSA_WITH_AES_256_CBC_SHA256, - ?TLS_DHE_RSA_WITH_AES_128_CBC_SHA256, - ?TLS_DHE_DSS_WITH_AES_128_CBC_SHA256, - ?TLS_RSA_WITH_AES_128_CBC_SHA256 - ]. - -%%-------------------------------------------------------------------- -%%% Internal functions -%%-------------------------------------------------------------------- -%%%% HMAC and the Pseudorandom Functions RFC 2246 & 4346 - 5.%%%% -hmac_hash(?NULL, _, _) -> - <<>>; -hmac_hash(Alg, Key, Value) -> - crypto:hmac(mac_algo(Alg), Key, Value). - -mac_algo(?MD5) -> md5; -mac_algo(?SHA) -> sha; -mac_algo(?SHA256) -> sha256; -mac_algo(?SHA384) -> sha384; -mac_algo(?SHA512) -> sha512. - -% First, we define a data expansion function, P_hash(secret, data) that -% uses a single hash function to expand a secret and seed into an -% arbitrary quantity of output: -%% P_hash(secret, seed) = HMAC_hash(secret, A(1) + seed) + -%% HMAC_hash(secret, A(2) + seed) + -%% HMAC_hash(secret, A(3) + seed) + ... - -p_hash(Secret, Seed, WantedLength, Method) -> - p_hash(Secret, Seed, WantedLength, Method, 0, []). - -p_hash(_Secret, _Seed, WantedLength, _Method, _N, []) - when WantedLength =< 0 -> - []; -p_hash(_Secret, _Seed, WantedLength, _Method, _N, [Last | Acc]) - when WantedLength =< 0 -> - Keep = byte_size(Last) + WantedLength, - <> = Last, - list_to_binary(lists:reverse(Acc, [B])); -p_hash(Secret, Seed, WantedLength, Method, N, Acc) -> - N1 = N+1, - Bin = hmac_hash(Method, Secret, [a(N1, Secret, Seed, Method), Seed]), - p_hash(Secret, Seed, WantedLength - byte_size(Bin), Method, N1, [Bin|Acc]). - - -%% ... Where A(0) = seed -%% A(i) = HMAC_hash(secret, A(i-1)) -%% a(0, _Secret, Seed, _Method) -> -%% Seed. -%% a(N, Secret, Seed, Method) -> -%% hmac_hash(Method, Secret, a(N-1, Secret, Seed, Method)). -a(0, _Secret, Seed, _Method) -> - Seed; -a(N, Secret, Seed0, Method) -> - Seed = hmac_hash(Method, Secret, Seed0), - a(N-1, Secret, Seed, Method). - -split_secret(BinSecret) -> - %% L_S = length in bytes of secret; - %% L_S1 = L_S2 = ceil(L_S / 2); - %% The secret is partitioned into two halves (with the possibility of - %% one shared byte) as described above, S1 taking the first L_S1 bytes, - %% and S2 the last L_S2 bytes. - Length = byte_size(BinSecret), - Div = Length div 2, - EvenLength = Length - Div, - <> = BinSecret, - <<_:Div/binary, Secret2:EvenLength/binary>> = BinSecret, - {Secret1, Secret2}. - -prf(?MD5SHA, Secret, Label, Seed, WantedLength) -> - %% PRF(secret, label, seed) = P_MD5(S1, label + seed) XOR - %% P_SHA-1(S2, label + seed); - {S1, S2} = split_secret(Secret), - LS = list_to_binary([Label, Seed]), - crypto:exor(p_hash(S1, LS, WantedLength, ?MD5), - p_hash(S2, LS, WantedLength, ?SHA)); - -prf(MAC, Secret, Label, Seed, WantedLength) -> - %% PRF(secret, label, seed) = P_SHA256(secret, label + seed); - LS = list_to_binary([Label, Seed]), - p_hash(Secret, LS, WantedLength, MAC). - -%%%% Misc help functions %%%% - -finished_label(client) -> - <<"client finished">>; -finished_label(server) -> - <<"server finished">>. - -%% list ECC curves in prefered order -ecc_curves(_Minor) -> - [?sect571r1,?sect571k1,?secp521r1,?sect409k1,?sect409r1, - ?secp384r1,?sect283k1,?sect283r1,?secp256k1,?secp256r1, - ?sect239k1,?sect233k1,?sect233r1,?secp224k1,?secp224r1, - ?sect193r1,?sect193r2,?secp192k1,?secp192r1,?sect163k1, - ?sect163r1,?sect163r2,?secp160k1,?secp160r1,?secp160r2]. - -%% ECC curves from draft-ietf-tls-ecc-12.txt (Oct. 17, 2005) -oid_to_enum(?sect163k1) -> 1; -oid_to_enum(?sect163r1) -> 2; -oid_to_enum(?sect163r2) -> 3; -oid_to_enum(?sect193r1) -> 4; -oid_to_enum(?sect193r2) -> 5; -oid_to_enum(?sect233k1) -> 6; -oid_to_enum(?sect233r1) -> 7; -oid_to_enum(?sect239k1) -> 8; -oid_to_enum(?sect283k1) -> 9; -oid_to_enum(?sect283r1) -> 10; -oid_to_enum(?sect409k1) -> 11; -oid_to_enum(?sect409r1) -> 12; -oid_to_enum(?sect571k1) -> 13; -oid_to_enum(?sect571r1) -> 14; -oid_to_enum(?secp160k1) -> 15; -oid_to_enum(?secp160r1) -> 16; -oid_to_enum(?secp160r2) -> 17; -oid_to_enum(?secp192k1) -> 18; -oid_to_enum(?secp192r1) -> 19; -oid_to_enum(?secp224k1) -> 20; -oid_to_enum(?secp224r1) -> 21; -oid_to_enum(?secp256k1) -> 22; -oid_to_enum(?secp256r1) -> 23; -oid_to_enum(?secp384r1) -> 24; -oid_to_enum(?secp521r1) -> 25. - -enum_to_oid(1) -> ?sect163k1; -enum_to_oid(2) -> ?sect163r1; -enum_to_oid(3) -> ?sect163r2; -enum_to_oid(4) -> ?sect193r1; -enum_to_oid(5) -> ?sect193r2; -enum_to_oid(6) -> ?sect233k1; -enum_to_oid(7) -> ?sect233r1; -enum_to_oid(8) -> ?sect239k1; -enum_to_oid(9) -> ?sect283k1; -enum_to_oid(10) -> ?sect283r1; -enum_to_oid(11) -> ?sect409k1; -enum_to_oid(12) -> ?sect409r1; -enum_to_oid(13) -> ?sect571k1; -enum_to_oid(14) -> ?sect571r1; -enum_to_oid(15) -> ?secp160k1; -enum_to_oid(16) -> ?secp160r1; -enum_to_oid(17) -> ?secp160r2; -enum_to_oid(18) -> ?secp192k1; -enum_to_oid(19) -> ?secp192r1; -enum_to_oid(20) -> ?secp224k1; -enum_to_oid(21) -> ?secp224r1; -enum_to_oid(22) -> ?secp256k1; -enum_to_oid(23) -> ?secp256r1; -enum_to_oid(24) -> ?secp384r1; -enum_to_oid(25) -> ?secp521r1. - -sufficent_ec_support() -> - CryptoSupport = crypto:supports(), - proplists:get_bool(ecdh, proplists:get_value(public_keys, CryptoSupport)). diff --git a/lib/ssl/src/ssl_v2.erl b/lib/ssl/src/ssl_v2.erl new file mode 100644 index 0000000000..07876366f1 --- /dev/null +++ b/lib/ssl/src/ssl_v2.erl @@ -0,0 +1,37 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2007-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 +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +%%---------------------------------------------------------------------- +%% Purpose: Handles sslv2 hello as clients supporting sslv2 and higher +%% will send an sslv2 hello. +%%---------------------------------------------------------------------- + +-module(ssl_v2). + +-export([client_random/2]). + +client_random(ChallengeData, 32) -> + ChallengeData; +client_random(ChallengeData, N) when N > 32 -> + <> = ChallengeData, + NewChallengeData; +client_random(ChallengeData, N) -> + Pad = list_to_binary(lists:duplicate(N, 0)), + <>. diff --git a/lib/ssl/src/ssl_v3.erl b/lib/ssl/src/ssl_v3.erl new file mode 100644 index 0000000000..d477b3df81 --- /dev/null +++ b/lib/ssl/src/ssl_v3.erl @@ -0,0 +1,203 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2007-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 +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +%%---------------------------------------------------------------------- +%% Purpose: Handles sslv3 encryption. +%%---------------------------------------------------------------------- + +-module(ssl_v3). + +-include("ssl_cipher.hrl"). +-include("ssl_internal.hrl"). +-include("ssl_record.hrl"). % MD5 and SHA + +-export([master_secret/3, finished/3, certificate_verify/3, + mac_hash/6, setup_keys/7, + suites/0]). +-compile(inline). + +%%==================================================================== +%% Internal application API +%%==================================================================== + +-spec master_secret(binary(), binary(), binary()) -> binary(). + +master_secret(PremasterSecret, ClientRandom, ServerRandom) -> + %% draft-ietf-tls-ssl-version3-00 - 6.2.2 + %% key_block = + %% MD5(master_secret + SHA(`A' + master_secret + + %% ServerHello.random + + %% ClientHello.random)) + + %% MD5(master_secret + SHA(`BB' + master_secret + + %% ServerHello.random + + %% ClientHello.random)) + + %% MD5(master_secret + SHA(`CCC' + master_secret + + %% ServerHello.random + + %% ClientHello.random)) + [...]; + Block = generate_keyblock(PremasterSecret, ClientRandom, ServerRandom, 48), + Block. + +-spec finished(client | server, binary(), [binary()]) -> binary(). + +finished(Role, MasterSecret, Handshake) -> + %% draft-ietf-tls-ssl-version3-00 - 5.6.9 Finished + %% struct { + %% opaque md5_hash[16]; + %% opaque sha_hash[20]; + %% } Finished; + %% + %% md5_hash MD5(master_secret + pad2 + + %% MD5(handshake_messages + Sender + + %% master_secret + pad1)); + %% sha_hash SHA(master_secret + pad2 + + %% SHA(handshake_messages + Sender + + %% master_secret + pad1)); + Sender = get_sender(Role), + MD5 = handshake_hash(?MD5, MasterSecret, Sender, Handshake), + SHA = handshake_hash(?SHA, MasterSecret, Sender, Handshake), + <>. + +-spec certificate_verify(md5sha | sha, binary(), [binary()]) -> binary(). + +certificate_verify(md5sha, MasterSecret, Handshake) -> + %% md5_hash + %% MD5(master_secret + pad_2 + + %% MD5(handshake_messages + master_secret + pad_1)); + %% sha_hash + %% SHA(master_secret + pad_2 + + %% SHA(handshake_messages + master_secret + pad_1)); + + MD5 = handshake_hash(?MD5, MasterSecret, undefined, Handshake), + SHA = handshake_hash(?SHA, MasterSecret, undefined, Handshake), + <>; + +certificate_verify(sha, MasterSecret, Handshake) -> + %% sha_hash + %% SHA(master_secret + pad_2 + + %% SHA(handshake_messages + master_secret + pad_1)); + + handshake_hash(?SHA, MasterSecret, undefined, Handshake). + +-spec mac_hash(integer(), binary(), integer(), integer(), integer(), binary()) -> binary(). + +mac_hash(Method, Mac_write_secret, Seq_num, Type, Length, Fragment) -> + %% draft-ietf-tls-ssl-version3-00 - 5.2.3.1 + %% hash(MAC_write_secret + pad_2 + + %% hash(MAC_write_secret + pad_1 + seq_num + + %% SSLCompressed.type + SSLCompressed.length + + %% SSLCompressed.fragment)); + Mac = mac_hash(Method, Mac_write_secret, + [<>, Fragment]), + Mac. + +-spec setup_keys(binary(), binary(), binary(), + integer(), integer(), term(), integer()) -> + {binary(), binary(), binary(), + binary(), binary(), binary()}. + +setup_keys(MasterSecret, ServerRandom, ClientRandom, HS, KML, _EKML, IVS) -> + KeyBlock = generate_keyblock(MasterSecret, ServerRandom, ClientRandom, + 2*(HS+KML+IVS)), + %% draft-ietf-tls-ssl-version3-00 - 6.2.2 + %% The key_block is partitioned as follows. + %% client_write_MAC_secret[CipherSpec.hash_size] + %% server_write_MAC_secret[CipherSpec.hash_size] + %% client_write_key[CipherSpec.key_material] + %% server_write_key[CipherSpec.key_material] + %% client_write_IV[CipherSpec.IV_size] /* non-export ciphers */ + %% server_write_IV[CipherSpec.IV_size] /* non-export ciphers */ + <> = KeyBlock, + {ClientWriteMacSecret, ServerWriteMacSecret, ClientWriteKey, + ServerWriteKey, ClientIV, ServerIV}. + +-spec suites() -> [cipher_suite()]. + +suites() -> + [ + ?TLS_DHE_RSA_WITH_AES_256_CBC_SHA, + ?TLS_DHE_DSS_WITH_AES_256_CBC_SHA, + ?TLS_RSA_WITH_AES_256_CBC_SHA, + ?TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA, + ?TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA, + ?TLS_RSA_WITH_3DES_EDE_CBC_SHA, + ?TLS_DHE_RSA_WITH_AES_128_CBC_SHA, + ?TLS_DHE_DSS_WITH_AES_128_CBC_SHA, + ?TLS_RSA_WITH_AES_128_CBC_SHA, + %%?TLS_RSA_WITH_IDEA_CBC_SHA, + ?TLS_RSA_WITH_RC4_128_SHA, + ?TLS_RSA_WITH_RC4_128_MD5, + ?TLS_RSA_WITH_DES_CBC_SHA + ]. + +%%-------------------------------------------------------------------- +%%% Internal functions +%%-------------------------------------------------------------------- + +hash(?MD5, Data) -> + crypto:hash(md5, Data); +hash(?SHA, Data) -> + crypto:hash(sha, Data). + +%%pad_1(?NULL) -> +%% ""; +pad_1(?MD5) -> + <<"666666666666666666666666666666666666666666666666">>; +pad_1(?SHA) -> + <<"6666666666666666666666666666666666666666">>. +%%pad_2(?NULL) -> +%% ""; +pad_2(?MD5) -> + <<"\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\" + "\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\">>; +pad_2(?SHA) -> + <<"\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\" + "\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\">>. + +mac_hash(?NULL, _Secret, _Data) -> + <<>>; +mac_hash(Method, Secret, Data) -> + InnerHash = hash(Method, [Secret, pad_1(Method), Data]), + hash(Method, [Secret, pad_2(Method), InnerHash]). + +handshake_hash(Method, MasterSecret, undefined, Handshake) -> + InnerHash = hash(Method, [Handshake, MasterSecret, pad_1(Method)]), + hash(Method, [MasterSecret, pad_2(Method), InnerHash]); +handshake_hash(Method, MasterSecret, Sender, Handshake) -> + InnerHash = hash(Method, [Handshake, Sender, MasterSecret, pad_1(Method)]), + hash(Method, [MasterSecret, pad_2(Method), InnerHash]). + +get_sender(client) -> "CLNT"; +get_sender(server) -> "SRVR". + +generate_keyblock(MasterSecret, ServerRandom, ClientRandom, WantedLength) -> + gen(MasterSecret, [MasterSecret, ServerRandom, ClientRandom], + WantedLength, 0, $A, 1, []). + +gen(_Secret, _All, Wanted, Len, _C, _N, Acc) when Wanted =< Len -> + <> = list_to_binary(lists:reverse(Acc)), + Block; +gen(Secret, All, Wanted, Len, C, N, Acc) -> + Prefix = lists:duplicate(N, C), + SHA = crypto:hash(sha, [Prefix, All]), + MD5 = crypto:hash(md5, [Secret, SHA]), + gen(Secret, All, Wanted, Len + 16, C+1, N+1, [MD5 | Acc]). diff --git a/lib/ssl/src/tls_handshake.erl b/lib/ssl/src/tls_handshake.erl index 066590afb1..fef1464c64 100644 --- a/lib/ssl/src/tls_handshake.erl +++ b/lib/ssl/src/tls_handshake.erl @@ -33,7 +33,7 @@ -include_lib("public_key/include/public_key.hrl"). -export([client_hello/8, server_hello/4, hello/4, - get_tls_handshake/3, encode_handshake/2, + get_tls_handshake/3, encode_handshake/2, decode_handshake/3, init_handshake_history/0, update_handshake_history/2]). %%==================================================================== @@ -197,17 +197,22 @@ update_handshake_history(Handshake, % special-case SSL2 client hello update_handshake_history({Handshake0, _Prev}, Data) -> {[Data|Handshake0], Handshake0}. -%%-------------------------------------------------------------------- -%%% Internal functions -%%-------------------------------------------------------------------- + get_tls_handshake_aux(Version, <>, Acc) -> Raw = <>, - H = decode_handshake(Version, Type, Body), - get_tls_handshake_aux(Version, Rest, [{H,Raw} | Acc]); + Handshake = decode_handshake(Version, Type, Body), + get_tls_handshake_aux(Version, Rest, [{Handshake,Raw} | Acc]); get_tls_handshake_aux(_Version, Data, Acc) -> {lists:reverse(Acc), Data}. +%%-------------------------------------------------------------------- +%%% Internal functions +%%-------------------------------------------------------------------- + +decode_handshake(_, ?HELLO_REQUEST, <<>>) -> + #hello_request{}; + %% Client hello v2. %% 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. @@ -217,7 +222,7 @@ decode_handshake(_Version, ?CLIENT_HELLO, <>) -> #client_hello{client_version = {Major, Minor}, - random = ssl_ssl2:client_random(ChallengeData, CDLength), + random = ssl_v2:client_random(ChallengeData, CDLength), session_id = 0, cipher_suites = ssl_handshake:decode_suites('3_bytes', CipherSuites), compression_methods = [?NULL], diff --git a/lib/ssl/src/tls_record.erl b/lib/ssl/src/tls_record.erl index 1409a04763..9ab512f334 100644 --- a/lib/ssl/src/tls_record.erl +++ b/lib/ssl/src/tls_record.erl @@ -77,7 +77,7 @@ init_connection_states(Role) -> ConnectionEnd = record_protocol_role(Role), Current = initial_connection_state(ConnectionEnd), - Pending = empty_connection_state(ConnectionEnd), + Pending = ssl_record:empty_connection_state(ConnectionEnd), #connection_states{current_read = Current, pending_read = Pending, current_write = Current, @@ -265,7 +265,7 @@ activate_pending_connection_state(States = NewCurrent = Pending#connection_state{sequence_number = 0}, SecParams = Pending#connection_state.security_parameters, ConnectionEnd = SecParams#security_parameters.connection_end, - EmptyPending = empty_connection_state(ConnectionEnd), + 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, @@ -278,7 +278,7 @@ activate_pending_connection_state(States = NewCurrent = Pending#connection_state{sequence_number = 0}, SecParams = Pending#connection_state.security_parameters, ConnectionEnd = SecParams#security_parameters.connection_end, - EmptyPending = empty_connection_state(ConnectionEnd), + 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, @@ -591,21 +591,6 @@ initial_security_params(ConnectionEnd) -> ssl_cipher:security_parameters(highest_protocol_version(), ?TLS_NULL_WITH_NULL_NULL, SecParams). -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), - <>. record_protocol_role(client) -> ?CLIENT; @@ -667,7 +652,7 @@ decipher(TLS=#ssl_tls{type=Type, version=Version, fragment=Fragment}, CS0) -> 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 + case ssl_record:is_correct_mac(Mac, MacHash) of true -> {TLS#ssl_tls{fragment = T}, CS2}; false -> @@ -696,19 +681,15 @@ hash_and_bump_seqno(#connection_state{sequence_number = SeqNo, 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); + ssl_v3: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, + tls_v1:mac_hash(MacAlg, MacSecret, SeqNo, Type, Version, Length, Fragment). sufficient_tlsv1_2_crypto_support() -> diff --git a/lib/ssl/src/tls_record.hrl b/lib/ssl/src/tls_record.hrl index 0e3552bafa..0be1e35458 100644 --- a/lib/ssl/src/tls_record.hrl +++ b/lib/ssl/src/tls_record.hrl @@ -27,13 +27,6 @@ -define(tls_record, true). -include("ssl_record.hrl"). %% Common TLS and DTLS records and Constantes --record(connection_states, { - current_read, - pending_read, - current_write, - pending_write - }). - %% Used to handle tls_plain_text, tls_compressed and tls_cipher_text -record(ssl_tls, { diff --git a/lib/ssl/src/tls_v1.erl b/lib/ssl/src/tls_v1.erl new file mode 100644 index 0000000000..2395e98642 --- /dev/null +++ b/lib/ssl/src/tls_v1.erl @@ -0,0 +1,432 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2007-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 +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +%%---------------------------------------------------------------------- +%% Purpose: Handles tls1 encryption. +%%---------------------------------------------------------------------- + +-module(tls_v1). + +-include("ssl_cipher.hrl"). +-include("ssl_internal.hrl"). +-include("ssl_record.hrl"). + +-export([master_secret/4, finished/5, certificate_verify/3, mac_hash/7, + setup_keys/8, suites/1, prf/5, + ecc_curves/1, oid_to_enum/1, enum_to_oid/1]). + +%%==================================================================== +%% Internal application API +%%==================================================================== + +-spec master_secret(integer(), binary(), binary(), binary()) -> binary(). + +master_secret(PrfAlgo, PreMasterSecret, ClientRandom, ServerRandom) -> + %% RFC 2246 & 4346 && RFC 5246 - 8.1 %% master_secret = PRF(pre_master_secret, + %% "master secret", ClientHello.random + + %% ServerHello.random)[0..47]; + + prf(PrfAlgo, PreMasterSecret, <<"master secret">>, + [ClientRandom, ServerRandom], 48). + +-spec finished(client | server, integer(), integer(), binary(), [binary()]) -> binary(). + +finished(Role, Version, PrfAlgo, MasterSecret, Handshake) + when Version == 1; Version == 2; PrfAlgo == ?MD5SHA -> + %% RFC 2246 & 4346 - 7.4.9. Finished + %% struct { + %% opaque verify_data[12]; + %% } Finished; + %% + %% verify_data + %% PRF(master_secret, finished_label, MD5(handshake_messages) + + %% SHA-1(handshake_messages)) [0..11]; + MD5 = crypto:hash(md5, Handshake), + SHA = crypto:hash(sha, Handshake), + prf(?MD5SHA, MasterSecret, finished_label(Role), [MD5, SHA], 12); + +finished(Role, Version, PrfAlgo, MasterSecret, Handshake) + when Version == 3 -> + %% RFC 5246 - 7.4.9. Finished + %% struct { + %% opaque verify_data[12]; + %% } Finished; + %% + %% verify_data + %% PRF(master_secret, finished_label, Hash(handshake_messages)) [0..11]; + Hash = crypto:hash(mac_algo(PrfAlgo), Handshake), + prf(PrfAlgo, MasterSecret, finished_label(Role), Hash, 12). + +-spec certificate_verify(md5sha | sha, integer(), [binary()]) -> binary(). + +certificate_verify(md5sha, _Version, Handshake) -> + MD5 = crypto:hash(md5, Handshake), + SHA = crypto:hash(sha, Handshake), + <>; + +certificate_verify(HashAlgo, _Version, Handshake) -> + crypto:hash(HashAlgo, Handshake). + +-spec setup_keys(integer(), integer(), binary(), binary(), binary(), integer(), + integer(), integer()) -> {binary(), binary(), binary(), + binary(), binary(), binary()}. + +setup_keys(Version, _PrfAlgo, MasterSecret, ServerRandom, ClientRandom, HashSize, + KeyMatLen, IVSize) + when Version == 1 -> + %% RFC 2246 - 6.3. Key calculation + %% key_block = PRF(SecurityParameters.master_secret, + %% "key expansion", + %% SecurityParameters.server_random + + %% SecurityParameters.client_random); + %% Then the key_block is partitioned as follows: + %% client_write_MAC_secret[SecurityParameters.hash_size] + %% server_write_MAC_secret[SecurityParameters.hash_size] + %% client_write_key[SecurityParameters.key_material_length] + %% server_write_key[SecurityParameters.key_material_length] + %% client_write_IV[SecurityParameters.IV_size] + %% server_write_IV[SecurityParameters.IV_size] + WantedLength = 2 * (HashSize + KeyMatLen + IVSize), + KeyBlock = prf(?MD5SHA, MasterSecret, "key expansion", + [ServerRandom, ClientRandom], WantedLength), + <> = KeyBlock, + {ClientWriteMacSecret, ServerWriteMacSecret, ClientWriteKey, + ServerWriteKey, ClientIV, ServerIV}; + +%% TLS v1.1 +setup_keys(Version, _PrfAlgo, MasterSecret, ServerRandom, ClientRandom, HashSize, + KeyMatLen, IVSize) + when Version == 2 -> + %% RFC 4346 - 6.3. Key calculation + %% key_block = PRF(SecurityParameters.master_secret, + %% "key expansion", + %% SecurityParameters.server_random + + %% SecurityParameters.client_random); + %% Then the key_block is partitioned as follows: + %% client_write_MAC_secret[SecurityParameters.hash_size] + %% server_write_MAC_secret[SecurityParameters.hash_size] + %% client_write_key[SecurityParameters.key_material_length] + %% server_write_key[SecurityParameters.key_material_length] + %% + %% RFC 4346 is incomplete, the client and server IVs have to + %% be generated just like for TLS 1.0 + WantedLength = 2 * (HashSize + KeyMatLen + IVSize), + KeyBlock = prf(?MD5SHA, MasterSecret, "key expansion", + [ServerRandom, ClientRandom], WantedLength), + <> = KeyBlock, + {ClientWriteMacSecret, ServerWriteMacSecret, ClientWriteKey, + ServerWriteKey, ClientIV, ServerIV}; + +%% TLS v1.2 +setup_keys(Version, PrfAlgo, MasterSecret, ServerRandom, ClientRandom, HashSize, + KeyMatLen, IVSize) + when Version == 3 -> + %% RFC 5246 - 6.3. Key calculation + %% key_block = PRF(SecurityParameters.master_secret, + %% "key expansion", + %% SecurityParameters.server_random + + %% SecurityParameters.client_random); + %% Then the key_block is partitioned as follows: + %% client_write_MAC_secret[SecurityParameters.hash_size] + %% server_write_MAC_secret[SecurityParameters.hash_size] + %% client_write_key[SecurityParameters.key_material_length] + %% server_write_key[SecurityParameters.key_material_length] + %% client_write_IV[SecurityParameters.fixed_iv_length] + %% server_write_IV[SecurityParameters.fixed_iv_length] + WantedLength = 2 * (HashSize + KeyMatLen + IVSize), + KeyBlock = prf(PrfAlgo, MasterSecret, "key expansion", + [ServerRandom, ClientRandom], WantedLength), + <> = KeyBlock, + {ClientWriteMacSecret, ServerWriteMacSecret, ClientWriteKey, + ServerWriteKey, ClientIV, ServerIV}. + +-spec mac_hash(integer(), binary(), integer(), integer(), tls_version(), + integer(), binary()) -> binary(). + +mac_hash(Method, Mac_write_secret, Seq_num, Type, {Major, Minor}, + Length, Fragment) -> + %% RFC 2246 & 4346 - 6.2.3.1. + %% HMAC_hash(MAC_write_secret, seq_num + TLSCompressed.type + + %% TLSCompressed.version + TLSCompressed.length + + %% TLSCompressed.fragment)); + Mac = hmac_hash(Method, Mac_write_secret, + [<>, + Fragment]), + Mac. + +-spec suites(1|2|3) -> [cipher_suite()]. + +suites(Minor) when Minor == 1; Minor == 2-> + case sufficent_ec_support() of + true -> + all_suites(Minor); + false -> + no_ec_suites(Minor) + end; + +suites(Minor) when Minor == 3 -> + case sufficent_ec_support() of + true -> + all_suites(3) ++ all_suites(2); + false -> + no_ec_suites(3) ++ no_ec_suites(2) + end. + +all_suites(Minor) when Minor == 1; Minor == 2-> + [ + ?TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, + ?TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, + ?TLS_DHE_RSA_WITH_AES_256_CBC_SHA, + ?TLS_DHE_DSS_WITH_AES_256_CBC_SHA, + ?TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA, + ?TLS_ECDH_RSA_WITH_AES_256_CBC_SHA, + ?TLS_RSA_WITH_AES_256_CBC_SHA, + + ?TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA, + ?TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, + ?TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA, + ?TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA, + ?TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA, + ?TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA, + ?TLS_RSA_WITH_3DES_EDE_CBC_SHA, + + ?TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, + ?TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, + ?TLS_DHE_RSA_WITH_AES_128_CBC_SHA, + ?TLS_DHE_DSS_WITH_AES_128_CBC_SHA, + ?TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA, + ?TLS_ECDH_RSA_WITH_AES_128_CBC_SHA, + ?TLS_RSA_WITH_AES_128_CBC_SHA, + + ?TLS_ECDHE_ECDSA_WITH_RC4_128_SHA, + ?TLS_ECDHE_RSA_WITH_RC4_128_SHA, + ?TLS_RSA_WITH_RC4_128_SHA, + ?TLS_RSA_WITH_RC4_128_MD5, + ?TLS_DHE_RSA_WITH_DES_CBC_SHA, + ?TLS_ECDH_ECDSA_WITH_RC4_128_SHA, + ?TLS_ECDH_RSA_WITH_RC4_128_SHA, + + ?TLS_RSA_WITH_DES_CBC_SHA + ]; +all_suites(3) -> + [ + ?TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384, + ?TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384, + ?TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384, + ?TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384, + + ?TLS_DHE_RSA_WITH_AES_256_CBC_SHA256, + ?TLS_DHE_DSS_WITH_AES_256_CBC_SHA256, + ?TLS_RSA_WITH_AES_256_CBC_SHA256, + + ?TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, + ?TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, + ?TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256, + ?TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256, + + ?TLS_DHE_RSA_WITH_AES_128_CBC_SHA256, + ?TLS_DHE_DSS_WITH_AES_128_CBC_SHA256, + ?TLS_RSA_WITH_AES_128_CBC_SHA256 + ]. + +no_ec_suites(Minor) when Minor == 1; Minor == 2-> + [ + ?TLS_DHE_RSA_WITH_AES_256_CBC_SHA, + ?TLS_DHE_DSS_WITH_AES_256_CBC_SHA, + ?TLS_RSA_WITH_AES_256_CBC_SHA, + ?TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA, + ?TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA, + ?TLS_RSA_WITH_3DES_EDE_CBC_SHA, + ?TLS_DHE_RSA_WITH_AES_128_CBC_SHA, + ?TLS_DHE_DSS_WITH_AES_128_CBC_SHA, + ?TLS_RSA_WITH_AES_128_CBC_SHA, + ?TLS_RSA_WITH_RC4_128_SHA, + ?TLS_RSA_WITH_RC4_128_MD5, + ?TLS_DHE_RSA_WITH_DES_CBC_SHA, + ?TLS_RSA_WITH_DES_CBC_SHA + ]; +no_ec_suites(3) -> + [ + ?TLS_DHE_RSA_WITH_AES_256_CBC_SHA256, + ?TLS_DHE_DSS_WITH_AES_256_CBC_SHA256, + ?TLS_RSA_WITH_AES_256_CBC_SHA256, + ?TLS_DHE_RSA_WITH_AES_128_CBC_SHA256, + ?TLS_DHE_DSS_WITH_AES_128_CBC_SHA256, + ?TLS_RSA_WITH_AES_128_CBC_SHA256 + ]. + +%%-------------------------------------------------------------------- +%%% Internal functions +%%-------------------------------------------------------------------- +%%%% HMAC and the Pseudorandom Functions RFC 2246 & 4346 - 5.%%%% +hmac_hash(?NULL, _, _) -> + <<>>; +hmac_hash(Alg, Key, Value) -> + crypto:hmac(mac_algo(Alg), Key, Value). + +mac_algo(?MD5) -> md5; +mac_algo(?SHA) -> sha; +mac_algo(?SHA256) -> sha256; +mac_algo(?SHA384) -> sha384; +mac_algo(?SHA512) -> sha512. + +% First, we define a data expansion function, P_hash(secret, data) that +% uses a single hash function to expand a secret and seed into an +% arbitrary quantity of output: +%% P_hash(secret, seed) = HMAC_hash(secret, A(1) + seed) + +%% HMAC_hash(secret, A(2) + seed) + +%% HMAC_hash(secret, A(3) + seed) + ... + +p_hash(Secret, Seed, WantedLength, Method) -> + p_hash(Secret, Seed, WantedLength, Method, 0, []). + +p_hash(_Secret, _Seed, WantedLength, _Method, _N, []) + when WantedLength =< 0 -> + []; +p_hash(_Secret, _Seed, WantedLength, _Method, _N, [Last | Acc]) + when WantedLength =< 0 -> + Keep = byte_size(Last) + WantedLength, + <> = Last, + list_to_binary(lists:reverse(Acc, [B])); +p_hash(Secret, Seed, WantedLength, Method, N, Acc) -> + N1 = N+1, + Bin = hmac_hash(Method, Secret, [a(N1, Secret, Seed, Method), Seed]), + p_hash(Secret, Seed, WantedLength - byte_size(Bin), Method, N1, [Bin|Acc]). + + +%% ... Where A(0) = seed +%% A(i) = HMAC_hash(secret, A(i-1)) +%% a(0, _Secret, Seed, _Method) -> +%% Seed. +%% a(N, Secret, Seed, Method) -> +%% hmac_hash(Method, Secret, a(N-1, Secret, Seed, Method)). +a(0, _Secret, Seed, _Method) -> + Seed; +a(N, Secret, Seed0, Method) -> + Seed = hmac_hash(Method, Secret, Seed0), + a(N-1, Secret, Seed, Method). + +split_secret(BinSecret) -> + %% L_S = length in bytes of secret; + %% L_S1 = L_S2 = ceil(L_S / 2); + %% The secret is partitioned into two halves (with the possibility of + %% one shared byte) as described above, S1 taking the first L_S1 bytes, + %% and S2 the last L_S2 bytes. + Length = byte_size(BinSecret), + Div = Length div 2, + EvenLength = Length - Div, + <> = BinSecret, + <<_:Div/binary, Secret2:EvenLength/binary>> = BinSecret, + {Secret1, Secret2}. + +prf(?MD5SHA, Secret, Label, Seed, WantedLength) -> + %% PRF(secret, label, seed) = P_MD5(S1, label + seed) XOR + %% P_SHA-1(S2, label + seed); + {S1, S2} = split_secret(Secret), + LS = list_to_binary([Label, Seed]), + crypto:exor(p_hash(S1, LS, WantedLength, ?MD5), + p_hash(S2, LS, WantedLength, ?SHA)); + +prf(MAC, Secret, Label, Seed, WantedLength) -> + %% PRF(secret, label, seed) = P_SHA256(secret, label + seed); + LS = list_to_binary([Label, Seed]), + p_hash(Secret, LS, WantedLength, MAC). + +%%%% Misc help functions %%%% + +finished_label(client) -> + <<"client finished">>; +finished_label(server) -> + <<"server finished">>. + +%% list ECC curves in prefered order +ecc_curves(_Minor) -> + [?sect571r1,?sect571k1,?secp521r1,?sect409k1,?sect409r1, + ?secp384r1,?sect283k1,?sect283r1,?secp256k1,?secp256r1, + ?sect239k1,?sect233k1,?sect233r1,?secp224k1,?secp224r1, + ?sect193r1,?sect193r2,?secp192k1,?secp192r1,?sect163k1, + ?sect163r1,?sect163r2,?secp160k1,?secp160r1,?secp160r2]. + +%% ECC curves from draft-ietf-tls-ecc-12.txt (Oct. 17, 2005) +oid_to_enum(?sect163k1) -> 1; +oid_to_enum(?sect163r1) -> 2; +oid_to_enum(?sect163r2) -> 3; +oid_to_enum(?sect193r1) -> 4; +oid_to_enum(?sect193r2) -> 5; +oid_to_enum(?sect233k1) -> 6; +oid_to_enum(?sect233r1) -> 7; +oid_to_enum(?sect239k1) -> 8; +oid_to_enum(?sect283k1) -> 9; +oid_to_enum(?sect283r1) -> 10; +oid_to_enum(?sect409k1) -> 11; +oid_to_enum(?sect409r1) -> 12; +oid_to_enum(?sect571k1) -> 13; +oid_to_enum(?sect571r1) -> 14; +oid_to_enum(?secp160k1) -> 15; +oid_to_enum(?secp160r1) -> 16; +oid_to_enum(?secp160r2) -> 17; +oid_to_enum(?secp192k1) -> 18; +oid_to_enum(?secp192r1) -> 19; +oid_to_enum(?secp224k1) -> 20; +oid_to_enum(?secp224r1) -> 21; +oid_to_enum(?secp256k1) -> 22; +oid_to_enum(?secp256r1) -> 23; +oid_to_enum(?secp384r1) -> 24; +oid_to_enum(?secp521r1) -> 25. + +enum_to_oid(1) -> ?sect163k1; +enum_to_oid(2) -> ?sect163r1; +enum_to_oid(3) -> ?sect163r2; +enum_to_oid(4) -> ?sect193r1; +enum_to_oid(5) -> ?sect193r2; +enum_to_oid(6) -> ?sect233k1; +enum_to_oid(7) -> ?sect233r1; +enum_to_oid(8) -> ?sect239k1; +enum_to_oid(9) -> ?sect283k1; +enum_to_oid(10) -> ?sect283r1; +enum_to_oid(11) -> ?sect409k1; +enum_to_oid(12) -> ?sect409r1; +enum_to_oid(13) -> ?sect571k1; +enum_to_oid(14) -> ?sect571r1; +enum_to_oid(15) -> ?secp160k1; +enum_to_oid(16) -> ?secp160r1; +enum_to_oid(17) -> ?secp160r2; +enum_to_oid(18) -> ?secp192k1; +enum_to_oid(19) -> ?secp192r1; +enum_to_oid(20) -> ?secp224k1; +enum_to_oid(21) -> ?secp224r1; +enum_to_oid(22) -> ?secp256k1; +enum_to_oid(23) -> ?secp256r1; +enum_to_oid(24) -> ?secp384r1; +enum_to_oid(25) -> ?secp521r1. + +sufficent_ec_support() -> + CryptoSupport = crypto:supports(), + proplists:get_bool(ecdh, proplists:get_value(public_keys, CryptoSupport)). -- cgit v1.2.3 From 1116957c362cb71938dca162f5f784ea53c7463d Mon Sep 17 00:00:00 2001 From: Ingela Anderton Andin Date: Tue, 3 Sep 2013 18:10:27 +0200 Subject: ssl: Solve rebase issues --- lib/ssl/src/ssl_handshake.erl | 155 +++++++++++++++++++++++++---------------- lib/ssl/src/tls_connection.erl | 92 ++++++++++++------------ 2 files changed, 139 insertions(+), 108 deletions(-) (limited to 'lib/ssl/src') diff --git a/lib/ssl/src/ssl_handshake.erl b/lib/ssl/src/ssl_handshake.erl index 5084c46571..bef42d9071 100644 --- a/lib/ssl/src/ssl_handshake.erl +++ b/lib/ssl/src/ssl_handshake.erl @@ -32,7 +32,7 @@ %% Handshake messages -export([hello_request/0, server_hello_done/0, - certificate/4, certificate_request/3, key_exchange/3, + certificate/4, certificate_request/4, key_exchange/3, finished/5, next_protocol/1]). %% Handle handshake messages @@ -58,7 +58,8 @@ ]). %% MISC --export([select_version/3, prf/5, decrypt_premaster_secret/2]). +-export([select_version/3, prf/5, select_hashsign/2, select_cert_hashsign/3, + decrypt_premaster_secret/2]). %%==================================================================== %% Internal application API @@ -83,7 +84,7 @@ hello_request() -> server_hello_done() -> #server_hello_done{}. -client_hello_extensions(Version = {Major, Minor}, CipherSuites, SslOpts, ConnectionStates, Renegotiation) -> +client_hello_extensions(Version, CipherSuites, SslOpts, ConnectionStates, Renegotiation) -> {EcPointFormats, EllipticCurves} = case advertises_ec_ciphers(lists:map(fun ssl_cipher:suite_definition/1, CipherSuites)) of true -> @@ -91,21 +92,13 @@ client_hello_extensions(Version = {Major, Minor}, CipherSuites, SslOpts, Connect false -> {undefined, undefined} end, - - HashSign = if - Major == 3, Minor >=3 -> - default_hash_signs(); - true -> - undefined - end, - SRP = srp_user(SslOpts), #hello_extensions{ renegotiation_info = renegotiation_info(tls_record, client, ConnectionStates, Renegotiation), srp = SRP, - hash_signs = HashSign, + hash_signs = advertised_hash_signs(Version), ec_point_formats = EcPointFormats, elliptic_curves = EllipticCurves, next_protocol_negotiation = @@ -172,14 +165,14 @@ client_certificate_verify(OwnCert, MasterSecret, Version, end. %%-------------------------------------------------------------------- --spec certificate_request(erl_cipher_suite(), db_handle(), certdb_ref()) -> +-spec certificate_request(erl_cipher_suite(), db_handle(), certdb_ref(), tls_version()) -> #certificate_request{}. %% %% Description: Creates a certificate_request message, called by the server. %%-------------------------------------------------------------------- -certificate_request(CipherSuite, CertDbHandle, CertDbRef) -> +certificate_request(CipherSuite, CertDbHandle, CertDbRef, Version) -> Types = certificate_types(CipherSuite), - HashSigns = default_hash_signs(), + HashSigns = advertised_hash_signs(Version), Authorities = certificate_authorities(CertDbHandle, CertDbRef), #certificate_request{ certificate_types = Types, @@ -447,6 +440,52 @@ prf({3,1}, Secret, Label, Seed, WantedLength) -> {ok, tls_v1:prf(?MD5SHA, Secret, Label, Seed, WantedLength)}; prf({3,_N}, Secret, Label, Seed, WantedLength) -> {ok, tls_v1:prf(?SHA256, Secret, Label, Seed, WantedLength)}. +%%-------------------------------------------------------------------- +-spec select_hashsign(#hash_sign_algos{}| undefined, undefined | term()) -> + [{atom(), atom()}] | undefined. + +%% +%% Description: +%%-------------------------------------------------------------------- +select_hashsign(_, undefined) -> + {null, anon}; +select_hashsign(undefined, Cert) -> + #'OTPCertificate'{tbsCertificate = TBSCert} = public_key:pkix_decode_cert(Cert, otp), + #'OTPSubjectPublicKeyInfo'{algorithm = {_,Algo, _}} = TBSCert#'OTPTBSCertificate'.subjectPublicKeyInfo, + select_cert_hashsign(undefined, Algo, {undefined, undefined}); +select_hashsign(#hash_sign_algos{hash_sign_algos = HashSigns}, Cert) -> + #'OTPCertificate'{tbsCertificate = TBSCert} =public_key:pkix_decode_cert(Cert, otp), + #'OTPSubjectPublicKeyInfo'{algorithm = {_,Algo, _}} = TBSCert#'OTPTBSCertificate'.subjectPublicKeyInfo, + DefaultHashSign = {_, Sign} = select_cert_hashsign(undefined, Algo, {undefined, undefined}), + case lists:filter(fun({sha, dsa}) -> + true; + ({_, dsa}) -> + false; + ({Hash, S}) when S == Sign -> + ssl_cipher:is_acceptable_hash(Hash, proplists:get_value(hashs, crypto:supports())); + (_) -> + false + end, HashSigns) of + [] -> + DefaultHashSign; + [HashSign| _] -> + HashSign + end. +%%-------------------------------------------------------------------- +-spec select_cert_hashsign(#hash_sign_algos{}| undefined, oid(), tls_version()) -> + [{atom(), atom()}]. + +%% +%% Description: +%%-------------------------------------------------------------------- +select_cert_hashsign(HashSign, _, {Major, Minor}) when HashSign =/= undefined andalso Major >= 3 andalso Minor >= 3 -> + HashSign; +select_cert_hashsign(undefined,?'id-ecPublicKey', _) -> + {sha, ecdsa}; +select_cert_hashsign(undefined, ?rsaEncryption, _) -> + {md5sha, rsa}; +select_cert_hashsign(undefined, ?'id-dsa', _) -> + {sha, dsa}. %%-------------------------------------------------------------------- -spec master_secret(atom(), tls_version(), #session{} | binary(), #connection_states{}, @@ -699,7 +738,7 @@ decode_handshake({Major, Minor}, ?CERTIFICATE_REQUEST, <>) - when Major == 3, Minor >= 3 -> + when Major >= 3, Minor >= 3 -> HashSignAlgos = [{ssl_cipher:hash_algorithm(Hash), ssl_cipher:sign_algorithm(Sign)} || <> <= HashSigns], #certificate_request{certificate_types = CertTypes, @@ -760,11 +799,11 @@ dec_server_key(<> = KeyStruct, +dec_server_key(<> = KeyStruct, KeyExchange, Version) when KeyExchange == ?KEY_EXCHANGE_PSK; KeyExchange == ?KEY_EXCHANGE_RSA_PSK -> Params = #server_psk_params{ - hint = PskIdentityHint}, + hint = PskIdentityHint}, {BinMsg, HashSign, Signature} = dec_server_key_params(Len + 2, KeyStruct, Version), #server_key_params{params = Params, params_bin = BinMsg, @@ -1067,26 +1106,6 @@ handle_ecc_curves_extension(_Version, undefined) -> handle_ecc_curves_extension(Version, _) -> #elliptic_curves{elliptic_curve_list = tls_v1:ecc_curves(Version)}. --define(TLSEXT_SIGALG_RSA(MD), {MD, rsa}). --define(TLSEXT_SIGALG_DSA(MD), {MD, dsa}). --define(TLSEXT_SIGALG_ECDSA(MD), {MD, ecdsa}). - --define(TLSEXT_SIGALG(MD), ?TLSEXT_SIGALG_ECDSA(MD), ?TLSEXT_SIGALG_RSA(MD)). - -default_hash_signs() -> - HashSigns = [?TLSEXT_SIGALG(sha512), - ?TLSEXT_SIGALG(sha384), - ?TLSEXT_SIGALG(sha256), - ?TLSEXT_SIGALG(sha224), - ?TLSEXT_SIGALG(sha), - ?TLSEXT_SIGALG_DSA(sha), - ?TLSEXT_SIGALG_RSA(md5)], - CryptoSupport = proplists:get_value(public_keys, crypto:supports()), - HasECC = proplists:get_bool(ecdsa, CryptoSupport), - #hash_sign_algos{hash_sign_algos = - lists:filter(fun({_, ecdsa}) -> HasECC; - (_) -> true end, HashSigns)}. - advertises_ec_ciphers([]) -> false; advertises_ec_ciphers([{ecdh_ecdsa, _,_,_} | _]) -> @@ -1407,14 +1426,11 @@ dec_server_key_signature(Params, <>, _) -> dec_server_key_signature(_, _, _) -> throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE)). - dec_hello_extensions(<<>>, Acc) -> Acc; -dec_hello_extensions(<>, Acc) -> - dec_hello_extensions(Rest, - Acc#hello_extensions{next_protocol_negotiation = - #next_protocol_negotiation{extension_data = ExtensionData}}); +dec_hello_extensions(<>, Acc) -> + NextP = #next_protocol_negotiation{extension_data = ExtensionData}, + dec_hello_extensions(Rest, Acc#hello_extensions{next_protocol_negotiation = NextP}); dec_hello_extensions(<>, Acc) -> RenegotiateInfo = case Len of 1 -> % Initial handshake @@ -1424,13 +1440,13 @@ dec_hello_extensions(<> = Info, VerifyInfo end, - dec_hello_extensions(Rest, - Acc#hello_extensions{renegotiation_info = - #renegotiation_info{renegotiated_connection = RenegotiateInfo}}); + dec_hello_extensions(Rest, Acc#hello_extensions{renegotiation_info = + #renegotiation_info{renegotiated_connection = + RenegotiateInfo}}); dec_hello_extensions(<>, Acc) when Len == SRPLen + 2 -> - dec_hello_extensions(Rest, Acc#hello_extensions{srp = #srp{username = SRP}}); + dec_hello_extensions(Rest, Acc#hello_extensions{srp = #srp{username = SRP}}); dec_hello_extensions(<>, Acc) -> @@ -1439,26 +1455,22 @@ dec_hello_extensions(<> <= SignAlgoList], dec_hello_extensions(Rest, Acc#hello_extensions{hash_signs = - #hash_sign_algos{hash_sign_algos = HashSignAlgos}}); + #hash_sign_algos{hash_sign_algos = HashSignAlgos}}); dec_hello_extensions(<>, Acc) -> - EllipticCurveListLen = Len - 2, - <> = ExtData, + <> = ExtData, EllipticCurves = [tls_v1:enum_to_oid(X) || <> <= EllipticCurveList], - - dec_hello_extensions(Rest, [{elliptic_curves, - #elliptic_curves{elliptic_curve_list = EllipticCurves}} | Acc]); - + dec_hello_extensions(Rest, Acc#hello_extensions{elliptic_curves = + #elliptic_curves{elliptic_curve_list = + EllipticCurves}}); dec_hello_extensions(<>, Acc) -> - %%ECPointFormatListLen = Len - 1, <> = ExtData, ECPointFormats = binary_to_list(ECPointFormatList), - dec_hello_extensions(Rest, - Acc#hello_extensions{ec_point_formats = - #ec_point_formats{ec_point_format_list = ECPointFormats}}); - + dec_hello_extensions(Rest, Acc#hello_extensions{ec_point_formats = + #ec_point_formats{ec_point_format_list = + ECPointFormats}}); %% Ignore data following the ClientHello (i.e., %% extensions) if not understood. @@ -1609,3 +1621,26 @@ is_member(Suite, SupportedSuites) -> select_compression(_CompressionMetodes) -> ?NULL. + +-define(TLSEXT_SIGALG_RSA(MD), {MD, rsa}). +-define(TLSEXT_SIGALG_DSA(MD), {MD, dsa}). +-define(TLSEXT_SIGALG_ECDSA(MD), {MD, ecdsa}). + +-define(TLSEXT_SIGALG(MD), ?TLSEXT_SIGALG_ECDSA(MD), ?TLSEXT_SIGALG_RSA(MD)). + +advertised_hash_signs({Major, Minor}) when Major >= 3 andalso Minor >= 3 -> + HashSigns = [?TLSEXT_SIGALG(sha512), + ?TLSEXT_SIGALG(sha384), + ?TLSEXT_SIGALG(sha256), + ?TLSEXT_SIGALG(sha224), + ?TLSEXT_SIGALG(sha), + ?TLSEXT_SIGALG_DSA(sha), + ?TLSEXT_SIGALG_RSA(md5)], + CryptoSupport = crypto:supports(), + HasECC = proplists:get_bool(ecdsa, proplists:get_value(public_keys, CryptoSupport)), + Hashs = proplists:get_value(hashs, CryptoSupport), + #hash_sign_algos{hash_sign_algos = + lists:filter(fun({Hash, ecdsa}) -> HasECC andalso proplists:get_bool(Hash, Hashs); + ({Hash, _}) -> proplists:get_bool(Hash, Hashs) end, HashSigns)}; +advertised_hash_signs(_) -> + undefined. diff --git a/lib/ssl/src/tls_connection.erl b/lib/ssl/src/tls_connection.erl index b6b0558965..37e22015bf 100644 --- a/lib/ssl/src/tls_connection.erl +++ b/lib/ssl/src/tls_connection.erl @@ -363,7 +363,6 @@ hello(#hello_request{}, #state{role = client} = State0) -> next_state(hello, hello, Record, State); hello(#server_hello{cipher_suite = CipherSuite, - hash_signs = HashSign, compression_method = Compression} = Hello, #state{session = #session{session_id = OldId}, connection_states = ConnectionStates0, @@ -388,8 +387,6 @@ hello(#server_hello{cipher_suite = CipherSuite, end, State = State0#state{key_algorithm = KeyAlgorithm, - hashsign_algorithm = - negotiated_hashsign(HashSign, KeyAlgorithm, Version), negotiated_version = Version, connection_states = ConnectionStates, premaster_secret = PremasterSecret, @@ -406,27 +403,27 @@ hello(#server_hello{cipher_suite = CipherSuite, end; hello(Hello = #client_hello{client_version = ClientVersion, - hash_signs = HashSigns}, + extensions = #hello_extensions{hash_signs = HashSigns}}, State = #state{connection_states = ConnectionStates0, port = Port, session = #session{own_certificate = Cert} = Session0, renegotiation = {Renegotiation, _}, - session_cache = Cache, + session_cache = Cache, session_cache_cb = CacheCb, ssl_options = SslOpts}) -> - HashSign = ssl_handshake:select_hashsign(HashSigns, Cert), case tls_handshake:hello(Hello, SslOpts, {Port, Session0, Cache, CacheCb, ConnectionStates0, Cert}, Renegotiation) of - {Version, {Type, #session{cipher_suite = CipherSuite} = Session}, ConnectionStates, + {Version, {Type, #session{cipher_suite = CipherSuite} = Session}, + ConnectionStates, #hello_extensions{ec_point_formats = EcPointFormats, elliptic_curves = EllipticCurves} = ServerHelloExt} -> - {KeyAlgorithm, _, _, _} = ssl_cipher:suite_definition(CipherSuite), - NH = negotiated_hashsign(HashSign, KeyAlgorithm, Version), + {KeyAlg, _, _, _} = ssl_cipher:suite_definition(CipherSuite), + NegotiatedHashSign = negotiated_hashsign(HashSign, KeyAlg, Version), do_server_hello(Type, ServerHelloExt, State#state{connection_states = ConnectionStates, negotiated_version = Version, session = Session, - hashsign_algorithm = NH, + hashsign_algorithm = NegotiatedHashSign, client_ecc = {EllipticCurves, EcPointFormats}}); #alert{} = Alert -> handle_own_alert(Alert, ClientVersion, hello, State) @@ -559,7 +556,7 @@ certify(#server_key_exchange{} = Msg, certify(#certificate_request{hashsign_algorithms = HashSigns}, #state{session = #session{own_certificate = Cert}} = State0) -> - HashSign = tls_handshake:select_hashsign(HashSigns, Cert), + HashSign = ssl_handshake:select_hashsign(HashSigns, Cert), {Record, State} = next_record(State0#state{client_certificate_requested = true}), next_state(certify, certify, Record, State#state{cert_hashsign_algorithm = HashSign}); @@ -770,7 +767,7 @@ cipher(#certificate_verify{signature = Signature, hashsign_algorithm = CertHashS tls_handshake_history = Handshake } = State0) -> - HashSign = tls_handshake:select_cert_hashsign(CertHashSign, Algo, Version), + HashSign = ssl_handshake:select_cert_hashsign(CertHashSign, Algo, Version), case ssl_handshake:certificate_verify(Signature, PublicKeyInfo, Version, HashSign, MasterSecret, Handshake) of valid -> @@ -1430,7 +1427,7 @@ verify_client_cert(#state{client_certificate_requested = true, role = client, cert_hashsign_algorithm = HashSign, tls_handshake_history = Handshake0} = State) -> - case tls_handshake:client_certificate_verify(OwnCert, MasterSecret, + case ssl_handshake:client_certificate_verify(OwnCert, MasterSecret, Version, HashSign, PrivateKey, Handshake0) of #certificate_verify{} = Verified -> {BinVerified, ConnectionStates, Handshake} = @@ -1947,7 +1944,7 @@ request_client_cert(#state{ssl_options = #ssl_options{verify = verify_peer}, #connection_state{security_parameters = #security_parameters{cipher_suite = CipherSuite}} = tls_record:pending_connection_state(ConnectionStates0, read), - Msg = ssl_handshake:certificate_request(CipherSuite, CertDbHandle, CertDbRef), + Msg = ssl_handshake:certificate_request(CipherSuite, CertDbHandle, CertDbRef, Version), {BinMsg, ConnectionStates, Handshake} = encode_handshake(Msg, Version, ConnectionStates0, Handshake0), @@ -3008,6 +3005,39 @@ get_pending_connection_state_prf(CStates, Direction) -> CS = tls_record:pending_connection_state(CStates, Direction), CS#connection_state.security_parameters#security_parameters.prf_algorithm. +start_or_recv_cancel_timer(infinity, _RecvFrom) -> + undefined; +start_or_recv_cancel_timer(Timeout, RecvFrom) -> + erlang:send_after(Timeout, self(), {cancel_start_or_recv, RecvFrom}). + +cancel_timer(undefined) -> + ok; +cancel_timer(Timer) -> + erlang:cancel_timer(Timer), + ok. + +handle_unrecv_data(StateName, #state{socket = Socket, transport_cb = Transport} = State) -> + ssl_socket:setopts(Transport, Socket, [{active, false}]), + case Transport:recv(Socket, 0, 0) of + {error, closed} -> + ok; + {ok, Data} -> + handle_close_alert(Data, StateName, State) + end. + +handle_close_alert(Data, StateName, State0) -> + case next_tls_record(Data, State0) of + {#ssl_tls{type = ?ALERT, fragment = EncAlerts}, State} -> + [Alert|_] = decode_alerts(EncAlerts), + handle_normal_shutdown(Alert, StateName, State); + _ -> + ok + end. +negotiated_hashsign(undefined, Algo, Version) -> + default_hashsign(Version, Algo); +negotiated_hashsign(HashSign = {_, _}, _, _) -> + HashSign. + %% RFC 5246, Sect. 7.4.1.4.1. Signature Algorithms %% If the client does not send the signature_algorithms extension, the %% server MUST do the following: @@ -3022,11 +3052,6 @@ get_pending_connection_state_prf(CStates, Direction) -> %% - If the negotiated key exchange algorithm is one of (ECDH_ECDSA, %% ECDHE_ECDSA), behave as if the client had sent value {sha1,ecdsa}. -negotiated_hashsign(undefined, Algo, Version) -> - default_hashsign(Version, Algo); -negotiated_hashsign(HashSign = {_, _}, _, _) -> - HashSign. - default_hashsign(_Version = {Major, Minor}, KeyExchange) when Major >= 3 andalso Minor >= 3 andalso (KeyExchange == rsa orelse @@ -3062,35 +3087,6 @@ default_hashsign(_Version, KeyExchange) KeyExchange == srp_anon -> {null, anon}. -start_or_recv_cancel_timer(infinity, _RecvFrom) -> - undefined; -start_or_recv_cancel_timer(Timeout, RecvFrom) -> - erlang:send_after(Timeout, self(), {cancel_start_or_recv, RecvFrom}). - -cancel_timer(undefined) -> - ok; -cancel_timer(Timer) -> - erlang:cancel_timer(Timer), - ok. - -handle_unrecv_data(StateName, #state{socket = Socket, transport_cb = Transport} = State) -> - ssl_socket:setopts(Transport, Socket, [{active, false}]), - case Transport:recv(Socket, 0, 0) of - {error, closed} -> - ok; - {ok, Data} -> - handle_close_alert(Data, StateName, State) - end. - -handle_close_alert(Data, StateName, State0) -> - case next_tls_record(Data, State0) of - {#ssl_tls{type = ?ALERT, fragment = EncAlerts}, State} -> - [Alert|_] = decode_alerts(EncAlerts), - handle_normal_shutdown(Alert, StateName, State); - _ -> - ok - end. - select_curve(#state{client_ecc = {[Curve|_], _}}) -> {namedCurve, Curve}; select_curve(_) -> -- cgit v1.2.3 From fa8b8cd60406ddcb2781f27e291949a8698c2886 Mon Sep 17 00:00:00 2001 From: Ingela Anderton Andin Date: Wed, 4 Sep 2013 12:19:09 +0200 Subject: ssl: Dialyzer fixes --- lib/ssl/src/ssl_cipher.erl | 10 +++++----- lib/ssl/src/ssl_cipher.hrl | 2 +- lib/ssl/src/ssl_handshake.erl | 25 ++++++++++++++----------- lib/ssl/src/tls_handshake.erl | 4 ++-- 4 files changed, 22 insertions(+), 19 deletions(-) (limited to 'lib/ssl/src') diff --git a/lib/ssl/src/ssl_cipher.erl b/lib/ssl/src/ssl_cipher.erl index d958b74e9f..3fe4df99c9 100644 --- a/lib/ssl/src/ssl_cipher.erl +++ b/lib/ssl/src/ssl_cipher.erl @@ -1192,15 +1192,15 @@ hash_size(md5) -> hash_size(sha) -> 20; %% Uncomment when adding cipher suite that needs it -hash_size(sha224) -> - 28; +%hash_size(sha224) -> +% 28; hash_size(sha256) -> 32; hash_size(sha384) -> - 48; + 48. %% Uncomment when adding cipher suite that needs it -hash_size(sha512) -> - 64. +%hash_size(sha512) -> +% 64. %% RFC 5246: 6.2.3.2. CBC Block Cipher %% diff --git a/lib/ssl/src/ssl_cipher.hrl b/lib/ssl/src/ssl_cipher.hrl index c7c71ee1a7..8417564d41 100644 --- a/lib/ssl/src/ssl_cipher.hrl +++ b/lib/ssl/src/ssl_cipher.hrl @@ -28,7 +28,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 | ssh224 | sha256 | sha384 | sha512. +-type hash() :: null | sha | md5 | sha224 | sha256 | sha384 | sha512. -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 bef42d9071..189e5e7051 100644 --- a/lib/ssl/src/ssl_handshake.erl +++ b/lib/ssl/src/ssl_handshake.erl @@ -229,15 +229,15 @@ key_exchange(client, _Version, {psk_premaster_secret, PskIdentity, Secret, {_, P EncPremasterSecret = encrypted_premaster_secret(Secret, PublicKey), #client_key_exchange{ - exchange_keys = #client_rsa_psk_identity{ - identity = PskIdentity, - exchange_keys = EncPremasterSecret}}; + exchange_keys = #client_rsa_psk_identity{ + identity = PskIdentity, + exchange_keys = EncPremasterSecret}}; key_exchange(client, _Version, {srp, PublicKey}) -> #client_key_exchange{ - exchange_keys = #client_srp_public{ - srp_a = PublicKey} - }; + exchange_keys = #client_srp_public{ + srp_a = PublicKey} + }; key_exchange(server, Version, {dh, {PublicKey, _}, #'DHParameter'{prime = P, base = G}, @@ -441,7 +441,7 @@ prf({3,1}, Secret, Label, Seed, WantedLength) -> prf({3,_N}, Secret, Label, Seed, WantedLength) -> {ok, tls_v1:prf(?SHA256, Secret, Label, Seed, WantedLength)}. %%-------------------------------------------------------------------- --spec select_hashsign(#hash_sign_algos{}| undefined, undefined | term()) -> +-spec select_hashsign(#hash_sign_algos{}| undefined, undefined | binary()) -> [{atom(), atom()}] | undefined. %% @@ -472,11 +472,14 @@ select_hashsign(#hash_sign_algos{hash_sign_algos = HashSigns}, Cert) -> HashSign end. %%-------------------------------------------------------------------- --spec select_cert_hashsign(#hash_sign_algos{}| undefined, oid(), tls_version()) -> - [{atom(), atom()}]. +-spec select_cert_hashsign(#hash_sign_algos{}| undefined, oid(), tls_version() | {undefined, undefined}) -> + {atom(), atom()}. %% -%% Description: +%% Description: For TLS 1.2 selected cert_hash_sign will be recived +%% in the handshake message, for previous versions use appropriate defaults. +%% This function is also used by select_hashsign to extract +%% the alogrithm of the server cert key. %%-------------------------------------------------------------------- select_cert_hashsign(HashSign, _, {Major, Minor}) when HashSign =/= undefined andalso Major >= 3 andalso Minor >= 3 -> HashSign; @@ -765,7 +768,7 @@ decode_handshake(_, _, _) -> throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE)). %%-------------------------------------------------------------------- --spec decode_hello_extensions(binary()) -> #hello_extensions{}. +-spec decode_hello_extensions({client, binary()} | binary()) -> #hello_extensions{}. %% %% Description: Decodes TLS hello extensions %%-------------------------------------------------------------------- diff --git a/lib/ssl/src/tls_handshake.erl b/lib/ssl/src/tls_handshake.erl index fef1464c64..0f1d544821 100644 --- a/lib/ssl/src/tls_handshake.erl +++ b/lib/ssl/src/tls_handshake.erl @@ -91,7 +91,7 @@ server_hello(SessionId, Version, ConnectionStates, Extensions) -> %%-------------------------------------------------------------------- -spec hello(#server_hello{} | #client_hello{}, #ssl_options{}, #connection_states{} | {inet:port_number(), #session{}, db_handle(), - atom(), #connection_states{}, binary()}, + atom(), #connection_states{}, binary() | undefined}, boolean()) -> {tls_version(), session_id(), #connection_states{}, binary() | undefined}| {tls_version(), {resumed | new, #session{}}, #connection_states{}, @@ -264,7 +264,7 @@ enc_handshake(#client_hello{client_version = {Major, Minor}, ExtensionsBin = ssl_handshake:encode_hello_extensions(HelloExtensions), {?CLIENT_HELLO, <>}; -- cgit v1.2.3 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/src') 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