aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--lib/ssl/src/dtls_connection.erl408
-rw-r--r--lib/ssl/src/dtls_connection.hrl1
-rw-r--r--lib/ssl/src/dtls_handshake.erl208
-rw-r--r--lib/ssl/src/dtls_record.erl115
-rw-r--r--lib/ssl/src/ssl_connection.erl44
-rw-r--r--lib/ssl/src/ssl_connection.hrl7
-rw-r--r--lib/ssl/src/ssl_handshake.erl13
-rw-r--r--lib/ssl/src/ssl_record.erl18
-rw-r--r--lib/ssl/src/ssl_record.hrl2
-rw-r--r--lib/ssl/src/tls_connection.erl50
-rw-r--r--lib/ssl/src/tls_handshake.erl14
11 files changed, 564 insertions, 316 deletions
diff --git a/lib/ssl/src/dtls_connection.erl b/lib/ssl/src/dtls_connection.erl
index 60a61bc901..b8be686b99 100644
--- a/lib/ssl/src/dtls_connection.erl
+++ b/lib/ssl/src/dtls_connection.erl
@@ -42,9 +42,8 @@
-export([next_record/1, next_event/3]).
%% Handshake handling
--export([%%renegotiate/2,
- send_handshake/2, send_change_cipher/2]).
-
+-export([%%renegotiate/2,
+ send_handshake/2, queue_handshake/2, queue_change_cipher/2]).
%% Alert and close handling
-export([%%send_alert/2, handle_own_alert/4, handle_close_alert/3,
@@ -53,11 +52,12 @@
]).
%% Data handling
+
-export([%%write_application_data/3,
read_application_data/2,
- %%passive_receive/2,
- next_record_if_active/1 %%,
- %%handle_common_event/4
+ passive_receive/2, next_record_if_active/1%,
+ %%handle_common_event/4,
+ %handle_packet/3
]).
%% gen_statem state functions
@@ -72,13 +72,13 @@
%%====================================================================
%% Internal application API
%%====================================================================
-start_fsm(Role, Host, Port, Socket, {#ssl_options{erl_dist = false},_} = Opts,
+start_fsm(Role, Host, Port, Socket, {#ssl_options{erl_dist = false},_, Tracker} = Opts,
User, {CbModule, _,_, _} = CbInfo,
Timeout) ->
try
{ok, Pid} = dtls_connection_sup:start_child([Role, Host, Port, Socket,
Opts, User, CbInfo]),
- {ok, SslSocket} = ssl_connection:socket_control(?MODULE, Socket, Pid, CbModule),
+ {ok, SslSocket} = ssl_connection:socket_control(?MODULE, Socket, Pid, CbModule, Tracker),
ok = ssl_connection:handshake(SslSocket, Timeout),
{ok, SslSocket}
catch
@@ -86,13 +86,13 @@ start_fsm(Role, Host, Port, Socket, {#ssl_options{erl_dist = false},_} = Opts,
Error
end;
-start_fsm(Role, Host, Port, Socket, {#ssl_options{erl_dist = true},_} = Opts,
+start_fsm(Role, Host, Port, Socket, {#ssl_options{erl_dist = true},_, Tracker} = Opts,
User, {CbModule, _,_, _} = CbInfo,
Timeout) ->
try
{ok, Pid} = dtls_connection_sup:start_child_dist([Role, Host, Port, Socket,
Opts, User, CbInfo]),
- {ok, SslSocket} = ssl_connection:socket_control(?MODULE, Socket, Pid, CbModule),
+ {ok, SslSocket} = ssl_connection:socket_control(?MODULE, Socket, Pid, CbModule, Tracker),
ok = ssl_connection:handshake(SslSocket, Timeout),
{ok, SslSocket}
catch
@@ -100,14 +100,37 @@ start_fsm(Role, Host, Port, Socket, {#ssl_options{erl_dist = true},_} = Opts,
Error
end.
-send_handshake(Handshake, #state{negotiated_version = Version,
- tls_handshake_history = Hist0,
- connection_states = ConnectionStates0} = State0) ->
- {BinHandshake, ConnectionStates, Hist} =
+send_handshake(Handshake, State) ->
+ send_handshake_flight(queue_handshake(Handshake, State)).
+
+queue_flight_buffer(Msg, #state{negotiated_version = Version,
+ connection_states = #connection_states{
+ current_write =
+ #connection_state{epoch = Epoch}},
+ flight_buffer = Flight} = State) ->
+ State#state{flight_buffer = Flight ++ [{Version, Epoch, Msg}]}.
+
+queue_handshake(Handshake, #state{negotiated_version = Version,
+ tls_handshake_history = Hist0,
+ connection_states = ConnectionStates0} = State0) ->
+ {Frag, ConnectionStates, Hist} =
encode_handshake(Handshake, Version, ConnectionStates0, Hist0),
- send_flight(BinHandshake, State0#state{connection_states = ConnectionStates,
- tls_handshake_history = Hist
- }).
+ queue_flight_buffer(Frag, State0#state{connection_states = ConnectionStates,
+ tls_handshake_history = Hist}).
+
+send_handshake_flight(#state{socket = Socket,
+ transport_cb = Transport,
+ flight_buffer = Flight,
+ connection_states = ConnectionStates0} = State0) ->
+
+ {Encoded, ConnectionStates} =
+ encode_handshake_flight(Flight, ConnectionStates0),
+
+ Transport:send(Socket, Encoded),
+ State0#state{flight_buffer = [], connection_states = ConnectionStates}.
+
+queue_change_cipher(Msg, State) ->
+ queue_flight_buffer(Msg, State).
send_alert(Alert, #state{negotiated_version = Version,
socket = Socket,
@@ -118,15 +141,6 @@ send_alert(Alert, #state{negotiated_version = Version,
Transport:send(Socket, BinMsg),
State0#state{connection_states = ConnectionStates}.
-send_change_cipher(Msg, #state{connection_states = ConnectionStates0,
- socket = Socket,
- negotiated_version = Version,
- transport_cb = Transport} = State0) ->
- {BinChangeCipher, ConnectionStates} =
- encode_change_cipher(Msg, Version, ConnectionStates0),
- Transport:send(Socket, BinChangeCipher),
- State0#state{connection_states = ConnectionStates}.
-
%%====================================================================
%% tls_connection_sup API
%%====================================================================
@@ -196,28 +210,44 @@ error({call, From}, Msg, State) ->
error(_, _, _) ->
{keep_state_and_data, [postpone]}.
-hello(internal, #client_hello{client_version = ClientVersion} = Hello,
- #state{connection_states = ConnectionStates0,
- port = Port, session = #session{own_certificate = Cert} = Session0,
- renegotiation = {Renegotiation, _},
- session_cache = Cache,
- session_cache_cb = CacheCb,
- ssl_options = SslOpts} = State) ->
+%%--------------------------------------------------------------------
+-spec hello(gen_statem:event_type(),
+ #hello_request{} | #client_hello{} | #server_hello{} | term(),
+ #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+hello(internal, #client_hello{client_version = ClientVersion,
+ extensions = #hello_extensions{ec_point_formats = EcPointFormats,
+ elliptic_curves = EllipticCurves}} = Hello,
+ State = #state{connection_states = ConnectionStates0,
+ port = Port, session = #session{own_certificate = Cert} = Session0,
+ renegotiation = {Renegotiation, _},
+ session_cache = Cache,
+ session_cache_cb = CacheCb,
+ negotiated_protocol = CurrentProtocol,
+ key_algorithm = KeyExAlg,
+ ssl_options = SslOpts}) ->
+
case dtls_handshake:hello(Hello, SslOpts, {Port, Session0, Cache, CacheCb,
- ConnectionStates0, Cert}, Renegotiation) of
- {Version, {Type, Session},
- ConnectionStates,
- #hello_extensions{ec_point_formats = EcPointFormats,
- elliptic_curves = EllipticCurves} = ServerHelloExt, HashSign} ->
- ssl_connection:hello(internal, {common_client_hello, Type, ServerHelloExt, HashSign},
- State#state{connection_states = ConnectionStates,
+ ConnectionStates0, Cert, KeyExAlg}, Renegotiation) of
+ #alert{} = Alert ->
+ handle_own_alert(Alert, ClientVersion, hello, State);
+ {Version, {Type, Session},
+ ConnectionStates, Protocol0, ServerHelloExt, HashSign} ->
+ Protocol = case Protocol0 of
+ undefined -> CurrentProtocol;
+ _ -> Protocol0
+ end,
+
+ ssl_connection:hello(internal, {common_client_hello, Type, ServerHelloExt},
+ State#state{connection_states = ConnectionStates,
negotiated_version = Version,
+ hashsign_algorithm = HashSign,
session = Session,
- client_ecc = {EllipticCurves, EcPointFormats}}, ?MODULE);
- #alert{} = Alert ->
- handle_own_alert(Alert, ClientVersion, hello, State)
+ client_ecc = {EllipticCurves, EcPointFormats},
+ negotiated_protocol = Protocol}, ?MODULE)
end;
-hello(internal, Hello,
+hello(internal, #server_hello{} = Hello,
#state{connection_states = ConnectionStates0,
negotiated_version = ReqVersion,
role = client,
@@ -261,8 +291,7 @@ connection(internal, #hello_request{}, #state{host = Host, port = Port,
renegotiation = {Renegotiation, _}} = State0) ->
Hello = dtls_handshake:client_hello(Host, Port, ConnectionStates0, SslOpts,
Cache, CacheCb, Renegotiation, Cert),
- %% TODO DTLS version State1 = send_handshake(Hello, State0),
- State1 = State0,
+ State1 = send_handshake(Hello, State0),
{Record, State} =
next_record(
State1#state{session = Session0#session{session_id
@@ -309,11 +338,24 @@ handle_info({Protocol, _, Data}, StateName,
{stop, {shutdown, own_alert}}
end;
handle_info({CloseTag, Socket}, StateName,
- #state{socket = Socket, close_tag = CloseTag,
- negotiated_version = _Version} = State) ->
+ #state{socket = Socket, close_tag = CloseTag,
+ negotiated_version = Version} = State) ->
+ %% Note that as of DTLS 1.2 (TLS 1.1),
+ %% failure to properly close a connection no longer requires that a
+ %% session not be resumed. This is a change from DTLS 1.0 to conform
+ %% with widespread implementation practice.
+ case Version of
+ {254, N} when N =< 253 ->
+ ok;
+ _ ->
+ %% As invalidate_sessions here causes performance issues,
+ %% we will conform to the widespread implementation
+ %% practice and go aginst the spec
+ %%invalidate_session(Role, Host, Port, Session)
+ ok
+ end,
handle_normal_shutdown(?ALERT_REC(?FATAL, ?CLOSE_NOTIFY), StateName, State),
{stop, {shutdown, transport_closed}};
-
handle_info(Msg, StateName, State) ->
ssl_connection:handle_info(Msg, StateName, State).
@@ -343,76 +385,82 @@ format_status(Type, Data) ->
%%% Internal functions
%%--------------------------------------------------------------------
encode_handshake(Handshake, Version, ConnectionStates0, Hist0) ->
- Seq = sequence(ConnectionStates0),
- {EncHandshake, FragmentedHandshake} = dtls_handshake:encode_handshake(Handshake, Version,
- Seq),
+ {Seq, ConnectionStates} = sequence(ConnectionStates0),
+ {EncHandshake, Frag} = dtls_handshake:encode_handshake(Handshake, Version, Seq),
Hist = ssl_handshake:update_handshake_history(Hist0, EncHandshake),
- {Encoded, ConnectionStates} =
- dtls_record:encode_handshake(FragmentedHandshake,
- Version, ConnectionStates0),
- {Encoded, ConnectionStates, Hist}.
-
-next_record(#state{%%flight = #flight{state = finished},
- protocol_buffers =
- #protocol_buffers{dtls_packets = [], dtls_cipher_texts = [CT | Rest]}
- = Buffers,
- connection_states = ConnStates0} = State) ->
- case dtls_record:decode_cipher_text(CT, ConnStates0) of
- {Plain, ConnStates} ->
- {Plain, State#state{protocol_buffers =
- Buffers#protocol_buffers{dtls_cipher_texts = Rest},
- connection_states = ConnStates}};
- #alert{} = Alert ->
- {Alert, State}
- end;
-next_record(#state{socket = Socket,
- transport_cb = Transport} = State) -> %% when FlightState =/= finished
- ssl_socket:setopts(Transport, Socket, [{active,once}]),
- {no_record, State};
-
+ {Frag, ConnectionStates, Hist}.
-next_record(State) ->
- {no_record, State}.
+encode_change_cipher(#change_cipher_spec{}, Version, ConnectionStates) ->
+ dtls_record:encode_change_cipher_spec(Version, ConnectionStates).
+encode_handshake_flight(Flight, ConnectionStates) ->
+ MSS = 1400,
+ encode_handshake_records(Flight, ConnectionStates, MSS, init_pack_records()).
-next_event(StateName, Record, State) ->
- next_event(StateName, Record, State, []).
+encode_handshake_records([], CS, _MSS, Recs) ->
+ {finish_pack_records(Recs), CS};
-next_event(connection = StateName, no_record, State0, Actions) ->
- case next_record_if_active(State0) of
- {no_record, State} ->
- ssl_connection:hibernate_after(StateName, State, Actions);
- {#ssl_tls{} = Record, State} ->
- {next_state, StateName, State, [{next_event, internal, {dtls_record, Record}} | Actions]};
- {#alert{} = Alert, State} ->
- {next_state, StateName, State, [{next_event, internal, Alert} | Actions]}
+encode_handshake_records([{Version, _Epoch, Frag = #change_cipher_spec{}}|Tail], ConnectionStates0, MSS, Recs0) ->
+ {Encoded, ConnectionStates} =
+ encode_change_cipher(Frag, Version, ConnectionStates0),
+ Recs = append_pack_records([Encoded], MSS, Recs0),
+ encode_handshake_records(Tail, ConnectionStates, MSS, Recs);
+
+encode_handshake_records([{Version, Epoch, {MsgType, MsgSeq, Bin}}|Tail], CS0, MSS, Recs0 = {Buf0, _}) ->
+ Space = MSS - iolist_size(Buf0),
+ Len = byte_size(Bin),
+ {Encoded, CS} =
+ encode_handshake_record(Version, Epoch, Space, MsgType, MsgSeq, Len, Bin, 0, MSS, [], CS0),
+ Recs = append_pack_records(Encoded, MSS, Recs0),
+ encode_handshake_records(Tail, CS, MSS, Recs).
+
+%% TODO: move to dtls_handshake????
+encode_handshake_record(_Version, _Epoch, _Space, _MsgType, _MsgSeq, _Len, <<>>, _Offset, _MRS, Encoded, CS)
+ when length(Encoded) > 0 ->
+ %% make sure we encode at least one segment (for empty messages like Server Hello Done
+ {lists:reverse(Encoded), CS};
+
+encode_handshake_record(Version, Epoch, Space, MsgType, MsgSeq, Len, Bin,
+ Offset, MRS, Encoded0, CS0) ->
+ MaxFragmentLen = Space - 25,
+ case Bin of
+ <<BinFragment:MaxFragmentLen/bytes, Rest/binary>> ->
+ ok;
+ _ ->
+ BinFragment = Bin,
+ Rest = <<>>
+ end,
+ FragLength = byte_size(BinFragment),
+ Frag = [MsgType, ?uint24(Len), ?uint16(MsgSeq), ?uint24(Offset), ?uint24(FragLength), BinFragment],
+ %% TODO Real solution, now avoid dialyzer error {Encoded, CS} = ssl_record:encode_handshake({Epoch, Frag}, Version, CS0),
+ {Encoded, CS} = ssl_record:encode_handshake(Frag, Version, CS0),
+ encode_handshake_record(Version, Epoch, MRS, MsgType, MsgSeq, Len, Rest, Offset + FragLength, MRS, [Encoded|Encoded0], CS).
+
+init_pack_records() ->
+ {[], []}.
+
+append_pack_records([], MSS, Recs = {Buf0, Acc0}) ->
+ Remaining = MSS - iolist_size(Buf0),
+ if Remaining < 12 ->
+ {[], [lists:reverse(Buf0)|Acc0]};
+ true ->
+ Recs
end;
-next_event(StateName, Record, State, Actions) ->
- case Record of
- no_record ->
- {next_state, StateName, State, Actions};
- #ssl_tls{} = Record ->
- {next_state, StateName, State, [{next_event, internal, {dtls_record, Record}} | Actions]};
- #alert{} = Alert ->
- {next_state, StateName, State, [{next_event, internal, Alert} | Actions]}
+append_pack_records([Head|Tail], MSS, {Buf0, Acc0}) ->
+ TotLen = iolist_size(Buf0) + iolist_size(Head),
+ if TotLen > MSS ->
+ append_pack_records(Tail, MSS, {[Head], [lists:reverse(Buf0)|Acc0]});
+ true ->
+ append_pack_records(Tail, MSS, {[Head|Buf0], Acc0})
end.
-send_flight(Fragments, #state{transport_cb = Transport, socket = Socket,
- protocol_buffers = _PBuffers} = State) ->
- Transport:send(Socket, Fragments),
- %% Start retransmission
- %% State#state{protocol_buffers =
- %% (PBuffers#protocol_buffers){ #flight{state = waiting}}}}.
- State.
+finish_pack_records({[], Acc}) ->
+ lists:reverse(Acc);
+finish_pack_records({Buf, Acc}) ->
+ lists:reverse([lists:reverse(Buf)|Acc]).
-handle_own_alert(_,_,_, State) -> %% Place holder
- {stop, {shutdown, own_alert}, State}.
-
-handle_normal_shutdown(_, _, _State) -> %% Place holder
- ok.
-
-encode_change_cipher(#change_cipher_spec{}, Version, ConnectionStates) ->
- dtls_record:encode_change_cipher_spec(Version, ConnectionStates).
+%% decode_alerts(Bin) ->
+%% ssl_alert:decode(Bin).
initial_state(Role, Host, Port, Socket, {SSLOptions, SocketOptions}, User,
{CbModule, DataTag, CloseTag, ErrorTag}) ->
@@ -451,21 +499,137 @@ initial_state(Role, Host, Port, Socket, {SSLOptions, SocketOptions}, User,
start_or_recv_from = undefined,
protocol_cb = ?MODULE
}.
-read_application_data(_,State) ->
- {#ssl_tls{fragment = <<"place holder">>}, State}.
-next_tls_record(<<>>, _State) ->
- #alert{}; %% Place holder
-next_tls_record(_, State) ->
- {#ssl_tls{fragment = <<"place holder">>}, State}.
+next_tls_record(Data, #state{protocol_buffers = #protocol_buffers{
+ dtls_record_buffer = Buf0,
+ dtls_cipher_texts = CT0} = Buffers} = State0) ->
+ case dtls_record:get_dtls_records(Data, Buf0) of
+ {Records, Buf1} ->
+ CT1 = CT0 ++ Records,
+ next_record(State0#state{protocol_buffers =
+ Buffers#protocol_buffers{dtls_record_buffer = Buf1,
+ dtls_cipher_texts = CT1}});
+
+ #alert{} = Alert ->
+ Alert
+ end.
+
+next_record(#state{%%flight = #flight{state = finished},
+ protocol_buffers =
+ #protocol_buffers{dtls_packets = [], dtls_cipher_texts = [CT | Rest]}
+ = Buffers,
+ connection_states = ConnStates0} = State) ->
+ case dtls_record:decode_cipher_text(CT, ConnStates0) of
+ {Plain, ConnStates} ->
+ {Plain, State#state{protocol_buffers =
+ Buffers#protocol_buffers{dtls_cipher_texts = Rest},
+ connection_states = ConnStates}};
+ #alert{} = Alert ->
+ {Alert, State}
+ end;
+next_record(#state{socket = Socket,
+ transport_cb = Transport} = State) -> %% when FlightState =/= finished
+ ssl_socket:setopts(Transport, Socket, [{active,once}]),
+ {no_record, State};
+next_record(State) ->
+ {no_record, State}.
-sequence(_) ->
- %%TODO real imp
- 1.
-next_record_if_active(State =
- #state{socket_options =
- #socket_options{active = false}}) ->
+next_record_if_active(State =
+ #state{socket_options =
+ #socket_options{active = false}}) ->
{no_record ,State};
next_record_if_active(State) ->
next_record(State).
+
+passive_receive(State0 = #state{user_data_buffer = Buffer}, StateName) ->
+ case Buffer of
+ <<>> ->
+ {Record, State} = next_record(State0),
+ next_event(StateName, Record, State);
+ _ ->
+ {Record, State} = read_application_data(<<>>, State0),
+ next_event(StateName, Record, State)
+ end.
+
+next_event(StateName, Record, State) ->
+ next_event(StateName, Record, State, []).
+
+next_event(connection = StateName, no_record, State0, Actions) ->
+ case next_record_if_active(State0) of
+ {no_record, State} ->
+ ssl_connection:hibernate_after(StateName, State, Actions);
+ {#ssl_tls{} = Record, State} ->
+ {next_state, StateName, State, [{next_event, internal, {dtls_record, Record}} | Actions]};
+ {#alert{} = Alert, State} ->
+ {next_state, StateName, State, [{next_event, internal, Alert} | Actions]}
+ end;
+next_event(StateName, Record, State, Actions) ->
+ case Record of
+ no_record ->
+ {next_state, StateName, State, Actions};
+ #ssl_tls{} = Record ->
+ {next_state, StateName, State, [{next_event, internal, {dtls_record, Record}} | Actions]};
+ #alert{} = Alert ->
+ {next_state, StateName, State, [{next_event, internal, Alert} | Actions]}
+ end.
+
+read_application_data(_,State) ->
+ {#ssl_tls{fragment = <<"place holder">>}, State}.
+
+handle_own_alert(_,_,_, State) -> %% Place holder
+ {stop, {shutdown, own_alert}, State}.
+
+handle_normal_shutdown(_, _, _State) -> %% Place holder
+ ok.
+
+%% TODO This generates dialyzer warnings, has to be handled differently.
+%% handle_packet(Address, Port, Packet) ->
+%% try dtls_record:get_dtls_records(Packet, <<>>) of
+%% %% expect client hello
+%% {[#ssl_tls{type = ?HANDSHAKE, version = {254, _}} = Record], <<>>} ->
+%% handle_dtls_client_hello(Address, Port, Record);
+%% _Other ->
+%% {error, not_dtls}
+%% catch
+%% _Class:_Error ->
+%% {error, not_dtls}
+%% end.
+
+%% handle_dtls_client_hello(Address, Port,
+%% #ssl_tls{epoch = Epoch, sequence_number = Seq,
+%% version = Version} = Record) ->
+%% {[{Hello, _}], _} =
+%% dtls_handshake:get_dtls_handshake(Record,
+%% dtls_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),
+%% <<?BYTE(Major), ?BYTE(Minor)>>,
+%% Random, SessionId, CipherSuites, CompressionMethods],
+%% Cookie = crypto:hmac(sha, <<"secret">>, CookieData),
+
+%% case Hello of
+%% #client_hello{cookie = Cookie} ->
+%% accept;
+
+%% _ ->
+%% %% generate HelloVerifyRequest
+%% {RequestFragment, _} = dtls_handshake:encode_handshake(
+%% dtls_handshake:hello_verify_request(Cookie),
+%% Version, 0),
+%% HelloVerifyRequest =
+%% dtls_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) ->
+%% <<A:16,B:16,C:16,D:16,E:16,F:16,G:16,H:16,Port:16>>.
+
+sequence(#connection_states{dtls_write_msg_seq = Seq} = CS) ->
+ {Seq, CS#connection_states{dtls_write_msg_seq = Seq + 1}}.
diff --git a/lib/ssl/src/dtls_connection.hrl b/lib/ssl/src/dtls_connection.hrl
index 69137b520b..ee3daa3c14 100644
--- a/lib/ssl/src/dtls_connection.hrl
+++ b/lib/ssl/src/dtls_connection.hrl
@@ -31,6 +31,7 @@
-record(protocol_buffers, {
dtls_packets = [], %%::[binary()], % Not yet handled decode ssl/tls packets.
dtls_record_buffer = <<>>, %%:: binary(), % Buffer of incomplete records
+ dtls_fragment_state, %%:: [], % DTLS fragments
dtls_handshake_buffer = <<>>, %%:: binary(), % Buffer of incomplete handshakes
dtls_cipher_texts = [], %%:: [binary()],
dtls_cipher_texts_next %%:: [binary()] % Received for Epoch not yet active
diff --git a/lib/ssl/src/dtls_handshake.erl b/lib/ssl/src/dtls_handshake.erl
index 4f48704cac..5a799cf441 100644
--- a/lib/ssl/src/dtls_handshake.erl
+++ b/lib/ssl/src/dtls_handshake.erl
@@ -24,8 +24,8 @@
-include("ssl_alert.hrl").
-export([client_hello/8, client_hello/9, hello/4,
- get_dtls_handshake/2,
- %%dtls_handshake_new_flight/1, dtls_handshake_new_epoch/1,
+ hello_verify_request/1, get_dtls_handshake/2,
+ dtls_handshake_new_flight/1, dtls_handshake_new_epoch/1,
encode_handshake/3]).
-type dtls_handshake() :: #client_hello{} | #hello_verify_request{} |
@@ -92,96 +92,135 @@ hello(#server_hello{server_version = Version, random = Random,
?ALERT_REC(?FATAL, ?PROTOCOL_VERSION)
end;
-hello(#client_hello{client_version = ClientVersion}, _Options, {_,_,_,_,ConnectionStates,_}, _Renegotiation) ->
- %% Return correct typ to make dialyzer happy until we have time to make the real imp.
- HashSigns = tls_v1:default_signature_algs(dtls_v1:corresponding_tls_version(ClientVersion)),
- {ClientVersion, {new, #session{}}, ConnectionStates, #hello_extensions{},
- %% Placeholder for real hasign handling
- hd(HashSigns)}.
-
-%% hello(Address, Port,
-%% #ssl_tls{epoch = _Epoch, sequence_number = _Seq,
-%% version = Version} = Record) ->
-%% case get_dtls_handshake(Record,
-%% dtls_handshake_new_flight(undefined)) of
-%% {[Hello | _], _} ->
-%% hello(Address, Port, Version, Hello);
-%% {retransmit, HandshakeState} ->
-%% {retransmit, HandshakeState}
-%% end.
-
-%% hello(Address, Port, Version, Hello) ->
-%% #client_hello{client_version = {Major, Minor},
-%% random = Random,
-%% session_id = SessionId,
-%% cipher_suites = CipherSuites,
-%% compression_methods = CompressionMethods} = Hello,
-%% CookieData = [address_to_bin(Address, Port),
-%% <<?BYTE(Major), ?BYTE(Minor)>>,
-%% Random, SessionId, CipherSuites, CompressionMethods],
-%% Cookie = crypto:hmac(sha, <<"secret">>, CookieData),
-
-%% case Hello of
-%% #client_hello{cookie = Cookie} ->
-%% accept;
-%% _ ->
-%% %% generate HelloVerifyRequest
-%% HelloVerifyRequest = enc_hs(#hello_verify_request{protocol_version = Version,
-%% cookie = Cookie},
-%% Version, 0, 1400),
-%% {reply, HelloVerifyRequest}
-%% end.
+hello(#client_hello{client_version = ClientVersion} = Hello,
+ #ssl_options{versions = Versions} = SslOpts,
+ Info, Renegotiation) ->
+ Version = ssl_handshake:select_version(dtls_record, ClientVersion, Versions),
+ %%
+ %% TODO: handle Cipher Fallback
+ %%
+ handle_client_hello(Version, Hello, SslOpts, Info, Renegotiation).
+
+-spec hello_verify_request(binary()) -> #hello_verify_request{}.
+%%
+%% Description: Creates a hello verify request message sent by server to
+%% verify client
+%%--------------------------------------------------------------------
+hello_verify_request(Cookie) ->
+ %% TODO: DTLS Versions?????
+ #hello_verify_request{protocol_version = {254, 255}, cookie = Cookie}.
+
+%%--------------------------------------------------------------------
%% %%--------------------------------------------------------------------
encode_handshake(Handshake, Version, MsgSeq) ->
{MsgType, Bin} = enc_handshake(Handshake, Version),
Len = byte_size(Bin),
- EncHandshake = [MsgType, ?uint24(Len), ?uint16(MsgSeq), ?uint24(0), ?uint24(Len), Bin],
- FragmentedHandshake = dtls_fragment(erlang:iolist_size(EncHandshake), MsgType, Len, MsgSeq, Bin, 0, []),
- {EncHandshake, FragmentedHandshake}.
+ Enc = [MsgType, ?uint24(Len), ?uint16(MsgSeq), ?uint24(0), ?uint24(Len), Bin],
+ Frag = {MsgType, MsgSeq, Bin},
+ {Enc, Frag}.
%%--------------------------------------------------------------------
--spec get_dtls_handshake(#ssl_tls{}, #dtls_hs_state{} | binary()) ->
+-spec get_dtls_handshake(#ssl_tls{}, #dtls_hs_state{} | undefined) ->
{[dtls_handshake()], #dtls_hs_state{}} | {retransmit, #dtls_hs_state{}}.
%%
%% 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 handshake state!?
-get_dtls_handshake(Record, HsState) ->
- get_dtls_handshake_aux(Record, HsState).
+get_dtls_handshake(Records, undefined) ->
+ HsState = #dtls_hs_state{highest_record_seq = 0,
+ starting_read_seq = 0,
+ fragments = gb_trees:empty(),
+ completed = []},
+ get_dtls_handshake(Records, HsState);
+get_dtls_handshake(Records, HsState0) when is_list(Records) ->
+ HsState1 = lists:foldr(fun get_dtls_handshake_aux/2, HsState0, Records),
+ get_dtls_handshake_completed(HsState1);
+get_dtls_handshake(Record, HsState0) when is_record(Record, ssl_tls) ->
+ HsState1 = get_dtls_handshake_aux(Record, HsState0),
+ get_dtls_handshake_completed(HsState1).
-%% %%--------------------------------------------------------------------
-%% -spec dtls_handshake_new_epoch(#dtls_hs_state{}) -> #dtls_hs_state{}.
-%% %%
-%% %% Description: Reset the DTLS decoder state for a new Epoch
-%% %%--------------------------------------------------------------------
+%%--------------------------------------------------------------------
+-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 = []}.
+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
%%--------------------------------------------------------------------
+handle_client_hello(Version, #client_hello{session_id = SugesstedId,
+ cipher_suites = CipherSuites,
+ compression_methods = Compressions,
+ random = Random,
+ extensions = #hello_extensions{elliptic_curves = Curves,
+ signature_algs = ClientHashSigns} = HelloExt},
+ #ssl_options{versions = Versions,
+ signature_algs = SupportedHashSigns} = SslOpts,
+ {Port, Session0, Cache, CacheCb, ConnectionStates0, Cert, _}, Renegotiation) ->
+ case dtls_record:is_acceptable_version(Version, Versions) of
+ true ->
+ AvailableHashSigns = ssl_handshake:available_signature_algs(
+ ClientHashSigns, SupportedHashSigns, Cert,
+ dtls_v1:corresponding_tls_version(Version)),
+ ECCCurve = ssl_handshake:select_curve(Curves, ssl_handshake:supported_ecc(Version)),
+ {Type, #session{cipher_suite = CipherSuite} = Session1}
+ = ssl_handshake:select_session(SugesstedId, CipherSuites, AvailableHashSigns, Compressions,
+ Port, Session0#session{ecc = ECCCurve}, Version,
+ SslOpts, Cache, CacheCb, Cert),
+ case CipherSuite of
+ no_suite ->
+ ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY);
+ _ ->
+ {KeyExAlg,_,_,_} = ssl_cipher:suite_definition(CipherSuite),
+ case ssl_handshake:select_hashsign(ClientHashSigns, Cert, KeyExAlg, SupportedHashSigns, Version) of
+ #alert{} = Alert ->
+ Alert;
+ HashSign ->
+ handle_client_hello_extensions(Version, Type, Random, CipherSuites, HelloExt,
+ SslOpts, Session1, ConnectionStates0,
+ Renegotiation, HashSign)
+ end
+ end;
+ false ->
+ ?ALERT_REC(?FATAL, ?PROTOCOL_VERSION)
+ end.
+
+handle_client_hello_extensions(Version, Type, Random, CipherSuites,
+ HelloExt, SslOpts, Session0, ConnectionStates0, Renegotiation, HashSign) ->
+ try ssl_handshake:handle_client_hello_extensions(dtls_record, Random, CipherSuites,
+ HelloExt, dtls_v1:corresponding_tls_version(Version),
+ SslOpts, Session0, ConnectionStates0, Renegotiation) of
+ #alert{} = Alert ->
+ Alert;
+ {Session, ConnectionStates, Protocol, ServerHelloExt} ->
+ {Version, {Type, Session}, ConnectionStates, Protocol, ServerHelloExt, HashSign}
+ catch throw:Alert ->
+ Alert
+ end.
+
handle_server_hello_extensions(Version, SessionId, Random, CipherSuite,
Compression, HelloExt, SslOpt, ConnectionStates0, Renegotiation) ->
case ssl_handshake:handle_server_hello_extensions(dtls_record, Random, CipherSuite,
- Compression, HelloExt, Version,
+ Compression, HelloExt,
+ dtls_v1:corresponding_tls_version(Version),
SslOpt, ConnectionStates0, Renegotiation) of
#alert{} = Alert ->
Alert;
@@ -189,16 +228,8 @@ handle_server_hello_extensions(Version, SessionId, Random, CipherSuite,
{Version, SessionId, ConnectionStates, ProtoExt, Protocol}
end.
-dtls_fragment(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_fragment(Mss, MsgType, Len, MsgSeq, Bin, Offset, Acc) ->
- FragmentLen = Mss - 12,
- <<Fragment:FragmentLen/bytes, Rest/binary>> = Bin,
- BinMsg = [MsgType, ?uint24(Len), ?uint16(MsgSeq), ?uint24(Offset), ?uint24(FragmentLen), Fragment],
- dtls_fragment(Mss, MsgType, Len, MsgSeq, Rest, Offset + FragmentLen, [BinMsg|Acc]).
+get_dtls_handshake_completed(HsState = #dtls_hs_state{completed = Completed}) ->
+ {lists:reverse(Completed), HsState#dtls_hs_state{completed = []}}.
get_dtls_handshake_aux(#ssl_tls{version = Version,
sequence_number = SeqNo,
@@ -214,25 +245,18 @@ get_dtls_handshake_aux(Version, SeqNo,
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{completed = []}}.
+ HsState.
dec_dtls_fragment(Version, SeqNo, Type, Length, MessageSeq, MsgBody,
HsState = #dtls_hs_state{highest_record_seq = HighestSeqNo, completed = Acc}) ->
@@ -299,12 +323,6 @@ reassemble_dtls_fragment(SeqNo, _Type, Length, MessageSeq, 0, Length,
{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;
@@ -419,6 +437,8 @@ enc_handshake(#hello_verify_request{protocol_version = {Major, Minor},
?BYTE(CookieLength),
Cookie/binary>>};
+enc_handshake(#hello_request{}, _Version) ->
+ {?HELLO_REQUEST, <<>>};
enc_handshake(#client_hello{client_version = {Major, Minor},
random = Random,
session_id = SessionID,
diff --git a/lib/ssl/src/dtls_record.erl b/lib/ssl/src/dtls_record.erl
index e79e1cede0..5387fcafa8 100644
--- a/lib/ssl/src/dtls_record.erl
+++ b/lib/ssl/src/dtls_record.erl
@@ -36,11 +36,12 @@
-export([decode_cipher_text/2]).
%% Encoding
--export([encode_plain_text/4, encode_handshake/3, encode_change_cipher_spec/2]).
+-export([encode_plain_text/4, encode_tls_cipher_text/5, encode_change_cipher_spec/2]).
%% Protocol version handling
--export([protocol_version/1, lowest_protocol_version/2, lowest_protocol_version/1,
- highest_protocol_version/1, supported_protocol_versions/0,
+-export([protocol_version/1, lowest_protocol_version/1, lowest_protocol_version/2,
+ highest_protocol_version/1, highest_protocol_version/2,
+ is_higher/2, supported_protocol_versions/0,
is_acceptable_version/2]).
%% DTLS Epoch handling
@@ -208,14 +209,6 @@ decode_cipher_text(#ssl_tls{type = Type, version = Version,
false ->
?ALERT_REC(?FATAL, ?BAD_RECORD_MAC)
end.
-%%--------------------------------------------------------------------
--spec encode_handshake(iolist(), dtls_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_change_cipher_spec(dtls_version(), #connection_states{}) ->
@@ -271,20 +264,39 @@ lowest_protocol_version(Versions) ->
%%
%% Description: Highest protocol version present in a list
%%--------------------------------------------------------------------
-highest_protocol_version([Ver | Vers]) ->
- highest_protocol_version(Ver, Vers).
+highest_protocol_version([]) ->
+ highest_protocol_version();
+highest_protocol_version(Versions) ->
+ [Ver | Vers] = Versions,
+ highest_list_protocol_version(Ver, Vers).
-highest_protocol_version(Version, []) ->
+%%--------------------------------------------------------------------
+-spec highest_protocol_version(dtls_version(), dtls_version()) -> dtls_version().
+%%
+%% Description: Highest protocol version of two given versions
+%%--------------------------------------------------------------------
+highest_protocol_version(Version = {M, N}, {M, O}) when N < O ->
+ Version;
+highest_protocol_version({M, _},
+ Version = {M, _}) ->
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).
+highest_protocol_version(Version = {M,_},
+ {N, _}) when M < N ->
+ Version;
+highest_protocol_version(_,Version) ->
+ Version.
+%%--------------------------------------------------------------------
+-spec is_higher(V1 :: dtls_version(), V2::dtls_version()) -> boolean().
+%%
+%% Description: Is V1 > V2
+%%--------------------------------------------------------------------
+is_higher({M, N}, {M, O}) when N < O ->
+ true;
+is_higher({M, _}, {N, _}) when M < N ->
+ true;
+is_higher(_, _) ->
+ false.
%%--------------------------------------------------------------------
-spec supported_protocol_versions() -> [dtls_version()].
@@ -301,27 +313,33 @@ supported_protocol_versions() ->
{ok, []} ->
lists:map(Fun, supported_protocol_versions([]));
{ok, Vsns} when is_list(Vsns) ->
- supported_protocol_versions(Vsns);
+ supported_protocol_versions(lists:map(Fun, Vsns));
{ok, Vsn} ->
- supported_protocol_versions([Vsn])
+ supported_protocol_versions([Fun(Vsn)])
end.
supported_protocol_versions([]) ->
- Vsns = supported_connection_protocol_versions([]),
+ Vsns = case sufficient_dtlsv1_2_crypto_support() of
+ true ->
+ ?ALL_DATAGRAM_SUPPORTED_VERSIONS;
+ false ->
+ ?MIN_DATAGRAM_SUPPORTED_VERSIONS
+ end,
application:set_env(ssl, dtls_protocol_version, Vsns),
Vsns;
supported_protocol_versions([_|_] = Vsns) ->
- Vsns.
-
-%% highest_protocol_version() ->
-%% highest_protocol_version(supported_protocol_versions()).
-
-lowest_protocol_version() ->
- lowest_protocol_version(supported_protocol_versions()).
-
-supported_connection_protocol_versions([]) ->
- ?ALL_DATAGRAM_SUPPORTED_VERSIONS.
+ case sufficient_dtlsv1_2_crypto_support() of
+ true ->
+ Vsns;
+ false ->
+ case Vsns -- ['dtlsv1.2'] of
+ [] ->
+ ?MIN_SUPPORTED_VERSIONS;
+ NewVsns ->
+ NewVsns
+ end
+ end.
%%--------------------------------------------------------------------
-spec is_acceptable_version(dtls_version(), Supported :: [dtls_version()]) -> boolean().
@@ -419,6 +437,18 @@ set_connection_state_by_epoch(ConnectionStates0 =
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
+
+
+lowest_list_protocol_version(Ver, []) ->
+ Ver;
+lowest_list_protocol_version(Ver1, [Ver2 | Rest]) ->
+ lowest_list_protocol_version(lowest_protocol_version(Ver1, Ver2), Rest).
+
+highest_list_protocol_version(Ver, []) ->
+ Ver;
+highest_list_protocol_version(Ver1, [Ver2 | Rest]) ->
+ highest_list_protocol_version(highest_protocol_version(Ver1, Ver2), Rest).
+
encode_tls_cipher_text(Type, {MajVer, MinVer}, Epoch, Seq, Fragment) ->
Length = erlang:iolist_size(Fragment),
[<<?BYTE(Type), ?BYTE(MajVer), ?BYTE(MinVer), ?UINT16(Epoch),
@@ -432,6 +462,16 @@ calc_mac_hash(#connection_state{mac_secret = MacSecret,
mac_hash(Version, MacAlg, MacSecret, NewSeq, Type,
Length, Fragment).
+highest_protocol_version() ->
+ highest_protocol_version(supported_protocol_versions()).
+
+lowest_protocol_version() ->
+ lowest_protocol_version(supported_protocol_versions()).
+
+sufficient_dtlsv1_2_crypto_support() ->
+ CryptoSupport = crypto:supports(),
+ proplists:get_bool(sha256, proplists:get_value(hashs, CryptoSupport)).
+
mac_hash(Version, MacAlg, MacSecret, SeqNo, Type, Length, Fragment) ->
dtls_v1:mac_hash(Version, MacAlg, MacSecret, SeqNo, Type,
Length, Fragment).
@@ -439,8 +479,3 @@ mac_hash(Version, MacAlg, MacSecret, SeqNo, Type, Length, Fragment) ->
calc_aad(Type, {MajVer, MinVer}, Epoch, SeqNo) ->
NewSeq = (Epoch bsl 48) + SeqNo,
<<NewSeq:64/integer, ?BYTE(Type), ?BYTE(MajVer), ?BYTE(MinVer)>>.
-
-lowest_list_protocol_version(Ver, []) ->
- Ver;
-lowest_list_protocol_version(Ver1, [Ver2 | Rest]) ->
- lowest_list_protocol_version(lowest_protocol_version(Ver1, Ver2), Rest).
diff --git a/lib/ssl/src/ssl_connection.erl b/lib/ssl/src/ssl_connection.erl
index 90e0810241..53282998d0 100644
--- a/lib/ssl/src/ssl_connection.erl
+++ b/lib/ssl/src/ssl_connection.erl
@@ -1049,9 +1049,9 @@ format_status(terminate, [_, StateName, State]) ->
srp_params = ?SECRET_PRINTOUT,
srp_keys = ?SECRET_PRINTOUT,
premaster_secret = ?SECRET_PRINTOUT,
- ssl_options = NewOptions}
+ ssl_options = NewOptions,
+ flight_buffer = ?SECRET_PRINTOUT}
}}]}].
-
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
@@ -1120,7 +1120,7 @@ resumed_server_hello(#state{session = Session,
server_hello(ServerHello, State0, Connection) ->
CipherSuite = ServerHello#server_hello.cipher_suite,
{KeyAlgorithm, _, _, _} = ssl_cipher:suite_definition(CipherSuite),
- State = Connection:send_handshake(ServerHello, State0),
+ State = Connection:queue_handshake(ServerHello, State0),
State#state{key_algorithm = KeyAlgorithm}.
server_hello_done(State, Connection) ->
@@ -1166,7 +1166,7 @@ certify_client(#state{client_certificate_requested = true, role = client,
session = #session{own_certificate = OwnCert}}
= State, Connection) ->
Certificate = ssl_handshake:certificate(OwnCert, CertDbHandle, CertDbRef, client),
- Connection:send_handshake(Certificate, State);
+ Connection:queue_handshake(Certificate, State);
certify_client(#state{client_certificate_requested = false} = State, _) ->
State.
@@ -1182,7 +1182,7 @@ verify_client_cert(#state{client_certificate_requested = true, role = client,
case ssl_handshake:client_certificate_verify(OwnCert, MasterSecret,
Version, HashSign, PrivateKey, Handshake0) of
#certificate_verify{} = Verified ->
- Connection:send_handshake(Verified, State);
+ Connection:queue_handshake(Verified, State);
ignore ->
State;
#alert{} = Alert ->
@@ -1276,7 +1276,7 @@ certify_server(#state{cert_db = CertDbHandle,
session = #session{own_certificate = OwnCert}} = State, Connection) ->
case ssl_handshake:certificate(OwnCert, CertDbHandle, CertDbRef, server) of
Cert = #certificate{} ->
- Connection:send_handshake(Cert, State);
+ Connection:queue_handshake(Cert, State);
Alert = #alert{} ->
throw(Alert)
end.
@@ -1303,7 +1303,7 @@ key_exchange(#state{role = server, key_algorithm = Algo,
HashSignAlgo, ClientRandom,
ServerRandom,
PrivateKey}),
- State = Connection:send_handshake(Msg, State0),
+ State = Connection:queue_handshake(Msg, State0),
State#state{diffie_hellman_keys = DHKeys};
key_exchange(#state{role = server, private_key = Key, key_algorithm = Algo} = State, _)
@@ -1328,7 +1328,7 @@ key_exchange(#state{role = server, key_algorithm = Algo,
HashSignAlgo, ClientRandom,
ServerRandom,
PrivateKey}),
- State = Connection:send_handshake(Msg, State0),
+ State = Connection:queue_handshake(Msg, State0),
State#state{diffie_hellman_keys = ECDHKeys};
key_exchange(#state{role = server, key_algorithm = psk,
@@ -1350,7 +1350,7 @@ key_exchange(#state{role = server, key_algorithm = psk,
HashSignAlgo, ClientRandom,
ServerRandom,
PrivateKey}),
- Connection:send_handshake(Msg, State0);
+ Connection:queue_handshake(Msg, State0);
key_exchange(#state{role = server, key_algorithm = dhe_psk,
ssl_options = #ssl_options{psk_identity = PskIdentityHint},
@@ -1371,7 +1371,7 @@ key_exchange(#state{role = server, key_algorithm = dhe_psk,
HashSignAlgo, ClientRandom,
ServerRandom,
PrivateKey}),
- State = Connection:send_handshake(Msg, State0),
+ State = Connection:queue_handshake(Msg, State0),
State#state{diffie_hellman_keys = DHKeys};
key_exchange(#state{role = server, key_algorithm = rsa_psk,
@@ -1393,7 +1393,7 @@ key_exchange(#state{role = server, key_algorithm = rsa_psk,
HashSignAlgo, ClientRandom,
ServerRandom,
PrivateKey}),
- Connection:send_handshake(Msg, State0);
+ Connection:queue_handshake(Msg, State0);
key_exchange(#state{role = server, key_algorithm = Algo,
ssl_options = #ssl_options{user_lookup_fun = LookupFun},
@@ -1422,7 +1422,7 @@ key_exchange(#state{role = server, key_algorithm = Algo,
HashSignAlgo, ClientRandom,
ServerRandom,
PrivateKey}),
- State = Connection:send_handshake(Msg, State0),
+ State = Connection:queue_handshake(Msg, State0),
State#state{srp_params = SrpParams,
srp_keys = Keys};
@@ -1432,7 +1432,7 @@ key_exchange(#state{role = client,
negotiated_version = Version,
premaster_secret = PremasterSecret} = State0, Connection) ->
Msg = rsa_key_exchange(Version, PremasterSecret, PublicKeyInfo),
- Connection:send_handshake(Msg, State0);
+ Connection:queue_handshake(Msg, State0);
key_exchange(#state{role = client,
key_algorithm = Algorithm,
@@ -1443,7 +1443,7 @@ key_exchange(#state{role = client,
Algorithm == dhe_rsa;
Algorithm == dh_anon ->
Msg = ssl_handshake:key_exchange(client, Version, {dh, DhPubKey}),
- Connection:send_handshake(Msg, State0);
+ Connection:queue_handshake(Msg, State0);
key_exchange(#state{role = client,
key_algorithm = Algorithm,
@@ -1453,7 +1453,7 @@ key_exchange(#state{role = client,
Algorithm == ecdh_ecdsa; Algorithm == ecdh_rsa;
Algorithm == ecdh_anon ->
Msg = ssl_handshake:key_exchange(client, Version, {ecdh, Keys}),
- Connection:send_handshake(Msg, State0);
+ Connection:queue_handshake(Msg, State0);
key_exchange(#state{role = client,
ssl_options = SslOpts,
@@ -1461,7 +1461,7 @@ key_exchange(#state{role = client,
negotiated_version = Version} = State0, Connection) ->
Msg = ssl_handshake:key_exchange(client, Version,
{psk, SslOpts#ssl_options.psk_identity}),
- Connection:send_handshake(Msg, State0);
+ Connection:queue_handshake(Msg, State0);
key_exchange(#state{role = client,
ssl_options = SslOpts,
@@ -1471,7 +1471,7 @@ key_exchange(#state{role = client,
Msg = ssl_handshake:key_exchange(client, Version,
{dhe_psk,
SslOpts#ssl_options.psk_identity, DhPubKey}),
- Connection:send_handshake(Msg, State0);
+ Connection:queue_handshake(Msg, State0);
key_exchange(#state{role = client,
ssl_options = SslOpts,
key_algorithm = rsa_psk,
@@ -1481,7 +1481,7 @@ key_exchange(#state{role = client,
= State0, Connection) ->
Msg = rsa_psk_key_exchange(Version, SslOpts#ssl_options.psk_identity,
PremasterSecret, PublicKeyInfo),
- Connection:send_handshake(Msg, State0);
+ Connection:queue_handshake(Msg, State0);
key_exchange(#state{role = client,
key_algorithm = Algorithm,
@@ -1492,7 +1492,7 @@ key_exchange(#state{role = client,
Algorithm == srp_rsa;
Algorithm == srp_anon ->
Msg = ssl_handshake:key_exchange(client, Version, {srp, ClientPubKey}),
- Connection:send_handshake(Msg, State0).
+ Connection:queue_handshake(Msg, State0).
rsa_key_exchange(Version, PremasterSecret, PublicKeyInfo = {Algorithm, _, _})
when Algorithm == ?rsaEncryption;
@@ -1539,7 +1539,7 @@ request_client_cert(#state{ssl_options = #ssl_options{verify = verify_peer,
HashSigns = ssl_handshake:available_signature_algs(SupportedHashSigns, Version, [Version]),
Msg = ssl_handshake:certificate_request(CipherSuite, CertDbHandle, CertDbRef,
HashSigns, Version),
- State = Connection:send_handshake(Msg, State0),
+ State = Connection:queue_handshake(Msg, State0),
State#state{client_certificate_requested = true};
request_client_cert(#state{ssl_options = #ssl_options{verify = verify_none}} =
@@ -1583,10 +1583,10 @@ next_protocol(#state{expecting_next_protocol_negotiation = false} = State, _) ->
State;
next_protocol(#state{negotiated_protocol = NextProtocol} = State0, Connection) ->
NextProtocolMessage = ssl_handshake:next_protocol(NextProtocol),
- Connection:send_handshake(NextProtocolMessage, State0).
+ Connection:queue_handshake(NextProtocolMessage, State0).
cipher_protocol(State, Connection) ->
- Connection:send_change_cipher(#change_cipher_spec{}, State).
+ Connection:queue_change_cipher(#change_cipher_spec{}, State).
finished(#state{role = Role, negotiated_version = Version,
session = Session,
diff --git a/lib/ssl/src/ssl_connection.hrl b/lib/ssl/src/ssl_connection.hrl
index 7682cb86ea..4b54943ddf 100644
--- a/lib/ssl/src/ssl_connection.hrl
+++ b/lib/ssl/src/ssl_connection.hrl
@@ -84,7 +84,12 @@
client_ecc, % {Curves, PointFmt}
tracker :: pid() | 'undefined', %% Tracker process for listen socket
sni_hostname = undefined,
- downgrade
+ downgrade,
+ flight_buffer = [] :: list() %% Buffer of TLS/DTLS records, used during the TLS handshake
+ %% to when possible pack more than on TLS record into the
+ %% underlaying packet format. Introduced by DTLS - RFC 4347.
+ %% The mecahnism is also usefull in TLS although we do not
+ %% need to worry about packet loss in TLS.
}).
-define(DEFAULT_DIFFIE_HELLMAN_PARAMS,
diff --git a/lib/ssl/src/ssl_handshake.erl b/lib/ssl/src/ssl_handshake.erl
index acc5ae6bd7..9c3fe9d73b 100644
--- a/lib/ssl/src/ssl_handshake.erl
+++ b/lib/ssl/src/ssl_handshake.erl
@@ -65,7 +65,7 @@
%% Cipher suites handling
-export([available_suites/2, available_signature_algs/3, cipher_suites/2,
- select_session/11, supported_ecc/1]).
+ select_session/11, supported_ecc/1, available_signature_algs/4]).
%% Extensions handling
-export([client_hello_extensions/6,
@@ -2191,3 +2191,14 @@ bad_key(#'RSAPrivateKey'{}) ->
unacceptable_rsa_key;
bad_key(#'ECPrivateKey'{}) ->
unacceptable_ecdsa_key.
+
+available_signature_algs(undefined, SupportedHashSigns, _, {Major, Minor}) when
+ (Major >= 3) andalso (Minor >= 3) ->
+ SupportedHashSigns;
+available_signature_algs(#hash_sign_algos{hash_sign_algos = ClientHashSigns}, SupportedHashSigns,
+ _, {Major, Minor}) when (Major >= 3) andalso (Minor >= 3) ->
+ sets:to_list(sets:intersection(sets:from_list(ClientHashSigns),
+ sets:from_list(SupportedHashSigns)));
+available_signature_algs(_, _, _, _) ->
+ undefined.
+
diff --git a/lib/ssl/src/ssl_record.erl b/lib/ssl/src/ssl_record.erl
index 0a086f5eeb..5bb1c92c2d 100644
--- a/lib/ssl/src/ssl_record.erl
+++ b/lib/ssl/src/ssl_record.erl
@@ -72,7 +72,8 @@ init_connection_states(Role, BeastMitigation) ->
ConnectionEnd = record_protocol_role(Role),
Current = initial_connection_state(ConnectionEnd, BeastMitigation),
Pending = empty_connection_state(ConnectionEnd, BeastMitigation),
- #connection_states{current_read = Current,
+ #connection_states{dtls_write_msg_seq = 1, % only used by dtls
+ current_read = Current,
pending_read = Pending,
current_write = Current,
pending_write = Pending
@@ -320,14 +321,25 @@ encode_handshake(Frag, Version,
beast_mitigation = BeastMitigation,
security_parameters =
#security_parameters{bulk_cipher_algorithm = BCA}}} =
- ConnectionStates) ->
+ ConnectionStates)
+when is_list(Frag) ->
case iolist_size(Frag) of
N when N > ?MAX_PLAIN_TEXT_LENGTH ->
Data = split_bin(iolist_to_binary(Frag), ?MAX_PLAIN_TEXT_LENGTH, Version, BCA, BeastMitigation),
encode_iolist(?HANDSHAKE, Data, Version, ConnectionStates);
_ ->
encode_plain_text(?HANDSHAKE, Version, Frag, ConnectionStates)
- end.
+ end;
+%% TODO: this is a workarround for DTLS
+%%
+%% DTLS need to select the connection write state based on Epoch it wants to
+%% send this fragment in. That Epoch does not nessarily has to be the same
+%% as the current_write epoch.
+%% The right solution might be to pass the WriteState instead of the ConnectionStates,
+%% however, this will require substantion API changes.
+encode_handshake(Frag, Version, ConnectionStates) ->
+ encode_plain_text(?HANDSHAKE, Version, Frag, ConnectionStates).
+
%%--------------------------------------------------------------------
-spec encode_alert_record(#alert{}, ssl_version(), #connection_states{}) ->
{iolist(), #connection_states{}}.
diff --git a/lib/ssl/src/ssl_record.hrl b/lib/ssl/src/ssl_record.hrl
index 87fde35258..a41264ff9b 100644
--- a/lib/ssl/src/ssl_record.hrl
+++ b/lib/ssl/src/ssl_record.hrl
@@ -46,6 +46,8 @@
}).
-record(connection_states, {
+ dtls_write_msg_seq, %% Only used by DTLS
+
current_read,
pending_read,
current_write,
diff --git a/lib/ssl/src/tls_connection.erl b/lib/ssl/src/tls_connection.erl
index eaf2dd002d..9880befa94 100644
--- a/lib/ssl/src/tls_connection.erl
+++ b/lib/ssl/src/tls_connection.erl
@@ -49,8 +49,9 @@
-export([next_record/1, next_event/3]).
%% Handshake handling
--export([renegotiate/2, send_handshake/2, send_change_cipher/2,
- reinit_handshake_data/1, handle_sni_extension/2]).
+-export([renegotiate/2, send_handshake/2,
+ queue_handshake/2, queue_change_cipher/2,
+ reinit_handshake_data/1, handle_sni_extension/2]).
%% Alert and close handling
-export([send_alert/2, handle_own_alert/4, handle_close_alert/3,
@@ -102,17 +103,32 @@ start_fsm(Role, Host, Port, Socket, {#ssl_options{erl_dist = true},_, Tracker} =
Error
end.
-send_handshake(Handshake, #state{negotiated_version = Version,
- socket = Socket,
- transport_cb = Transport,
- tls_handshake_history = Hist0,
- connection_states = ConnectionStates0} = State0) ->
+send_handshake(Handshake, State) ->
+ send_handshake_flight(queue_handshake(Handshake, State)).
+
+queue_handshake(Handshake, #state{negotiated_version = Version,
+ tls_handshake_history = Hist0,
+ flight_buffer = Flight0,
+ connection_states = ConnectionStates0} = State0) ->
{BinHandshake, ConnectionStates, Hist} =
encode_handshake(Handshake, Version, ConnectionStates0, Hist0),
- Transport:send(Socket, BinHandshake),
State0#state{connection_states = ConnectionStates,
- tls_handshake_history = Hist
- }.
+ tls_handshake_history = Hist,
+ flight_buffer = Flight0 ++ [BinHandshake]}.
+
+send_handshake_flight(#state{socket = Socket,
+ transport_cb = Transport,
+ flight_buffer = Flight} = State0) ->
+ Transport:send(Socket, Flight),
+ State0#state{flight_buffer = []}.
+
+queue_change_cipher(Msg, #state{negotiated_version = Version,
+ flight_buffer = Flight0,
+ connection_states = ConnectionStates0} = State0) ->
+ {BinChangeCipher, ConnectionStates} =
+ encode_change_cipher(Msg, Version, ConnectionStates0),
+ State0#state{connection_states = ConnectionStates,
+ flight_buffer = Flight0 ++ [BinChangeCipher]}.
send_alert(Alert, #state{negotiated_version = Version,
socket = Socket,
@@ -123,15 +139,6 @@ send_alert(Alert, #state{negotiated_version = Version,
Transport:send(Socket, BinMsg),
State0#state{connection_states = ConnectionStates}.
-send_change_cipher(Msg, #state{connection_states = ConnectionStates0,
- socket = Socket,
- negotiated_version = Version,
- transport_cb = Transport} = State0) ->
- {BinChangeCipher, ConnectionStates} =
- encode_change_cipher(Msg, Version, ConnectionStates0),
- Transport:send(Socket, BinChangeCipher),
- State0#state{connection_states = ConnectionStates}.
-
reinit_handshake_data(State) ->
%% premaster_secret, public_key_info and tls_handshake_info
%% are only needed during the handshake phase.
@@ -141,7 +148,7 @@ reinit_handshake_data(State) ->
public_key_info = undefined,
tls_handshake_history = ssl_handshake:init_handshake_history()
}.
-
+
%%====================================================================
%% tls_connection_sup API
%%====================================================================
@@ -504,7 +511,8 @@ initial_state(Role, Host, Port, Socket, {SSLOptions, SocketOptions, Tracker}, Us
allow_renegotiate = SSLOptions#ssl_options.client_renegotiation,
start_or_recv_from = undefined,
protocol_cb = ?MODULE,
- tracker = Tracker
+ tracker = Tracker,
+ flight_buffer = []
}.
diff --git a/lib/ssl/src/tls_handshake.erl b/lib/ssl/src/tls_handshake.erl
index 397f963ad5..566b7db332 100644
--- a/lib/ssl/src/tls_handshake.erl
+++ b/lib/ssl/src/tls_handshake.erl
@@ -159,7 +159,8 @@ handle_client_hello(Version, #client_hello{session_id = SugesstedId,
{Port, Session0, Cache, CacheCb, ConnectionStates0, Cert, _}, Renegotiation) ->
case tls_record:is_acceptable_version(Version, Versions) of
true ->
- AvailableHashSigns = available_signature_algs(ClientHashSigns, SupportedHashSigns, Cert, Version),
+ AvailableHashSigns = ssl_handshake:available_signature_algs(
+ ClientHashSigns, SupportedHashSigns, Cert, Version),
ECCCurve = ssl_handshake:select_curve(Curves, ssl_handshake:supported_ecc(Version)),
{Type, #session{cipher_suite = CipherSuite} = Session1}
= ssl_handshake:select_session(SugesstedId, CipherSuites, AvailableHashSigns, Compressions,
@@ -284,14 +285,3 @@ handle_server_hello_extensions(Version, SessionId, Random, CipherSuite,
{Version, SessionId, ConnectionStates, ProtoExt, Protocol}
end.
-available_signature_algs(undefined, SupportedHashSigns, _, {Major, Minor}) when
- (Major >= 3) andalso (Minor >= 3) ->
- SupportedHashSigns;
-available_signature_algs(#hash_sign_algos{hash_sign_algos = ClientHashSigns}, SupportedHashSigns,
- _, {Major, Minor}) when (Major >= 3) andalso (Minor >= 3) ->
- sets:to_list(sets:intersection(sets:from_list(ClientHashSigns),
- sets:from_list(SupportedHashSigns)));
-available_signature_algs(_, _, _, _) ->
- undefined.
-
-