aboutsummaryrefslogtreecommitdiffstats
path: root/lib/ssl/src/tls_connection.erl
diff options
context:
space:
mode:
Diffstat (limited to 'lib/ssl/src/tls_connection.erl')
-rw-r--r--lib/ssl/src/tls_connection.erl673
1 files changed, 214 insertions, 459 deletions
diff --git a/lib/ssl/src/tls_connection.erl b/lib/ssl/src/tls_connection.erl
index 9880befa94..352874c77d 100644
--- a/lib/ssl/src/tls_connection.erl
+++ b/lib/ssl/src/tls_connection.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2007-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2007-2017. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -45,33 +45,30 @@
%% Setup
-export([start_fsm/8, start_link/7, init/1]).
+-export([encode_data/3, encode_alert/3]).
+
%% State transition handling
--export([next_record/1, next_event/3]).
+-export([next_record/1, next_event/3, next_event/4]).
%% Handshake handling
-export([renegotiate/2, send_handshake/2,
queue_handshake/2, queue_change_cipher/2,
- reinit_handshake_data/1, handle_sni_extension/2]).
+ reinit_handshake_data/1, select_sni_extension/1]).
%% Alert and close handling
--export([send_alert/2, handle_own_alert/4, handle_close_alert/3,
- handle_normal_shutdown/3,
- close/5, alert_user/6, alert_user/9
- ]).
+-export([send_alert/2, close/5]).
%% Data handling
--export([write_application_data/3, read_application_data/2,
- passive_receive/2, next_record_if_active/1, handle_common_event/4]).
+-export([passive_receive/2, next_record_if_active/1, handle_common_event/4, send/3,
+ socket/5, setopts/3, getopts/3]).
%% gen_statem state functions
-export([init/3, error/3, downgrade/3, %% Initiation and take down states
hello/3, certify/3, cipher/3, abbreviated/3, %% Handshake states
connection/3]).
%% gen_statem callbacks
--export([terminate/3, code_change/4, format_status/2]).
+-export([callback_mode/0, terminate/3, code_change/4, format_status/2]).
--define(GEN_STATEM_CB_MODE, state_functions).
-
%%====================================================================
%% Internal application API
%%====================================================================
@@ -109,9 +106,10 @@ send_handshake(Handshake, State) ->
queue_handshake(Handshake, #state{negotiated_version = Version,
tls_handshake_history = Hist0,
flight_buffer = Flight0,
+ ssl_options = #ssl_options{v2_hello_compatible = V2HComp},
connection_states = ConnectionStates0} = State0) ->
{BinHandshake, ConnectionStates, Hist} =
- encode_handshake(Handshake, Version, ConnectionStates0, Hist0),
+ encode_handshake(Handshake, Version, ConnectionStates0, Hist0, V2HComp),
State0#state{connection_states = ConnectionStates,
tls_handshake_history = Hist,
flight_buffer = Flight0 ++ [BinHandshake]}.
@@ -119,8 +117,8 @@ queue_handshake(Handshake, #state{negotiated_version = Version,
send_handshake_flight(#state{socket = Socket,
transport_cb = Transport,
flight_buffer = Flight} = State0) ->
- Transport:send(Socket, Flight),
- State0#state{flight_buffer = []}.
+ send(Transport, Socket, Flight),
+ {State0#state{flight_buffer = []}, []}.
queue_change_cipher(Msg, #state{negotiated_version = Version,
flight_buffer = Flight0,
@@ -135,8 +133,8 @@ send_alert(Alert, #state{negotiated_version = Version,
transport_cb = Transport,
connection_states = ConnectionStates0} = State0) ->
{BinMsg, ConnectionStates} =
- ssl_alert:encode(Alert, Version, ConnectionStates0),
- Transport:send(Socket, BinMsg),
+ encode_alert(Alert, Version, ConnectionStates0),
+ send(Transport, Socket, BinMsg),
State0#state{connection_states = ConnectionStates}.
reinit_handshake_data(State) ->
@@ -149,6 +147,23 @@ reinit_handshake_data(State) ->
tls_handshake_history = ssl_handshake:init_handshake_history()
}.
+select_sni_extension(#client_hello{extensions = HelloExtensions}) ->
+ HelloExtensions#hello_extensions.sni;
+select_sni_extension(_) ->
+ undefined.
+
+encode_data(Data, Version, ConnectionStates0)->
+ tls_record:encode_data(Data, Version, ConnectionStates0).
+
+%%--------------------------------------------------------------------
+-spec encode_alert(#alert{}, ssl_record:ssl_version(), ssl_record:connection_states()) ->
+ {iolist(), ssl_record:connection_states()}.
+%%
+%% Description: Encodes an alert
+%%--------------------------------------------------------------------
+encode_alert(#alert{} = Alert, Version, ConnectionStates) ->
+ tls_record:encode_alert_record(Alert, Version, ConnectionStates).
+
%%====================================================================
%% tls_connection_sup API
%%====================================================================
@@ -169,11 +184,22 @@ init([Role, Host, Port, Socket, Options, User, CbInfo]) ->
State0 = initial_state(Role, Host, Port, Socket, Options, User, CbInfo),
try
State = ssl_connection:ssl_config(State0#state.ssl_options, Role, State0),
- gen_statem:enter_loop(?MODULE, [], ?GEN_STATEM_CB_MODE, init, State)
+ gen_statem:enter_loop(?MODULE, [], init, State)
catch throw:Error ->
- gen_statem:enter_loop(?MODULE, [], ?GEN_STATEM_CB_MODE, error, {Error, State0})
+ gen_statem:enter_loop(?MODULE, [], error, {Error, State0})
end.
+callback_mode() ->
+ state_functions.
+
+socket(Pid, Transport, Socket, Connection, Tracker) ->
+ tls_socket:socket(Pid, Transport, Socket, Connection, Tracker).
+
+setopts(Transport, Socket, Other) ->
+ tls_socket:setopts(Transport, Socket, Other).
+getopts(Transport, Socket, Tag) ->
+ tls_socket:getopts(Transport, Socket, Tag).
+
%%--------------------------------------------------------------------
%% State functions
%%--------------------------------------------------------------------
@@ -185,7 +211,7 @@ init([Role, Host, Port, Socket, Options, User, CbInfo]) ->
init({call, From}, {start, Timeout},
#state{host = Host, port = Port, role = client,
- ssl_options = SslOpts,
+ ssl_options = #ssl_options{v2_hello_compatible = V2HComp} = SslOpts,
session = #session{own_certificate = Cert} = Session0,
transport_cb = Transport, socket = Socket,
connection_states = ConnectionStates0,
@@ -198,11 +224,11 @@ init({call, From}, {start, Timeout},
Cache, CacheCb, Renegotiation, Cert),
Version = Hello#client_hello.client_version,
- HelloVersion = tls_record:lowest_protocol_version(SslOpts#ssl_options.versions),
+ HelloVersion = tls_record:hello_version(Version, SslOpts#ssl_options.versions),
Handshake0 = ssl_handshake:init_handshake_history(),
{BinMsg, ConnectionStates, Handshake} =
- encode_handshake(Hello, HelloVersion, ConnectionStates0, Handshake0),
- Transport:send(Socket, BinMsg),
+ encode_handshake(Hello, HelloVersion, ConnectionStates0, Handshake0, V2HComp),
+ send(Transport, Socket, BinMsg),
State1 = State0#state{connection_states = ConnectionStates,
negotiated_version = Version, %% Requested version
session =
@@ -213,7 +239,7 @@ init({call, From}, {start, Timeout},
{Record, State} = next_record(State1),
next_event(hello, Record, State);
init(Type, Event, State) ->
- ssl_connection:init(Type, Event, State, ?MODULE).
+ gen_handshake(ssl_connection, init, Type, Event, State).
%%--------------------------------------------------------------------
-spec error(gen_statem:event_type(),
@@ -234,9 +260,7 @@ error(_, _, _) ->
#state{}) ->
gen_statem:state_function_result().
%%--------------------------------------------------------------------
-hello(internal, #client_hello{client_version = ClientVersion,
- extensions = #hello_extensions{ec_point_formats = EcPointFormats,
- elliptic_curves = EllipticCurves}} = Hello,
+hello(internal, #client_hello{client_version = ClientVersion} = Hello,
#state{connection_states = ConnectionStates0,
port = Port, session = #session{own_certificate = Cert} = Session0,
renegotiation = {Renegotiation, _},
@@ -249,7 +273,7 @@ hello(internal, #client_hello{client_version = ClientVersion,
case tls_handshake:hello(Hello, SslOpts, {Port, Session0, Cache, CacheCb,
ConnectionStates0, Cert, KeyExAlg}, Renegotiation) of
#alert{} = Alert ->
- handle_own_alert(Alert, ClientVersion, hello, State);
+ ssl_connection:handle_own_alert(Alert, ClientVersion, hello, State);
{Version, {Type, Session},
ConnectionStates, Protocol0, ServerHelloExt, HashSign} ->
Protocol = case Protocol0 of
@@ -257,13 +281,12 @@ hello(internal, #client_hello{client_version = ClientVersion,
_ -> Protocol0
end,
- ssl_connection:hello(internal, {common_client_hello, Type, ServerHelloExt},
+ gen_handshake(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},
- negotiated_protocol = Protocol}, ?MODULE)
+ negotiated_protocol = Protocol})
end;
hello(internal, #server_hello{} = Hello,
#state{connection_states = ConnectionStates0,
@@ -273,42 +296,42 @@ hello(internal, #server_hello{} = Hello,
ssl_options = SslOptions} = State) ->
case tls_handshake:hello(Hello, SslOptions, ConnectionStates0, Renegotiation) of
#alert{} = Alert ->
- handle_own_alert(Alert, ReqVersion, hello, State);
+ ssl_connection:handle_own_alert(Alert, ReqVersion, hello, State);
{Version, NewId, ConnectionStates, ProtoExt, Protocol} ->
ssl_connection:handle_session(Hello,
Version, NewId, ConnectionStates, ProtoExt, Protocol, State)
end;
hello(info, Event, State) ->
- handle_info(Event, hello, State);
+ gen_info(Event, hello, State);
hello(Type, Event, State) ->
- ssl_connection:hello(Type, Event, State, ?MODULE).
+ gen_handshake(ssl_connection, hello, Type, Event, State).
%%--------------------------------------------------------------------
-spec abbreviated(gen_statem:event_type(), term(), #state{}) ->
gen_statem:state_function_result().
%%--------------------------------------------------------------------
abbreviated(info, Event, State) ->
- handle_info(Event, abbreviated, State);
+ gen_info(Event, abbreviated, State);
abbreviated(Type, Event, State) ->
- ssl_connection:abbreviated(Type, Event, State, ?MODULE).
+ gen_handshake(ssl_connection, abbreviated, Type, Event, State).
%%--------------------------------------------------------------------
-spec certify(gen_statem:event_type(), term(), #state{}) ->
gen_statem:state_function_result().
%%--------------------------------------------------------------------
certify(info, Event, State) ->
- handle_info(Event, certify, State);
+ gen_info(Event, certify, State);
certify(Type, Event, State) ->
- ssl_connection:certify(Type, Event, State, ?MODULE).
+ gen_handshake(ssl_connection, certify, Type, Event, State).
%%--------------------------------------------------------------------
-spec cipher(gen_statem:event_type(), term(), #state{}) ->
gen_statem:state_function_result().
%%--------------------------------------------------------------------
cipher(info, Event, State) ->
- handle_info(Event, cipher, State);
+ gen_info(Event, cipher, State);
cipher(Type, Event, State) ->
- ssl_connection:cipher(Type, Event, State, ?MODULE).
+ gen_handshake(ssl_connection, cipher, Type, Event, State).
%%--------------------------------------------------------------------
-spec connection(gen_statem:event_type(),
@@ -316,7 +339,7 @@ cipher(Type, Event, State) ->
gen_statem:state_function_result().
%%--------------------------------------------------------------------
connection(info, Event, State) ->
- handle_info(Event, connection, State);
+ gen_info(Event, connection, State);
connection(internal, #hello_request{},
#state{role = client, host = Host, port = Port,
session = #session{own_certificate = Cert} = Session0,
@@ -326,12 +349,12 @@ connection(internal, #hello_request{},
renegotiation = {Renegotiation, _}} = State0) ->
Hello = tls_handshake:client_hello(Host, Port, ConnectionStates0, SslOpts,
Cache, CacheCb, Renegotiation, Cert),
- State1 = send_handshake(Hello, State0),
+ {State1, Actions} = send_handshake(Hello, State0),
{Record, State} =
next_record(
State1#state{session = Session0#session{session_id
= Hello#client_hello.session_id}}),
- next_event(hello, Record, State);
+ next_event(hello, Record, State, Actions);
connection(internal, #client_hello{} = Hello,
#state{role = server, allow_renegotiate = true} = State0) ->
%% Mitigate Computational DoS attack
@@ -373,34 +396,47 @@ handle_info({Protocol, _, Data}, StateName,
{Record, State} ->
next_event(StateName, Record, State);
#alert{} = Alert ->
- handle_normal_shutdown(Alert, StateName, State0),
+ ssl_connection:handle_normal_shutdown(Alert, StateName, State0),
{stop, {shutdown, own_alert}}
end;
handle_info({CloseTag, Socket}, StateName,
#state{socket = Socket, close_tag = CloseTag,
+ socket_options = #socket_options{active = Active},
+ protocol_buffers = #protocol_buffers{tls_cipher_texts = CTs},
negotiated_version = Version} = State) ->
+
%% Note that as of TLS 1.1,
%% failure to properly close a connection no longer requires that a
%% session not be resumed. This is a change from TLS 1.0 to conform
%% with widespread implementation practice.
- case Version of
- {1, N} when N >= 1 ->
- 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}};
+
+ case (Active == false) andalso (CTs =/= []) of
+ false ->
+ case Version of
+ {1, N} when N >= 1 ->
+ 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,
+
+ ssl_connection:handle_normal_shutdown(?ALERT_REC(?FATAL, ?CLOSE_NOTIFY), StateName, State),
+ {stop, {shutdown, transport_closed}};
+ true ->
+ %% Fixes non-delivery of final TLS record in {active, once}.
+ %% Basically allows the application the opportunity to set {active, once} again
+ %% and then receive the final message.
+ next_event(StateName, no_record, State)
+ end;
handle_info(Msg, StateName, State) ->
ssl_connection:handle_info(Msg, StateName, State).
handle_common_event(internal, #alert{} = Alert, StateName,
#state{negotiated_version = Version} = State) ->
- handle_own_alert(Alert, Version, StateName, State);
+ ssl_connection:handle_own_alert(Alert, Version, StateName, State);
%%% TLS record protocol level handshake messages
handle_common_event(internal, #ssl_tls{type = ?HANDSHAKE, fragment = Data},
@@ -410,18 +446,26 @@ handle_common_event(internal, #ssl_tls{type = ?HANDSHAKE, fragment = Data},
ssl_options = Options} = State0) ->
try
{Packets, Buf} = tls_handshake:get_tls_handshake(Version,Data,Buf0, Options),
- State =
+ State1 =
State0#state{protocol_buffers =
Buffers#protocol_buffers{tls_handshake_buffer = Buf}},
- Events = tls_handshake_events(Packets),
- case StateName of
- connection ->
- ssl_connection:hibernate_after(StateName, State, Events);
- _ ->
- {next_state, StateName, State, Events}
- end
+ case Packets of
+ [] ->
+ assert_buffer_sanity(Buf, Options),
+ {Record, State} = next_record(State1),
+ next_event(StateName, Record, State);
+ _ ->
+ Events = tls_handshake_events(Packets),
+ case StateName of
+ connection ->
+ ssl_connection:hibernate_after(StateName, State1, Events);
+ _ ->
+ {next_state, StateName,
+ State1#state{unprocessed_handshake_events = unprocessed_events(Events)}, Events}
+ end
+ end
catch throw:#alert{} = Alert ->
- handle_own_alert(Alert, Version, StateName, State0)
+ ssl_connection:handle_own_alert(Alert, Version, StateName, State0)
end;
%%% TLS record protocol level application data messages
handle_common_event(internal, #ssl_tls{type = ?APPLICATION_DATA, fragment = Data}, StateName, State) ->
@@ -432,16 +476,27 @@ handle_common_event(internal, #ssl_tls{type = ?CHANGE_CIPHER_SPEC, fragment = Da
%%% TLS record protocol level Alert messages
handle_common_event(internal, #ssl_tls{type = ?ALERT, fragment = EncAlerts}, StateName,
#state{negotiated_version = Version} = State) ->
- case decode_alerts(EncAlerts) of
+ try decode_alerts(EncAlerts) of
Alerts = [_|_] ->
handle_alerts(Alerts, {next_state, StateName, State});
+ [] ->
+ ssl_connection:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, empty_alert),
+ Version, StateName, State);
#alert{} = Alert ->
- handle_own_alert(Alert, Version, StateName, State)
+ ssl_connection:handle_own_alert(Alert, Version, StateName, State)
+ catch
+ _:_ ->
+ ssl_connection:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, alert_decode_error),
+ Version, StateName, State)
+
end;
%% Ignore unknown TLS record level protocol messages
handle_common_event(internal, #ssl_tls{type = _Unknown}, StateName, State) ->
{next_state, StateName, State}.
+send(Transport, Socket, Data) ->
+ tls_socket:send(Transport, Socket, Data).
+
%%--------------------------------------------------------------------
%% gen_statem callbacks
%%--------------------------------------------------------------------
@@ -457,22 +512,22 @@ format_status(Type, Data) ->
%%--------------------------------------------------------------------
code_change(_OldVsn, StateName, State0, {Direction, From, To}) ->
State = convert_state(State0, Direction, From, To),
- {?GEN_STATEM_CB_MODE, StateName, State};
+ {ok, StateName, State};
code_change(_OldVsn, StateName, State, _) ->
- {?GEN_STATEM_CB_MODE, StateName, State}.
+ {ok, StateName, State}.
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
-encode_handshake(Handshake, Version, ConnectionStates0, Hist0) ->
+encode_handshake(Handshake, Version, ConnectionStates0, Hist0, V2HComp) ->
Frag = tls_handshake:encode_handshake(Handshake, Version),
- Hist = ssl_handshake:update_handshake_history(Hist0, Frag),
+ Hist = ssl_handshake:update_handshake_history(Hist0, Frag, V2HComp),
{Encoded, ConnectionStates} =
- ssl_record:encode_handshake(Frag, Version, ConnectionStates0),
+ tls_record:encode_handshake(Frag, Version, ConnectionStates0),
{Encoded, ConnectionStates, Hist}.
encode_change_cipher(#change_cipher_spec{}, Version, ConnectionStates) ->
- ssl_record:encode_change_cipher_spec(Version, ConnectionStates).
+ tls_record:encode_change_cipher_spec(Version, ConnectionStates).
decode_alerts(Bin) ->
ssl_alert:decode(Bin).
@@ -480,7 +535,7 @@ decode_alerts(Bin) ->
initial_state(Role, Host, Port, Socket, {SSLOptions, SocketOptions, Tracker}, User,
{CbModule, DataTag, CloseTag, ErrorTag}) ->
#ssl_options{beast_mitigation = BeastMitigation} = SSLOptions,
- ConnectionStates = ssl_record:init_connection_states(Role, BeastMitigation),
+ ConnectionStates = tls_record:init_connection_states(Role, BeastMitigation),
SessionCacheCb = case application:get_env(ssl, session_cb) of
{ok, Cb} when is_atom(Cb) ->
@@ -515,23 +570,6 @@ initial_state(Role, Host, Port, Socket, {SSLOptions, SocketOptions, Tracker}, Us
flight_buffer = []
}.
-
-update_ssl_options_from_sni(OrigSSLOptions, SNIHostname) ->
- SSLOption =
- case OrigSSLOptions#ssl_options.sni_fun of
- undefined ->
- proplists:get_value(SNIHostname,
- OrigSSLOptions#ssl_options.sni_hosts);
- SNIFun ->
- SNIFun(SNIHostname)
- end,
- case SSLOption of
- undefined ->
- undefined;
- _ ->
- ssl:handle_options(SSLOption, OrigSSLOptions)
- end.
-
next_tls_record(Data, #state{protocol_buffers = #protocol_buffers{tls_record_buffer = Buf0,
tls_cipher_texts = CT0} = Buffers} = State0) ->
case tls_record:get_tls_records(Data, Buf0) of
@@ -543,7 +581,9 @@ next_tls_record(Data, #state{protocol_buffers = #protocol_buffers{tls_record_buf
#alert{} = Alert ->
Alert
end.
-
+next_record(#state{unprocessed_handshake_events = N} = State) when N > 0 ->
+ {no_record, State#state{unprocessed_handshake_events = N-1}};
+
next_record(#state{protocol_buffers =
#protocol_buffers{tls_packets = [], tls_cipher_texts = [CT | Rest]}
= Buffers,
@@ -560,8 +600,12 @@ next_record(#state{protocol_buffers =
next_record(#state{protocol_buffers = #protocol_buffers{tls_packets = [], tls_cipher_texts = []},
socket = Socket,
transport_cb = Transport} = State) ->
- ssl_socket:setopts(Transport, Socket, [{active,once}]),
- {no_record, State};
+ case tls_socket:setopts(Transport, Socket, [{active,once}]) of
+ ok ->
+ {no_record, State};
+ _ ->
+ {socket_closed, State}
+ end;
next_record(State) ->
{no_record, State}.
@@ -579,19 +623,24 @@ passive_receive(State0 = #state{user_data_buffer = Buffer}, StateName) ->
{Record, State} = next_record(State0),
next_event(StateName, Record, State);
_ ->
- {Record, State} = read_application_data(<<>>, State0),
+ {Record, State} = ssl_connection:read_application_data(<<>>, State0),
next_event(StateName, Record, State)
end.
next_event(StateName, Record, State) ->
next_event(StateName, Record, State, []).
+next_event(StateName, socket_closed, State, _) ->
+ ssl_connection:handle_normal_shutdown(?ALERT_REC(?FATAL, ?CLOSE_NOTIFY), StateName, State),
+ {stop, {shutdown, transport_closed}, 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);
+ {socket_closed, State} ->
+ next_event(StateName, socket_closed, State, Actions);
{#ssl_tls{} = Record, State} ->
- {next_state, StateName, State, [{next_event, internal, {tls_record, Record}} | Actions]};
+ {next_state, StateName, State, [{next_event, internal, {protocol_record, Record}} | Actions]};
{#alert{} = Alert, State} ->
{next_state, StateName, State, [{next_event, internal, Alert} | Actions]}
end;
@@ -600,225 +649,17 @@ next_event(StateName, Record, State, Actions) ->
no_record ->
{next_state, StateName, State, Actions};
#ssl_tls{} = Record ->
- {next_state, StateName, State, [{next_event, internal, {tls_record, Record}} | Actions]};
+ {next_state, StateName, State, [{next_event, internal, {protocol_record, Record}} | Actions]};
#alert{} = Alert ->
{next_state, StateName, State, [{next_event, internal, Alert} | Actions]}
end.
-read_application_data(Data, #state{user_application = {_Mon, Pid},
- socket = Socket,
- transport_cb = Transport,
- socket_options = SOpts,
- bytes_to_read = BytesToRead,
- start_or_recv_from = RecvFrom,
- timer = Timer,
- user_data_buffer = Buffer0,
- tracker = Tracker} = State0) ->
- Buffer1 = if
- Buffer0 =:= <<>> -> Data;
- Data =:= <<>> -> Buffer0;
- true -> <<Buffer0/binary, Data/binary>>
- end,
- case get_data(SOpts, BytesToRead, Buffer1) of
- {ok, ClientData, Buffer} -> % Send data
- SocketOpt = deliver_app_data(Transport, Socket, SOpts, ClientData, Pid, RecvFrom, Tracker),
- cancel_timer(Timer),
- State = State0#state{user_data_buffer = Buffer,
- start_or_recv_from = undefined,
- timer = undefined,
- bytes_to_read = undefined,
- socket_options = SocketOpt
- },
- if
- SocketOpt#socket_options.active =:= false; Buffer =:= <<>> ->
- %% Passive mode, wait for active once or recv
- %% Active and empty, get more data
- next_record_if_active(State);
- true -> %% We have more data
- read_application_data(<<>>, State)
- end;
- {more, Buffer} -> % no reply, we need more data
- next_record(State0#state{user_data_buffer = Buffer});
- {passive, Buffer} ->
- next_record_if_active(State0#state{user_data_buffer = Buffer});
- {error,_Reason} -> %% Invalid packet in packet mode
- deliver_packet_error(Transport, Socket, SOpts, Buffer1, Pid, RecvFrom, Tracker),
- {stop, normal, State0}
- end.
-
-%% Picks ClientData
-get_data(_, _, <<>>) ->
- {more, <<>>};
-%% Recv timed out save buffer data until next recv
-get_data(#socket_options{active=false}, undefined, Buffer) ->
- {passive, Buffer};
-get_data(#socket_options{active=Active, packet=Raw}, BytesToRead, Buffer)
- when Raw =:= raw; Raw =:= 0 -> %% Raw Mode
- if
- Active =/= false orelse BytesToRead =:= 0 ->
- %% Active true or once, or passive mode recv(0)
- {ok, Buffer, <<>>};
- byte_size(Buffer) >= BytesToRead ->
- %% Passive Mode, recv(Bytes)
- <<Data:BytesToRead/binary, Rest/binary>> = Buffer,
- {ok, Data, Rest};
- true ->
- %% Passive Mode not enough data
- {more, Buffer}
- end;
-get_data(#socket_options{packet=Type, packet_size=Size}, _, Buffer) ->
- PacketOpts = [{packet_size, Size}],
- case decode_packet(Type, Buffer, PacketOpts) of
- {more, _} ->
- {more, Buffer};
- Decoded ->
- Decoded
- end.
-
-decode_packet({http, headers}, Buffer, PacketOpts) ->
- decode_packet(httph, Buffer, PacketOpts);
-decode_packet({http_bin, headers}, Buffer, PacketOpts) ->
- decode_packet(httph_bin, Buffer, PacketOpts);
-decode_packet(Type, Buffer, PacketOpts) ->
- erlang:decode_packet(Type, Buffer, PacketOpts).
-
-%% Just like with gen_tcp sockets, an ssl socket that has been configured with
-%% {packet, http} (or {packet, http_bin}) will automatically switch to expect
-%% HTTP headers after it sees a HTTP Request or HTTP Response line. We
-%% represent the current state as follows:
-%% #socket_options.packet =:= http: Expect a HTTP Request/Response line
-%% #socket_options.packet =:= {http, headers}: Expect HTTP Headers
-%% Note that if the user has explicitly configured the socket to expect
-%% HTTP headers using the {packet, httph} option, we don't do any automatic
-%% switching of states.
-deliver_app_data(Transport, Socket, SOpts = #socket_options{active=Active, packet=Type},
- Data, Pid, From, Tracker) ->
- send_or_reply(Active, Pid, From, format_reply(Transport, Socket, SOpts, Data, Tracker)),
- SO = case Data of
- {P, _, _, _} when ((P =:= http_request) or (P =:= http_response)),
- ((Type =:= http) or (Type =:= http_bin)) ->
- SOpts#socket_options{packet={Type, headers}};
- http_eoh when tuple_size(Type) =:= 2 ->
- % End of headers - expect another Request/Response line
- {Type1, headers} = Type,
- SOpts#socket_options{packet=Type1};
- _ ->
- SOpts
- end,
- case Active of
- once ->
- SO#socket_options{active=false};
- _ ->
- SO
- end.
-
-format_reply(_, _,#socket_options{active = false, mode = Mode, packet = Packet,
- header = Header}, Data, _) ->
- {ok, do_format_reply(Mode, Packet, Header, Data)};
-format_reply(Transport, Socket, #socket_options{active = _, mode = Mode, packet = Packet,
- header = Header}, Data, Tracker) ->
- {ssl, ssl_socket:socket(self(), Transport, Socket, ?MODULE, Tracker),
- do_format_reply(Mode, Packet, Header, Data)}.
-
-deliver_packet_error(Transport, Socket, SO= #socket_options{active = Active}, Data, Pid, From, Tracker) ->
- send_or_reply(Active, Pid, From, format_packet_error(Transport, Socket, SO, Data, Tracker)).
-
-format_packet_error(_, _,#socket_options{active = false, mode = Mode}, Data, _) ->
- {error, {invalid_packet, do_format_reply(Mode, raw, 0, Data)}};
-format_packet_error(Transport, Socket, #socket_options{active = _, mode = Mode}, Data, Tracker) ->
- {ssl_error, ssl_socket:socket(self(), Transport, Socket, ?MODULE, Tracker),
- {invalid_packet, do_format_reply(Mode, raw, 0, Data)}}.
-
-do_format_reply(binary, _, N, Data) when N > 0 -> % Header mode
- header(N, Data);
-do_format_reply(binary, _, _, Data) ->
- Data;
-do_format_reply(list, Packet, _, Data)
- when Packet == http; Packet == {http, headers};
- Packet == http_bin; Packet == {http_bin, headers};
- Packet == httph; Packet == httph_bin ->
- Data;
-do_format_reply(list, _,_, Data) ->
- binary_to_list(Data).
-
-header(0, <<>>) ->
- <<>>;
-header(_, <<>>) ->
- [];
-header(0, Binary) ->
- Binary;
-header(N, Binary) ->
- <<?BYTE(ByteN), NewBinary/binary>> = Binary,
- [ByteN | header(N-1, NewBinary)].
-
-send_or_reply(false, _Pid, From, Data) when From =/= undefined ->
- gen_statem:reply(From, Data);
-%% Can happen when handling own alert or tcp error/close and there is
-%% no outstanding gen_fsm sync events
-send_or_reply(false, no_pid, _, _) ->
- ok;
-send_or_reply(_, Pid, _From, Data) ->
- send_user(Pid, Data).
-
-send_user(Pid, Msg) ->
- Pid ! Msg.
-
-tls_handshake_events([]) ->
- throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, malformed_handshake));
tls_handshake_events(Packets) ->
lists:map(fun(Packet) ->
{next_event, internal, {handshake, Packet}}
end, Packets).
-write_application_data(Data0, From,
- #state{socket = Socket,
- negotiated_version = Version,
- transport_cb = Transport,
- connection_states = ConnectionStates0,
- socket_options = SockOpts,
- ssl_options = #ssl_options{renegotiate_at = RenegotiateAt}} = State) ->
- Data = encode_packet(Data0, SockOpts),
-
- case time_to_renegotiate(Data, ConnectionStates0, RenegotiateAt) of
- true ->
- renegotiate(State#state{renegotiation = {true, internal}},
- [{next_event, {call, From}, {application_data, Data0}}]);
- false ->
- {Msgs, ConnectionStates} = ssl_record:encode_data(Data, Version, ConnectionStates0),
- Result = Transport:send(Socket, Msgs),
- ssl_connection:hibernate_after(connection, State#state{connection_states = ConnectionStates},
- [{reply, From, Result}])
- end.
-
-encode_packet(Data, #socket_options{packet=Packet}) ->
- case Packet of
- 1 -> encode_size_packet(Data, 8, (1 bsl 8) - 1);
- 2 -> encode_size_packet(Data, 16, (1 bsl 16) - 1);
- 4 -> encode_size_packet(Data, 32, (1 bsl 32) - 1);
- _ -> Data
- end.
-encode_size_packet(Bin, Size, Max) ->
- Len = erlang:byte_size(Bin),
- case Len > Max of
- true -> throw({error, {badarg, {packet_to_large, Len, Max}}});
- false -> <<Len:Size, Bin/binary>>
- end.
-
-time_to_renegotiate(_Data,
- #connection_states{current_write =
- #connection_state{sequence_number = Num}},
- RenegotiateAt) ->
-
- %% We could do test:
- %% is_time_to_renegotiate((erlang:byte_size(_Data) div ?MAX_PLAIN_TEXT_LENGTH) + 1, RenegotiateAt),
- %% but we chose to have a some what lower renegotiateAt and a much cheaper test
- is_time_to_renegotiate(Num, RenegotiateAt).
-
-is_time_to_renegotiate(N, M) when N < M->
- false;
-is_time_to_renegotiate(_,_) ->
- true.
renegotiate(#state{role = client} = State, Actions) ->
%% Handle same way as if server requested
%% the renegotiation
@@ -835,8 +676,8 @@ renegotiate(#state{role = server,
Frag = tls_handshake:encode_handshake(HelloRequest, Version),
Hs0 = ssl_handshake:init_handshake_history(),
{BinMsg, ConnectionStates} =
- ssl_record:encode_handshake(Frag, Version, ConnectionStates0),
- Transport:send(Socket, BinMsg),
+ tls_record:encode_handshake(Frag, Version, ConnectionStates0),
+ send(Transport, Socket, BinMsg),
State1 = State0#state{connection_states =
ConnectionStates,
tls_handshake_history = Hs0},
@@ -848,135 +689,14 @@ handle_alerts([], Result) ->
handle_alerts(_, {stop,_} = Stop) ->
Stop;
handle_alerts([Alert | Alerts], {next_state, StateName, State}) ->
- handle_alerts(Alerts, handle_alert(Alert, StateName, State));
+ handle_alerts(Alerts, ssl_connection:handle_alert(Alert, StateName, State));
handle_alerts([Alert | Alerts], {next_state, StateName, State, _Actions}) ->
- handle_alerts(Alerts, handle_alert(Alert, StateName, State)).
-handle_alert(#alert{level = ?FATAL} = Alert, StateName,
- #state{socket = Socket, transport_cb = Transport,
- ssl_options = SslOpts, start_or_recv_from = From, host = Host,
- port = Port, session = Session, user_application = {_Mon, Pid},
- role = Role, socket_options = Opts, tracker = Tracker}) ->
- invalidate_session(Role, Host, Port, Session),
- log_alert(SslOpts#ssl_options.log_alert, StateName, Alert),
- alert_user(Transport, Tracker, Socket, StateName, Opts, Pid, From, Alert, Role),
- {stop, normal};
-
-handle_alert(#alert{level = ?WARNING, description = ?CLOSE_NOTIFY} = Alert,
- StateName, State) ->
- handle_normal_shutdown(Alert, StateName, State),
- {stop, {shutdown, peer_close}};
-
-handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert, StateName,
- #state{ssl_options = SslOpts, renegotiation = {true, internal}} = State) ->
- log_alert(SslOpts#ssl_options.log_alert, StateName, Alert),
- handle_normal_shutdown(Alert, StateName, State),
- {stop, {shutdown, peer_close}};
-
-handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert, StateName,
- #state{ssl_options = SslOpts, renegotiation = {true, From}} = State0) ->
- log_alert(SslOpts#ssl_options.log_alert, StateName, Alert),
- gen_statem:reply(From, {error, renegotiation_rejected}),
- {Record, State} = next_record(State0),
- %% Go back to connection!
- next_event(connection, Record, State);
+ handle_alerts(Alerts, ssl_connection:handle_alert(Alert, StateName, State)).
-%% Gracefully log and ignore all other warning alerts
-handle_alert(#alert{level = ?WARNING} = Alert, StateName,
- #state{ssl_options = SslOpts} = State0) ->
- log_alert(SslOpts#ssl_options.log_alert, StateName, Alert),
- {Record, State} = next_record(State0),
- next_event(StateName, Record, State).
-
-alert_user(Transport, Tracker, Socket, connection, Opts, Pid, From, Alert, Role) ->
- alert_user(Transport, Tracker, Socket, Opts#socket_options.active, Pid, From, Alert, Role);
-alert_user(Transport, Tracker, Socket,_, _, _, From, Alert, Role) ->
- alert_user(Transport, Tracker, Socket, From, Alert, Role).
-
-alert_user(Transport, Tracker, Socket, From, Alert, Role) ->
- alert_user(Transport, Tracker, Socket, false, no_pid, From, Alert, Role).
-
-alert_user(_, _, _, false = Active, Pid, From, Alert, Role) when From =/= undefined ->
- %% If there is an outstanding ssl_accept | recv
- %% From will be defined and send_or_reply will
- %% send the appropriate error message.
- ReasonCode = ssl_alert:reason_code(Alert, Role),
- send_or_reply(Active, Pid, From, {error, ReasonCode});
-alert_user(Transport, Tracker, Socket, Active, Pid, From, Alert, Role) ->
- case ssl_alert:reason_code(Alert, Role) of
- closed ->
- send_or_reply(Active, Pid, From,
- {ssl_closed, ssl_socket:socket(self(),
- Transport, Socket, ?MODULE, Tracker)});
- ReasonCode ->
- send_or_reply(Active, Pid, From,
- {ssl_error, ssl_socket:socket(self(),
- Transport, Socket, ?MODULE, Tracker), ReasonCode})
- end.
-
-log_alert(true, Info, Alert) ->
- Txt = ssl_alert:alert_txt(Alert),
- error_logger:format("SSL: ~p: ~s\n", [Info, Txt]);
-log_alert(false, _, _) ->
- ok.
-
-handle_own_alert(Alert, Version, StateName,
- #state{transport_cb = Transport,
- socket = Socket,
- connection_states = ConnectionStates,
- ssl_options = SslOpts} = State) ->
- try %% Try to tell the other side
- {BinMsg, _} =
- ssl_alert:encode(Alert, Version, ConnectionStates),
- Transport:send(Socket, BinMsg)
- catch _:_ -> %% Can crash if we are in a uninitialized state
- ignore
- end,
- try %% Try to tell the local user
- log_alert(SslOpts#ssl_options.log_alert, StateName, Alert),
- handle_normal_shutdown(Alert,StateName, State)
- catch _:_ ->
- ok
- end,
- {stop, {shutdown, own_alert}}.
-
-handle_normal_shutdown(Alert, _, #state{socket = Socket,
- transport_cb = Transport,
- start_or_recv_from = StartFrom,
- tracker = Tracker,
- role = Role, renegotiation = {false, first}}) ->
- alert_user(Transport, Tracker,Socket, StartFrom, Alert, Role);
-
-handle_normal_shutdown(Alert, StateName, #state{socket = Socket,
- socket_options = Opts,
- transport_cb = Transport,
- user_application = {_Mon, Pid},
- tracker = Tracker,
- start_or_recv_from = RecvFrom, role = Role}) ->
- alert_user(Transport, Tracker, Socket, StateName, Opts, Pid, RecvFrom, Alert, Role).
-
-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.
-
-cancel_timer(undefined) ->
- ok;
-cancel_timer(Timer) ->
- erlang:cancel_timer(Timer),
- ok.
-
-invalidate_session(client, Host, Port, Session) ->
- ssl_manager:invalidate_session(Host, Port, Session);
-invalidate_session(server, _, Port, Session) ->
- ssl_manager:invalidate_session(Port, Session).
%% User closes or recursive call!
close({close, Timeout}, Socket, Transport = gen_tcp, _,_) ->
- ssl_socket:setopts(Transport, Socket, [{active, false}]),
+ tls_socket:setopts(Transport, Socket, [{active, false}]),
Transport:shutdown(Socket, write),
_ = Transport:recv(Socket, 0, Timeout),
ok;
@@ -1011,31 +731,66 @@ convert_options_partial_chain(Options, up) ->
convert_options_partial_chain(Options, down) ->
list_to_tuple(proplists:delete(partial_chain, tuple_to_list(Options))).
-handle_sni_extension(#client_hello{extensions = HelloExtensions}, State0) ->
- case HelloExtensions#hello_extensions.sni of
- undefined ->
- State0;
- #sni{hostname = Hostname} ->
- NewOptions = update_ssl_options_from_sni(State0#state.ssl_options, Hostname),
- case NewOptions of
- undefined ->
- State0;
- _ ->
- {ok, Ref, CertDbHandle, FileRefHandle, CacheHandle, CRLDbHandle, OwnCert, Key, DHParams} =
- ssl_config:init(NewOptions, State0#state.role),
- State0#state{
- session = State0#state.session#session{own_certificate = OwnCert},
- file_ref_db = FileRefHandle,
- cert_db_ref = Ref,
- cert_db = CertDbHandle,
- crl_db = CRLDbHandle,
- session_cache = CacheHandle,
- private_key = Key,
- diffie_hellman_params = DHParams,
- ssl_options = NewOptions,
- sni_hostname = Hostname
- }
- end
+gen_handshake(GenConnection, StateName, Type, Event,
+ #state{negotiated_version = Version} = State) ->
+ try GenConnection:StateName(Type, Event, State, ?MODULE) of
+ Result ->
+ Result
+ catch
+ _:_ ->
+ ssl_connection:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE,
+ malformed_handshake_data),
+ Version, StateName, State)
+ end.
+
+gen_info(Event, connection = StateName, #state{negotiated_version = Version} = State) ->
+ try handle_info(Event, StateName, State) of
+ Result ->
+ Result
+ catch
+ _:_ ->
+ ssl_connection:handle_own_alert(?ALERT_REC(?FATAL, ?INTERNAL_ERROR,
+ malformed_data),
+ Version, StateName, State)
end;
-handle_sni_extension(_, State) ->
- State.
+
+gen_info(Event, StateName, #state{negotiated_version = Version} = State) ->
+ try handle_info(Event, StateName, State) of
+ Result ->
+ Result
+ catch
+ _:_ ->
+ ssl_connection:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE,
+ malformed_handshake_data),
+ Version, StateName, State)
+ end.
+
+unprocessed_events(Events) ->
+ %% The first handshake event will be processed immediately
+ %% as it is entered first in the event queue and
+ %% when it is processed there will be length(Events)-1
+ %% handshake events left to process before we should
+ %% process more TLS-records received on the socket.
+ erlang:length(Events)-1.
+
+
+assert_buffer_sanity(<<?BYTE(_Type), ?UINT24(Length), Rest/binary>>, #ssl_options{max_handshake_size = Max}) when
+ Length =< Max ->
+ case size(Rest) of
+ N when N < Length ->
+ true;
+ N when N > Length ->
+ throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE,
+ too_big_handshake_data));
+ _ ->
+ throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE,
+ malformed_handshake_data))
+ end;
+assert_buffer_sanity(Bin, _) ->
+ case size(Bin) of
+ N when N < 3 ->
+ true;
+ _ ->
+ throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE,
+ malformed_handshake_data))
+ end.