diff options
Diffstat (limited to 'lib/ssl/src')
51 files changed, 2127 insertions, 1484 deletions
diff --git a/lib/ssl/src/Makefile b/lib/ssl/src/Makefile index 7a7a373487..b625db0656 100644 --- a/lib/ssl/src/Makefile +++ b/lib/ssl/src/Makefile @@ -70,6 +70,7 @@ MODULES= \ ssl_session_cache \ ssl_crl\ ssl_crl_cache \ + ssl_crl_hash_dir \ ssl_socket \ ssl_listen_tracker_sup \ tls_record \ diff --git a/lib/ssl/src/dtls.erl b/lib/ssl/src/dtls.erl index 14aefd4989..cd705152a8 100644 --- a/lib/ssl/src/dtls.erl +++ b/lib/ssl/src/dtls.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1999-2013. All Rights Reserved. +%% Copyright Ericsson AB 1999-2016. 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. diff --git a/lib/ssl/src/dtls_connection.erl b/lib/ssl/src/dtls_connection.erl index e490de7eeb..b8be686b99 100644 --- a/lib/ssl/src/dtls_connection.erl +++ b/lib/ssl/src/dtls_connection.erl @@ -21,7 +21,7 @@ %% Internal application API --behaviour(gen_fsm). +-behaviour(gen_statem). -include("dtls_connection.hrl"). -include("dtls_handshake.hrl"). @@ -36,48 +36,49 @@ %% Internal application API %% Setup --export([start_fsm/8]). +-export([start_fsm/8, start_link/7, init/1]). %% State transition handling --export([next_record/1, next_state/4%, - %%next_state_connection/2 - ]). +-export([next_record/1, next_event/3]). %% Handshake handling --export([%%renegotiate/1, - 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, - handle_normal_shutdown/3 - %%handle_unexpected_message/3, - %%alert_user/5, alert_user/8 +-export([%%send_alert/2, handle_own_alert/4, handle_close_alert/3, + handle_normal_shutdown/3 %%, close/5 + %%alert_user/6, alert_user/9 ]). %% Data handling + -export([%%write_application_data/3, - read_application_data/2%%, -%% passive_receive/2, next_record_if_active/1 + read_application_data/2, + passive_receive/2, next_record_if_active/1%, + %%handle_common_event/4, + %handle_packet/3 ]). -%% Called by tls_connection_sup --export([start_link/7]). +%% 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]). -%% gen_fsm callbacks --export([init/1, hello/2, certify/2, cipher/2, - abbreviated/2, connection/2, handle_event/3, - handle_sync_event/4, handle_info/3, terminate/3, code_change/4]). +-define(GEN_STATEM_CB_MODE, state_functions). %%==================================================================== %% 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 @@ -85,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 @@ -99,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, @@ -117,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 %%==================================================================== @@ -141,83 +156,98 @@ send_change_cipher(Msg, #state{connection_states = ConnectionStates0, start_link(Role, Host, Port, Socket, Options, User, CbInfo) -> {ok, proc_lib:spawn_link(?MODULE, init, [[Role, Host, Port, Socket, Options, User, CbInfo]])}. -init([Role, Host, Port, Socket, {SSLOpts0, _} = Options, User, CbInfo]) -> +init([Role, Host, Port, Socket, Options, User, CbInfo]) -> process_flag(trap_exit, true), State0 = initial_state(Role, Host, Port, Socket, Options, User, CbInfo), - Handshake = ssl_handshake:init_handshake_history(), - TimeStamp = erlang:monotonic_time(), - try ssl_config:init(SSLOpts0, Role) of - {ok, Ref, CertDbHandle, FileRefHandle, CacheHandle, CRLDbInfo, OwnCert, Key, DHParams} -> - Session = State0#state.session, - State = State0#state{ - tls_handshake_history = Handshake, - session = Session#session{own_certificate = OwnCert, - time_stamp = TimeStamp}, - file_ref_db = FileRefHandle, - cert_db_ref = Ref, - cert_db = CertDbHandle, - crl_db = CRLDbInfo, - session_cache = CacheHandle, - private_key = Key, - diffie_hellman_params = DHParams}, - gen_fsm:enter_loop(?MODULE, [], hello, State, get_timeout(State)) + try + State = ssl_connection:ssl_config(State0#state.ssl_options, Role, State0), + gen_statem:enter_loop(?MODULE, [], ?GEN_STATEM_CB_MODE, init, State) catch throw:Error -> - gen_fsm:enter_loop(?MODULE, [], error, {Error,State0}, get_timeout(State0)) + gen_statem:enter_loop(?MODULE, [], ?GEN_STATEM_CB_MODE, error, {Error,State0}) end. %%-------------------------------------------------------------------- -%% Description:There should be one instance of this function for each -%% possible state name. Whenever a gen_fsm receives an event sent -%% using gen_fsm:send_event/2, the instance of this function with the -%% same name as the current state name StateName is called to handle -%% the event. It is also called if a timeout occurs. -%% -hello(start, #state{host = Host, port = Port, role = client, - ssl_options = SslOpts, - session = #session{own_certificate = Cert} = Session0, - session_cache = Cache, session_cache_cb = CacheCb, - transport_cb = Transport, socket = Socket, - connection_states = ConnectionStates0, - renegotiation = {Renegotiation, _}} = State0) -> +%% State functionsconnection/2 +%%-------------------------------------------------------------------- + +init({call, From}, {start, Timeout}, + #state{host = Host, port = Port, role = client, + ssl_options = SslOpts, + session = #session{own_certificate = Cert} = Session0, + transport_cb = Transport, socket = Socket, + connection_states = ConnectionStates0, + renegotiation = {Renegotiation, _}, + session_cache = Cache, + session_cache_cb = CacheCb + } = State0) -> + Timer = ssl_connection:start_or_recv_cancel_timer(Timeout, From), Hello = dtls_handshake:client_hello(Host, Port, ConnectionStates0, SslOpts, - Cache, CacheCb, Renegotiation, Cert), + Cache, CacheCb, Renegotiation, Cert), Version = Hello#client_hello.client_version, + HelloVersion = dtls_record:lowest_protocol_version(SslOpts#ssl_options.versions), Handshake0 = ssl_handshake:init_handshake_history(), {BinMsg, ConnectionStates, Handshake} = - encode_handshake(Hello, Version, ConnectionStates0, Handshake0), + encode_handshake(Hello, HelloVersion, ConnectionStates0, Handshake0), Transport:send(Socket, BinMsg), State1 = State0#state{connection_states = ConnectionStates, negotiated_version = Version, %% Requested version session = Session0#session{session_id = Hello#client_hello.session_id}, - tls_handshake_history = Handshake}, + tls_handshake_history = Handshake, + start_or_recv_from = From, + timer = Timer}, {Record, State} = next_record(State1), - next_state(hello, hello, Record, State); + next_event(hello, Record, State); +init(Type, Event, State) -> + ssl_connection:init(Type, Event, State, ?MODULE). + +error({call, From}, {start, _Timeout}, {Error, State}) -> + {stop_and_reply, normal, {reply, From, {error, Error}}, State}; +error({call, From}, Msg, State) -> + handle_call(Msg, From, error, State); +error(_, _, _) -> + {keep_state_and_data, [postpone]}. -hello(Hello = #client_hello{client_version = ClientVersion}, +%%-------------------------------------------------------------------- +-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({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(Hello, +hello(internal, #server_hello{} = Hello, #state{connection_states = ConnectionStates0, negotiated_version = ReqVersion, role = client, @@ -230,20 +260,30 @@ hello(Hello, ssl_connection:handle_session(Hello, Version, NewId, ConnectionStates, ProtoExt, Protocol, State) end; - -hello(Msg, State) -> - ssl_connection:hello(Msg, State, ?MODULE). - -abbreviated(Msg, State) -> - ssl_connection:abbreviated(Msg, State, ?MODULE). - -certify(Msg, State) -> - ssl_connection:certify(Msg, State, ?MODULE). - -cipher(Msg, State) -> - ssl_connection:cipher(Msg, State, ?MODULE). - -connection(#hello_request{}, #state{host = Host, port = Port, +hello(info, Event, State) -> + handle_info(Event, hello, State); + +hello(Type, Event, State) -> + ssl_connection:hello(Type, Event, State, ?MODULE). + +abbreviated(info, Event, State) -> + handle_info(Event, abbreviated, State); +abbreviated(Type, Event, State) -> + ssl_connection:abbreviated(Type, Event, State, ?MODULE). + +certify(info, Event, State) -> + handle_info(Event, certify, State); +certify(Type, Event, State) -> + ssl_connection:certify(Type, Event, State, ?MODULE). + +cipher(info, Event, State) -> + handle_info(Event, cipher, State); +cipher(Type, Event, State) -> + ssl_connection:cipher(Type, Event, State, ?MODULE). + +connection(info, Event, State) -> + handle_info(Event, connection, State); +connection(internal, #hello_request{}, #state{host = Host, port = Port, session = #session{own_certificate = Cert} = Session0, session_cache = Cache, session_cache_cb = CacheCb, ssl_options = SslOpts, @@ -251,46 +291,35 @@ connection(#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 = Hello#client_hello.session_id}}), - next_state(connection, hello, Record, State); + next_event(hello, Record, State); -connection(#client_hello{} = Hello, #state{role = server, allow_renegotiate = true} = State) -> +connection(internal, #client_hello{} = Hello, #state{role = server, allow_renegotiate = true} = State) -> %% Mitigate Computational DoS attack %% http://www.educatedguesswork.org/2011/10/ssltls_and_computational_dos.html %% http://www.thc.org/thc-ssl-dos/ Rather than disabling client %% initiated renegotiation we will disallow many client initiated %% renegotiations immediately after each other. erlang:send_after(?WAIT_TO_ALLOW_RENEGOTIATION, self(), allow_renegotiate), - hello(Hello, State#state{allow_renegotiate = false}); + {next_state, hello, State#state{allow_renegotiate = false}, [{next_event, internal, Hello}]}; + -connection(#client_hello{}, #state{role = server, allow_renegotiate = false} = State0) -> +connection(internal, #client_hello{}, #state{role = server, allow_renegotiate = false} = State0) -> Alert = ?ALERT_REC(?WARNING, ?NO_RENEGOTIATION), - State = send_alert(Alert, State0), - next_state_connection(connection, State); + State1 = send_alert(Alert, State0), + {Record, State} = ssl_connection:prepare_connection(State1, ?MODULE), + next_event(connection, Record, State); -connection(Msg, State) -> - ssl_connection:connection(Msg, State, tls_connection). +connection(Type, Event, State) -> + ssl_connection:connection(Type, Event, State, ?MODULE). -%%-------------------------------------------------------------------- -%% Description: Whenever a gen_fsm receives an event sent using -%% gen_fsm:send_all_state_event/2, this function is called to handle -%% the event. Not currently used! -%%-------------------------------------------------------------------- -handle_event(_Event, StateName, State) -> - {next_state, StateName, State, get_timeout(State)}. +downgrade(Type, Event, State) -> + ssl_connection:downgrade(Type, Event, State, ?MODULE). -%%-------------------------------------------------------------------- -%% Description: Whenever a gen_fsm receives an event sent using -%% gen_fsm:sync_send_all_state_event/2,3, this function is called to handle -%% the event. -%%-------------------------------------------------------------------- -handle_sync_event(Event, From, StateName, State) -> - ssl_connection:handle_sync_event(Event, From, StateName, State). %%-------------------------------------------------------------------- %% Description: This function is called by a gen_fsm when it receives any @@ -301,26 +330,38 @@ handle_sync_event(Event, From, StateName, State) -> %% raw data from socket, unpack records handle_info({Protocol, _, Data}, StateName, #state{data_tag = Protocol} = State0) -> - %% Simplify for now to avoid dialzer warnings before implementation is compleate - %% case next_tls_record(Data, State0) of - %% {Record, State} -> - %% next_state(StateName, StateName, Record, State); - %% #alert{} = Alert -> - %% handle_normal_shutdown(Alert, StateName, State0), - %% {stop, {shutdown, own_alert}, State0} - %% end; - {Record, State} = next_tls_record(Data, State0), - next_state(StateName, StateName, Record, State); - + case next_tls_record(Data, State0) of + {Record, State} -> + next_event(StateName, Record, State); + #alert{} = Alert -> + handle_normal_shutdown(Alert, StateName, State0), + {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}, State}; - + {stop, {shutdown, transport_closed}}; handle_info(Msg, StateName, State) -> ssl_connection:handle_info(Msg, StateName, State). +handle_call(Event, From, StateName, State) -> + ssl_connection:handle_call(Event, From, StateName, State, ?MODULE). + %%-------------------------------------------------------------------- %% Description:This function is called by a gen_fsm when it is about %% to terminate. It should be the opposite of Module:init/1 and do any @@ -335,154 +376,96 @@ terminate(Reason, StateName, State) -> %% Description: Convert process state when code is changed %%-------------------------------------------------------------------- code_change(_OldVsn, StateName, State, _Extra) -> - {ok, StateName, State}. + {?GEN_STATEM_CB_MODE, StateName, State}. + +format_status(Type, Data) -> + ssl_connection: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}. + {Frag, 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}; +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_record(State) -> - {no_record, State}. +encode_handshake_records([], CS, _MSS, Recs) -> + {finish_pack_records(Recs), CS}; -next_state(Current,_, #alert{} = Alert, #state{negotiated_version = Version} = State) -> - handle_own_alert(Alert, Version, Current, State); - -next_state(_,Next, no_record, State) -> - {next_state, Next, State, get_timeout(State)}; - -%% next_state(_,Next, #ssl_tls{type = ?ALERT, fragment = EncAlerts}, State) -> -%% Alerts = decode_alerts(EncAlerts), -%% handle_alerts(Alerts, {next_state, Next, State, get_timeout(State)}); - -next_state(Current, Next, #ssl_tls{type = ?HANDSHAKE, fragment = Data}, - State0 = #state{protocol_buffers = - #protocol_buffers{dtls_handshake_buffer = Buf0} = Buffers, - negotiated_version = Version}) -> - Handle = - fun({#hello_request{} = Packet, _}, {next_state, connection = SName, State}) -> - %% This message should not be included in handshake - %% message hashes. Starts new handshake (renegotiation) - Hs0 = ssl_handshake:init_handshake_history(), - ?MODULE:SName(Packet, State#state{tls_handshake_history=Hs0, - renegotiation = {true, peer}}); - ({#hello_request{} = Packet, _}, {next_state, SName, State}) -> - %% This message should not be included in handshake - %% message hashes. Already in negotiation so it will be ignored! - ?MODULE:SName(Packet, State); - ({#client_hello{} = Packet, Raw}, {next_state, connection = SName, State}) -> - Version = Packet#client_hello.client_version, - Hs0 = ssl_handshake:init_handshake_history(), - Hs1 = ssl_handshake:update_handshake_history(Hs0, Raw), - ?MODULE:SName(Packet, State#state{tls_handshake_history=Hs1, - renegotiation = {true, peer}}); - ({Packet, Raw}, {next_state, SName, State = #state{tls_handshake_history=Hs0}}) -> - Hs1 = ssl_handshake:update_handshake_history(Hs0, Raw), - ?MODULE:SName(Packet, State#state{tls_handshake_history=Hs1}); - (_, StopState) -> StopState - end, - try - {Packets, Buf} = tls_handshake:get_tls_handshake(Version,Data,Buf0), - State = State0#state{protocol_buffers = - Buffers#protocol_buffers{dtls_packets = Packets, - dtls_handshake_buffer = Buf}}, - handle_dtls_handshake(Handle, Next, State) - catch throw:#alert{} = Alert -> - handle_own_alert(Alert, Version, Current, State0) +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_state(_, StateName, #ssl_tls{type = ?APPLICATION_DATA, fragment = Data}, State0) -> - %% Simplify for now to avoid dialzer warnings before implementation is compleate - %% case read_application_data(Data, State0) of - %% Stop = {stop,_,_} -> - %% Stop; - %% {Record, State} -> - %% next_state(StateName, StateName, Record, State) - %% end; - {Record, State} = read_application_data(Data, State0), - next_state(StateName, StateName, Record, State); - -next_state(Current, Next, #ssl_tls{type = ?CHANGE_CIPHER_SPEC, fragment = <<1>>} = - _ChangeCipher, - #state{connection_states = ConnectionStates0} = State0) -> - ConnectionStates1 = - ssl_record:activate_pending_connection_state(ConnectionStates0, read), - {Record, State} = next_record(State0#state{connection_states = ConnectionStates1}), - next_state(Current, Next, Record, State); -next_state(Current, Next, #ssl_tls{type = _Unknown}, State0) -> - %% Ignore unknown type - {Record, State} = next_record(State0), - next_state(Current, Next, Record, State). - -handle_dtls_handshake(Handle, StateName, - #state{protocol_buffers = - #protocol_buffers{dtls_packets = [Packet]} = Buffers} = State) -> - FsmReturn = {next_state, StateName, State#state{protocol_buffers = - Buffers#protocol_buffers{dtls_packets = []}}}, - Handle(Packet, FsmReturn); - -handle_dtls_handshake(Handle, StateName, - #state{protocol_buffers = - #protocol_buffers{dtls_packets = [Packet | Packets]} = Buffers} = - State0) -> - FsmReturn = {next_state, StateName, State0#state{protocol_buffers = - Buffers#protocol_buffers{dtls_packets = - Packets}}}, - case Handle(Packet, FsmReturn) of - {next_state, NextStateName, State, _Timeout} -> - handle_dtls_handshake(Handle, NextStateName, State); - {stop, _,_} = Stop -> - Stop +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. +finish_pack_records({[], Acc}) -> + lists:reverse(Acc); +finish_pack_records({Buf, Acc}) -> + lists:reverse([lists:reverse(Buf)|Acc]). -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. - -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}) -> - ConnectionStates = ssl_record:init_connection_states(Role), + #ssl_options{beast_mitigation = BeastMitigation} = SSLOptions, + ConnectionStates = ssl_record:init_connection_states(Role, BeastMitigation), SessionCacheCb = case application:get_env(ssl, session_cb) of {ok, Cb} when is_atom(Cb) -> @@ -514,21 +497,139 @@ initial_state(Role, Host, Port, Socket, {SSLOptions, SocketOptions}, User, renegotiation = {false, first}, allow_renegotiate = SSLOptions#ssl_options.client_renegotiation, start_or_recv_from = undefined, - send_queue = queue:new(), protocol_cb = ?MODULE }. + +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}. + +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}. - -next_tls_record(_, State) -> - {#ssl_tls{fragment = <<"place holder">>}, State}. -get_timeout(_) -> %% Place holder - infinity. +handle_own_alert(_,_,_, State) -> %% Place holder + {stop, {shutdown, own_alert}, State}. -next_state_connection(_, State) -> %% Place holder - {next_state, connection, State, get_timeout(State)}. +handle_normal_shutdown(_, _, _State) -> %% Place holder + ok. -sequence(_) -> - %%TODO real imp - 1. +%% 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 b74801b50a..ee3daa3c14 100644 --- a/lib/ssl/src/dtls_connection.hrl +++ b/lib/ssl/src/dtls_connection.hrl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2013-2014. All Rights Reserved. +%% Copyright Ericsson AB 2013-2016. 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. @@ -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_connection_sup.erl b/lib/ssl/src/dtls_connection_sup.erl index cf50537869..dc7601a684 100644 --- a/lib/ssl/src/dtls_connection_sup.erl +++ b/lib/ssl/src/dtls_connection_sup.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2014. All Rights Reserved. +%% Copyright Ericsson AB 2007-2016. 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. diff --git a/lib/ssl/src/dtls_handshake.erl b/lib/ssl/src/dtls_handshake.erl index 50c84b712f..5a799cf441 100644 --- a/lib/ssl/src/dtls_handshake.erl +++ b/lib/ssl/src/dtls_handshake.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2013-2014. All Rights Reserved. +%% Copyright Ericsson AB 2013-2016. 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. @@ -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_handshake.hrl b/lib/ssl/src/dtls_handshake.hrl index be32112120..0298fd3105 100644 --- a/lib/ssl/src/dtls_handshake.hrl +++ b/lib/ssl/src/dtls_handshake.hrl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2013-2014. All Rights Reserved. +%% Copyright Ericsson AB 2013-2016. 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. diff --git a/lib/ssl/src/dtls_record.erl b/lib/ssl/src/dtls_record.erl index 2530d66052..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, - 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{}) -> @@ -254,25 +247,56 @@ lowest_protocol_version(Version = {M,_}, {N, _}) when M > N -> Version; lowest_protocol_version(_,Version) -> Version. + +%%-------------------------------------------------------------------- +-spec lowest_protocol_version([dtls_version()]) -> dtls_version(). +%% +%% Description: Lowest protocol version present in a list +%%-------------------------------------------------------------------- +lowest_protocol_version([]) -> + lowest_protocol_version(); +lowest_protocol_version(Versions) -> + [Ver | Vers] = Versions, + lowest_list_protocol_version(Ver, Vers). + %%-------------------------------------------------------------------- -spec highest_protocol_version([dtls_version()]) -> dtls_version(). %% %% 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()]. @@ -289,21 +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. - -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(). @@ -401,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), @@ -414,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). diff --git a/lib/ssl/src/dtls_record.hrl b/lib/ssl/src/dtls_record.hrl index ab59a5fea1..b9f84cbe7f 100644 --- a/lib/ssl/src/dtls_record.hrl +++ b/lib/ssl/src/dtls_record.hrl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2013-2014. All Rights Reserved. +%% Copyright Ericsson AB 2013-2016. 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. diff --git a/lib/ssl/src/dtls_v1.erl b/lib/ssl/src/dtls_v1.erl index 99cedd2adc..8c03bda513 100644 --- a/lib/ssl/src/dtls_v1.erl +++ b/lib/ssl/src/dtls_v1.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2013-2014. All Rights Reserved. +%% Copyright Ericsson AB 2013-2016. 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. diff --git a/lib/ssl/src/inet_tls_dist.erl b/lib/ssl/src/inet_tls_dist.erl index ec26142a75..0da4b3587f 100644 --- a/lib/ssl/src/inet_tls_dist.erl +++ b/lib/ssl/src/inet_tls_dist.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2011-2012. All Rights Reserved. +%% Copyright Ericsson AB 2011-2016. 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. @@ -87,7 +87,8 @@ do_setup(Driver, Kernel, Node, Type, MyNode, LongOrShortNames, SetupTime) -> case inet:getaddr(Address, Driver:family()) of {ok, Ip} -> Timer = dist_util:start_timer(SetupTime), - case erl_epmd:port_please(Name, Ip) of + ErlEpmd = net_kernel:epmd_module(), + case ErlEpmd:port_please(Name, Ip) of {port, TcpPort, Version} -> ?trace("port_please(~p) -> version ~p~n", [Node,Version]), diff --git a/lib/ssl/src/ssl.app.src b/lib/ssl/src/ssl.app.src index 1a2bf90ccf..b26efbd88f 100644 --- a/lib/ssl/src/ssl.app.src +++ b/lib/ssl/src/ssl.app.src @@ -44,6 +44,7 @@ ssl_crl, ssl_crl_cache, ssl_crl_cache_api, + ssl_crl_hash_dir, %% App structure ssl_app, ssl_sup, @@ -54,7 +55,7 @@ {applications, [crypto, public_key, kernel, stdlib]}, {env, []}, {mod, {ssl_app, []}}, - {runtime_dependencies, ["stdlib-2.0","public_key-1.0","kernel-3.0", + {runtime_dependencies, ["stdlib-3.0","public_key-1.2","kernel-3.0", "erts-7.0","crypto-3.3", "inets-5.10.7"]}]}. diff --git a/lib/ssl/src/ssl.appup.src b/lib/ssl/src/ssl.appup.src index 203a4f7d10..fadc67ef80 100644 --- a/lib/ssl/src/ssl.appup.src +++ b/lib/ssl/src/ssl.appup.src @@ -1,7 +1,7 @@ %% -*- erlang -*- {"%VSN%", [ - {<<"^7[.]3[.]3$">>, + {<<"^8[.]0$">>, [{load_module, ssl_handshake, soft_purge, soft_purge, []} ]}, {<<"^7[.][^.].*">>, [{restart_application, ssl}]}, @@ -11,7 +11,7 @@ {<<"3\\..*">>, [{restart_application, ssl}]} ], [ - {<<"^7[.]3[.]3$">>, + {<<"^8[.]0$">>, [{load_module, ssl_handshake, soft_purge, soft_purge, []} ]}, {<<"^7[.][^.].*">>, [{restart_application, ssl}]}, diff --git a/lib/ssl/src/ssl.erl b/lib/ssl/src/ssl.erl index 97a1e2b5a8..d2aeb3258f 100644 --- a/lib/ssl/src/ssl.erl +++ b/lib/ssl/src/ssl.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1999-2015. All Rights Reserved. +%% Copyright Ericsson AB 1999-2016. 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. @@ -34,7 +34,8 @@ listen/2, transport_accept/1, transport_accept/2, ssl_accept/1, ssl_accept/2, ssl_accept/3, controlling_process/2, peername/1, peercert/1, sockname/1, - close/1, close/2, shutdown/2, recv/2, recv/3, send/2, getopts/2, setopts/2 + close/1, close/2, shutdown/2, recv/2, recv/3, send/2, + getopts/2, setopts/2, getstat/1, getstat/2 ]). %% SSL/TLS protocol handling -export([cipher_suites/0, cipher_suites/1, @@ -42,7 +43,7 @@ renegotiate/1, prf/5, negotiated_protocol/1, negotiated_next_protocol/1, connection_information/1, connection_information/2]). %% Misc --export([random_bytes/1, handle_options/2]). +-export([handle_options/2]). -deprecated({negotiated_next_protocol, 1, next_major_release}). -deprecated({connection_info, 1, next_major_release}). @@ -400,24 +401,23 @@ negotiated_next_protocol(Socket) -> end. %%-------------------------------------------------------------------- +-spec cipher_suites() -> [ssl_cipher:erl_cipher_suite()] | [string()]. +%%-------------------------------------------------------------------- +cipher_suites() -> + cipher_suites(erlang). +%%-------------------------------------------------------------------- -spec cipher_suites(erlang | openssl | all) -> [ssl_cipher:erl_cipher_suite()] | [string()]. %% Description: Returns all supported cipher suites. %%-------------------------------------------------------------------- cipher_suites(erlang) -> - Version = tls_record:highest_protocol_version([]), - ssl_cipher:filter_suites([ssl_cipher:erl_suite_definition(S) - || S <- ssl_cipher:suites(Version)]); + [ssl_cipher:erl_suite_definition(Suite) || Suite <- available_suites(default)]; + cipher_suites(openssl) -> - Version = tls_record:highest_protocol_version([]), - [ssl_cipher:openssl_suite_name(S) - || S <- ssl_cipher:filter_suites(ssl_cipher:suites(Version))]; + [ssl_cipher:openssl_suite_name(Suite) || Suite <- available_suites(default)]; + cipher_suites(all) -> - Version = tls_record:highest_protocol_version([]), - ssl_cipher:filter_suites([ssl_cipher:erl_suite_definition(S) - || S <-ssl_cipher:all_suites(Version)]). -cipher_suites() -> - cipher_suites(erlang). + [ssl_cipher:erl_suite_definition(Suite) || Suite <- available_suites(all)]. %%-------------------------------------------------------------------- -spec getopts(#sslsocket{}, [gen_tcp:option_name()]) -> @@ -470,6 +470,32 @@ setopts(#sslsocket{}, Options) -> {error, {options,{not_a_proplist, Options}}}. %%--------------------------------------------------------------- +-spec getstat(Socket) -> + {ok, OptionValues} | {error, inet:posix()} when + Socket :: #sslsocket{}, + OptionValues :: [{inet:stat_option(), integer()}]. +%% +%% Description: Get all statistic options for a socket. +%%-------------------------------------------------------------------- +getstat(Socket) -> + getstat(Socket, inet:stats()). + +%%--------------------------------------------------------------- +-spec getstat(Socket, Options) -> + {ok, OptionValues} | {error, inet:posix()} when + Socket :: #sslsocket{}, + Options :: [inet:stat_option()], + OptionValues :: [{inet:stat_option(), integer()}]. +%% +%% Description: Get one or more statistic options for a socket. +%%-------------------------------------------------------------------- +getstat(#sslsocket{pid = {Listen, #config{transport_info = {Transport, _, _, _}}}}, Options) when is_port(Listen), is_list(Options) -> + ssl_socket:getstat(Transport, Listen, Options); + +getstat(#sslsocket{pid = Pid, fd = {Transport, Socket, _, _}}, Options) when is_pid(Pid), is_list(Options) -> + ssl_socket:getstat(Transport, Socket, Options). + +%%--------------------------------------------------------------- -spec shutdown(#sslsocket{}, read | write | read_write) -> ok | {error, reason()}. %% %% Description: Same as gen_tcp:shutdown/2 @@ -581,25 +607,19 @@ format_error(Error) -> Other end. -%%-------------------------------------------------------------------- --spec random_bytes(integer()) -> binary(). - -%% -%% Description: Generates cryptographically secure random sequence if possible -%% fallbacks on pseudo random function -%%-------------------------------------------------------------------- -random_bytes(N) -> - try crypto:strong_rand_bytes(N) of - RandBytes -> - RandBytes - catch - error:low_entropy -> - crypto:rand_bytes(N) - end. - %%%-------------------------------------------------------------- %%% Internal functions %%%-------------------------------------------------------------------- + +%% Possible filters out suites not supported by crypto +available_suites(default) -> + Version = tls_record:highest_protocol_version([]), + ssl_cipher:filter_suites(ssl_cipher:suites(Version)); + +available_suites(all) -> + Version = tls_record:highest_protocol_version([]), + ssl_cipher:filter_suites(ssl_cipher:all_suites(Version)). + do_connect(Address, Port, #config{transport_info = CbInfo, inet_user = UserOpts, ssl = SslOpts, emulated = EmOpts, inet_ssl = SocketOpts, connection_cb = ConnetionCb}, @@ -712,7 +732,7 @@ handle_options(Opts0, Role) -> default_option_role(server, true, Role), server, Role), renegotiate_at = handle_option(renegotiate_at, Opts, ?DEFAULT_RENEGOTIATE_AT), - hibernate_after = handle_option(hibernate_after, Opts, undefined), + hibernate_after = handle_option(hibernate_after, Opts, infinity), erl_dist = handle_option(erl_dist, Opts, false), alpn_advertised_protocols = handle_option(alpn_advertised_protocols, Opts, undefined), @@ -732,13 +752,15 @@ handle_options(Opts0, Role) -> server, Role), protocol = proplists:get_value(protocol, Opts, tls), padding_check = proplists:get_value(padding_check, Opts, true), + beast_mitigation = handle_option(beast_mitigation, Opts, one_n_minus_one), fallback = handle_option(fallback, Opts, proplists:get_value(fallback, Opts, default_option_role(client, false, Role)), client, Role), crl_check = handle_option(crl_check, Opts, false), - crl_cache = handle_option(crl_cache, Opts, {ssl_crl_cache, {internal, []}}) + crl_cache = handle_option(crl_cache, Opts, {ssl_crl_cache, {internal, []}}), + v2_hello_compatible = handle_option(v2_hello_compatible, Opts, false) }, CbInfo = proplists:get_value(cb_info, Opts, {gen_tcp, tcp, tcp_closed, tcp_error}), @@ -753,7 +775,7 @@ handle_options(Opts0, Role) -> alpn_preferred_protocols, next_protocols_advertised, client_preferred_next_protocols, log_alert, server_name_indication, honor_cipher_order, padding_check, crl_check, crl_cache, - fallback, signature_algs], + fallback, signature_algs, beast_mitigation, v2_hello_compatible], SockOpts = lists:foldl(fun(Key, PropList) -> proplists:delete(Key, PropList) @@ -901,10 +923,13 @@ validate_option(client_renegotiation, Value) when is_boolean(Value) -> validate_option(renegotiate_at, Value) when is_integer(Value) -> erlang:min(Value, ?DEFAULT_RENEGOTIATE_AT); -validate_option(hibernate_after, undefined) -> - undefined; +validate_option(hibernate_after, undefined) -> %% Backwards compatibility + infinity; +validate_option(hibernate_after, infinity) -> + infinity; validate_option(hibernate_after, Value) when is_integer(Value), Value >= 0 -> Value; + validate_option(erl_dist,Value) when is_boolean(Value) -> Value; validate_option(Opt, Value) @@ -990,6 +1015,12 @@ validate_option(crl_check, Value) when (Value == best_effort) or (Value == peer) Value; validate_option(crl_cache, {Cb, {_Handle, Options}} = Value) when is_atom(Cb) and is_list(Options) -> Value; +validate_option(beast_mitigation, Value) when Value == one_n_minus_one orelse + Value == zero_n orelse + Value == disabled -> + Value; +validate_option(v2_hello_compatible, Value) when is_boolean(Value) -> + Value; validate_option(Opt, Value) -> throw({error, {options, {Opt, Value}}}). diff --git a/lib/ssl/src/ssl_alert.erl b/lib/ssl/src/ssl_alert.erl index 3e35e24527..db71b16d80 100644 --- a/lib/ssl/src/ssl_alert.erl +++ b/lib/ssl/src/ssl_alert.erl @@ -73,10 +73,14 @@ reason_code(#alert{description = Description}, _) -> %% %% Description: Returns the error string for given alert. %%-------------------------------------------------------------------- - -alert_txt(#alert{level = Level, description = Description, where = {Mod,Line}}) -> +alert_txt(#alert{level = Level, description = Description, where = {Mod,Line}, reason = undefined}) -> Mod ++ ":" ++ integer_to_list(Line) ++ ":" ++ - level_txt(Level) ++" "++ description_txt(Description). + level_txt(Level) ++" "++ description_txt(Description); +alert_txt(#alert{reason = Reason} = Alert) -> + BaseTxt = alert_txt(Alert#alert{reason = undefined}), + FormatDepth = 9, % Some limit on printed representation of an error + ReasonTxt = lists:flatten(io_lib:format("~P", [Reason, FormatDepth])), + BaseTxt ++ " - " ++ ReasonTxt. %%-------------------------------------------------------------------- %%% Internal functions @@ -85,7 +89,7 @@ alert_txt(#alert{level = Level, description = Description, where = {Mod,Line}}) %% It is very unlikely that an correct implementation will send more than one alert at the time %% So it there is more than 10 warning alerts we consider it an error decode(<<?BYTE(Level), ?BYTE(_), _/binary>>, _, N) when Level == ?WARNING, N > ?MAX_ALERTS -> - ?ALERT_REC(?FATAL, ?DECODE_ERROR); + ?ALERT_REC(?FATAL, ?DECODE_ERROR, too_many_remote_alerts); decode(<<?BYTE(Level), ?BYTE(Description), Rest/binary>>, Acc, N) when Level == ?WARNING -> Alert = ?ALERT_REC(Level, Description), decode(Rest, [Alert | Acc], N + 1); @@ -93,7 +97,7 @@ decode(<<?BYTE(Level), ?BYTE(Description), _Rest/binary>>, Acc, _) when Level == Alert = ?ALERT_REC(Level, Description), lists:reverse([Alert | Acc]); %% No need to decode rest fatal alert will end the connection decode(<<?BYTE(_Level), _/binary>>, _, _) -> - ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER); + ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER, failed_to_decode_remote_alert); decode(<<>>, Acc, _) -> lists:reverse(Acc, []). diff --git a/lib/ssl/src/ssl_alert.hrl b/lib/ssl/src/ssl_alert.hrl index 8c4bd08d31..38facb964f 100644 --- a/lib/ssl/src/ssl_alert.hrl +++ b/lib/ssl/src/ssl_alert.hrl @@ -109,6 +109,7 @@ -define(NO_APPLICATION_PROTOCOL, 120). -define(ALERT_REC(Level,Desc), #alert{level=Level,description=Desc,where={?FILE, ?LINE}}). +-define(ALERT_REC(Level,Desc,Reason), #alert{level=Level,description=Desc,where={?FILE, ?LINE},reason=Reason}). -define(MAX_ALERTS, 10). @@ -116,6 +117,7 @@ -record(alert, { level, description, - where = {?FILE, ?LINE} + where = {?FILE, ?LINE}, + reason }). -endif. % -ifdef(ssl_alert). diff --git a/lib/ssl/src/ssl_api.hrl b/lib/ssl/src/ssl_api.hrl index ceef7b0438..2bd51cf91e 100644 --- a/lib/ssl/src/ssl_api.hrl +++ b/lib/ssl/src/ssl_api.hrl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2013-2014. All Rights Reserved. +%% Copyright Ericsson AB 2013-2016. 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. diff --git a/lib/ssl/src/ssl_app.erl b/lib/ssl/src/ssl_app.erl index 191300b0a1..62e8765d4a 100644 --- a/lib/ssl/src/ssl_app.erl +++ b/lib/ssl/src/ssl_app.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1998-2011. All Rights Reserved. +%% Copyright Ericsson AB 1998-2016. 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. diff --git a/lib/ssl/src/ssl_certificate.erl b/lib/ssl/src/ssl_certificate.erl index e9dc5764a3..3ec3f50e05 100644 --- a/lib/ssl/src/ssl_certificate.erl +++ b/lib/ssl/src/ssl_certificate.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2015 All Rights Reserved. +%% Copyright Ericsson AB 2007-2016 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. diff --git a/lib/ssl/src/ssl_cipher.erl b/lib/ssl/src/ssl_cipher.erl index af53d4abf9..e935c033c7 100644 --- a/lib/ssl/src/ssl_cipher.erl +++ b/lib/ssl/src/ssl_cipher.erl @@ -1,7 +1,7 @@ -%% +% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2015. All Rights Reserved. +%% Copyright Ericsson AB 2007-2016. 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. @@ -38,8 +38,9 @@ cipher_init/3, decipher/6, cipher/5, decipher_aead/6, cipher_aead/6, suite/1, suites/1, all_suites/1, ec_keyed_suites/0, anonymous_suites/1, psk_suites/1, srp_suites/0, - rc4_suites/1, openssl_suite/1, openssl_suite_name/1, filter/2, filter_suites/1, - hash_algorithm/1, sign_algorithm/1, is_acceptable_hash/2, is_fallback/1]). + rc4_suites/1, des_suites/1, openssl_suite/1, openssl_suite_name/1, filter/2, filter_suites/1, + hash_algorithm/1, sign_algorithm/1, is_acceptable_hash/2, is_fallback/1, + random_bytes/1]). -export_type([cipher_suite/0, erl_cipher_suite/0, openssl_cipher_suite/0, @@ -49,7 +50,8 @@ | aes_128_cbc | aes_256_cbc | aes_128_gcm | aes_256_gcm | chacha20_poly1305. -type hash() :: null | sha | md5 | sha224 | sha256 | sha384 | sha512. -type sign_algo() :: rsa | dsa | ecdsa. --type key_algo() :: null | rsa | dhe_rsa | dhe_dss | ecdhe_ecdsa| ecdh_ecdsa | ecdh_rsa| srp_rsa| srp_dss | psk | dhe_psk | rsa_psk | dh_anon | ecdh_anon | srp_anon. +-type key_algo() :: null | rsa | dhe_rsa | dhe_dss | ecdhe_ecdsa| ecdh_ecdsa | ecdh_rsa| srp_rsa| srp_dss | + psk | dhe_psk | rsa_psk | dh_anon | ecdh_anon | srp_anon. -type erl_cipher_suite() :: {key_algo(), cipher(), hash()} % Pre TLS 1.2 %% TLS 1.2, internally PRE TLS 1.2 will use default_prf | {key_algo(), cipher(), hash(), hash() | default_prf}. @@ -102,7 +104,7 @@ cipher_init(?RC4, IV, Key) -> State = crypto:stream_init(rc4, Key), #cipher_state{iv = IV, key = Key, state = State}; cipher_init(?AES_GCM, IV, Key) -> - <<Nonce:64>> = ssl:random_bytes(8), + <<Nonce:64>> = random_bytes(8), #cipher_state{iv = IV, key = Key, nonce = Nonce}; cipher_init(_BCA, IV, Key) -> #cipher_state{iv = IV, key = Key}. @@ -212,7 +214,7 @@ decipher(?RC4, HashSz, CipherState = #cipher_state{state = State0}, Fragment, _, %% alerts may permit certain attacks against CBC mode as used in %% TLS [CBCATT]. It is preferable to uniformly use the %% bad_record_mac alert to hide the specific type of the error." - ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC) + ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC, decryption_failed) end; decipher(?DES, HashSz, CipherState, Fragment, Version, PaddingCheck) -> @@ -270,7 +272,7 @@ block_decipher(Fun, #cipher_state{key=Key, iv=IV} = CipherState0, %% alerts may permit certain attacks against CBC mode as used in %% TLS [CBCATT]. It is preferable to uniformly use the %% bad_record_mac alert to hide the specific type of the error." - ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC) + ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC, decryption_failed) end. aead_ciphertext_to_state(chacha20_poly1305, SeqNo, _IV, AAD0, Fragment, _Version) -> @@ -294,11 +296,11 @@ aead_decipher(Type, #cipher_state{key = Key, iv = IV} = CipherState, Content when is_binary(Content) -> {Content, CipherState}; _ -> - ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC) + ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC, decryption_failed) end catch _:_ -> - ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC) + ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC, decryption_failed) end. %%-------------------------------------------------------------------- @@ -316,7 +318,8 @@ all_suites(Version) -> ++ anonymous_suites(Version) ++ psk_suites(Version) ++ srp_suites() - ++ rc4_suites(Version). + ++ rc4_suites(Version) + ++ des_suites(Version). %%-------------------------------------------------------------------- -spec anonymous_suites(ssl_record:ssl_version() | integer()) -> [cipher_suite()]. %% @@ -420,6 +423,16 @@ rc4_suites({3, N}) when N =< 3 -> ?TLS_RSA_WITH_RC4_128_MD5, ?TLS_ECDH_ECDSA_WITH_RC4_128_SHA, ?TLS_ECDH_RSA_WITH_RC4_128_SHA]. +%%-------------------------------------------------------------------- +-spec des_suites(Version::ssl_record:ssl_version()) -> [cipher_suite()]. +%% +%% Description: Returns a list of the cipher suites +%% with DES cipher, only supported if explicitly set by user. +%% Are not considered secure any more. +%%-------------------------------------------------------------------- +des_suites(_)-> + [?TLS_DHE_RSA_WITH_DES_CBC_SHA, + ?TLS_RSA_WITH_DES_CBC_SHA]. %%-------------------------------------------------------------------- -spec suite_definition(cipher_suite()) -> erl_cipher_suite(). @@ -1461,6 +1474,16 @@ is_acceptable_prf(Prf, Algos) -> is_fallback(CipherSuites)-> lists:member(?TLS_FALLBACK_SCSV, CipherSuites). + +%%-------------------------------------------------------------------- +-spec random_bytes(integer()) -> binary(). + +%% +%% Description: Generates cryptographically secure random sequence +%%-------------------------------------------------------------------- +random_bytes(N) -> + crypto:strong_rand_bytes(N). + %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- @@ -1701,7 +1724,7 @@ get_padding_aux(BlockSize, PadLength) -> random_iv(IV) -> IVSz = byte_size(IV), - ssl:random_bytes(IVSz). + random_bytes(IVSz). next_iv(Bin, IV) -> BinSz = byte_size(Bin), @@ -1729,7 +1752,8 @@ dhe_rsa_suites() -> ?TLS_DHE_RSA_WITH_DES_CBC_SHA, ?TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, ?TLS_DHE_RSA_WITH_AES_256_GCM_SHA384, - ?TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256]. + ?TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256 + ]. psk_rsa_suites() -> [?TLS_RSA_PSK_WITH_AES_256_GCM_SHA384, diff --git a/lib/ssl/src/ssl_connection.erl b/lib/ssl/src/ssl_connection.erl index 0f0072ba34..adee59393e 100644 --- a/lib/ssl/src/ssl_connection.erl +++ b/lib/ssl/src/ssl_connection.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2013-2015. All Rights Reserved. +%% Copyright Ericsson AB 2013-2016. 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. @@ -38,7 +38,7 @@ %% Setup -export([connect/8, ssl_accept/7, handshake/2, handshake/3, - socket_control/4, socket_control/5]). + socket_control/4, socket_control/5, start_or_recv_cancel_timer/2]). %% User Events -export([send/2, recv/3, close/2, shutdown/2, @@ -47,12 +47,16 @@ connection_information/1 ]). --export([handle_session/7]). +%% General gen_statem state functions with extra callback argument +%% to determine if it is an SSL/TLS or DTLS gen_statem machine +-export([init/4, hello/4, abbreviated/4, certify/4, cipher/4, connection/4, downgrade/4]). -%% SSL FSM state functions --export([hello/3, abbreviated/3, certify/3, cipher/3, connection/3]). -%% SSL all state functions --export([handle_sync_event/4, handle_info/3, terminate/3, format_status/2]). +%% gen_statem callbacks +-export([terminate/3, format_status/2]). + +%% +-export([handle_info/3, handle_call/5, handle_session/7, ssl_config/3, + prepare_connection/2, hibernate_after/3]). %%==================================================================== @@ -100,7 +104,7 @@ ssl_accept(Connection, Port, Socket, Opts, User, CbInfo, Timeout) -> %% Description: Starts ssl handshake. %%-------------------------------------------------------------------- handshake(#sslsocket{pid = Pid}, Timeout) -> - case sync_send_all_state_event(Pid, {start, Timeout}) of + case call(Pid, {start, Timeout}) of connected -> ok; Error -> @@ -114,7 +118,7 @@ handshake(#sslsocket{pid = Pid}, Timeout) -> %% Description: Starts ssl handshake with some new options %%-------------------------------------------------------------------- handshake(#sslsocket{pid = Pid}, SslOptions, Timeout) -> - case sync_send_all_state_event(Pid, {start, SslOptions, Timeout}) of + case call(Pid, {start, SslOptions, Timeout}) of connected -> ok; Error -> @@ -148,7 +152,7 @@ socket_control(Connection, Socket, Pid, Transport, ListenTracker) -> %% Description: Sends data over the ssl connection %%-------------------------------------------------------------------- send(Pid, Data) -> - sync_send_all_state_event(Pid, {application_data, + call(Pid, {application_data, %% iolist_to_binary should really %% be called iodata_to_binary() erlang:iolist_to_binary(Data)}). @@ -160,7 +164,7 @@ send(Pid, Data) -> %% Description: Receives data when active = false %%-------------------------------------------------------------------- recv(Pid, Length, Timeout) -> - sync_send_all_state_event(Pid, {recv, Length, Timeout}). + call(Pid, {recv, Length, Timeout}). %%-------------------------------------------------------------------- -spec connection_information(pid()) -> {ok, list()} | {error, reason()}. @@ -168,7 +172,7 @@ recv(Pid, Length, Timeout) -> %% Description: Get the SNI hostname %%-------------------------------------------------------------------- connection_information(Pid) when is_pid(Pid) -> - sync_send_all_state_event(Pid, connection_information). + call(Pid, connection_information). %%-------------------------------------------------------------------- -spec close(pid(), {close, Timeout::integer() | @@ -178,7 +182,7 @@ connection_information(Pid) when is_pid(Pid) -> %% Description: Close an ssl connection %%-------------------------------------------------------------------- close(ConnectionPid, How) -> - case sync_send_all_state_event(ConnectionPid, How) of + case call(ConnectionPid, How) of {error, closed} -> ok; Other -> @@ -190,7 +194,7 @@ close(ConnectionPid, How) -> %% Description: Same as gen_tcp:shutdown/2 %%-------------------------------------------------------------------- shutdown(ConnectionPid, How) -> - sync_send_all_state_event(ConnectionPid, {shutdown, How}). + call(ConnectionPid, {shutdown, How}). %%-------------------------------------------------------------------- -spec new_user(pid(), pid()) -> ok | {error, reason()}. @@ -199,7 +203,7 @@ shutdown(ConnectionPid, How) -> %% or once. %%-------------------------------------------------------------------- new_user(ConnectionPid, User) -> - sync_send_all_state_event(ConnectionPid, {new_user, User}). + call(ConnectionPid, {new_user, User}). %%-------------------------------------------------------------------- -spec negotiated_protocol(pid()) -> {ok, binary()} | {error, reason()}. @@ -207,7 +211,7 @@ new_user(ConnectionPid, User) -> %% Description: Returns the negotiated protocol %%-------------------------------------------------------------------- negotiated_protocol(ConnectionPid) -> - sync_send_all_state_event(ConnectionPid, negotiated_protocol). + call(ConnectionPid, negotiated_protocol). %%-------------------------------------------------------------------- -spec get_opts(pid(), list()) -> {ok, list()} | {error, reason()}. @@ -215,14 +219,14 @@ negotiated_protocol(ConnectionPid) -> %% Description: Same as inet:getopts/2 %%-------------------------------------------------------------------- get_opts(ConnectionPid, OptTags) -> - sync_send_all_state_event(ConnectionPid, {get_opts, OptTags}). + call(ConnectionPid, {get_opts, OptTags}). %%-------------------------------------------------------------------- -spec set_opts(pid(), list()) -> ok | {error, reason()}. %% %% Description: Same as inet:setopts/2 %%-------------------------------------------------------------------- set_opts(ConnectionPid, Options) -> - sync_send_all_state_event(ConnectionPid, {set_opts, Options}). + call(ConnectionPid, {set_opts, Options}). %%-------------------------------------------------------------------- -spec session_info(pid()) -> {ok, list()} | {error, reason()}. @@ -230,7 +234,7 @@ set_opts(ConnectionPid, Options) -> %% Description: Returns info about the ssl session %%-------------------------------------------------------------------- session_info(ConnectionPid) -> - sync_send_all_state_event(ConnectionPid, session_info). + call(ConnectionPid, session_info). %%-------------------------------------------------------------------- -spec peer_certificate(pid()) -> {ok, binary()| undefined} | {error, reason()}. @@ -238,7 +242,7 @@ session_info(ConnectionPid) -> %% Description: Returns the peer cert %%-------------------------------------------------------------------- peer_certificate(ConnectionPid) -> - sync_send_all_state_event(ConnectionPid, peer_certificate). + call(ConnectionPid, peer_certificate). %%-------------------------------------------------------------------- -spec renegotiation(pid()) -> ok | {error, reason()}. @@ -246,7 +250,7 @@ peer_certificate(ConnectionPid) -> %% Description: Starts a renegotiation of the ssl session. %%-------------------------------------------------------------------- renegotiation(ConnectionPid) -> - sync_send_all_state_event(ConnectionPid, renegotiate). + call(ConnectionPid, renegotiate). %%-------------------------------------------------------------------- -spec prf(pid(), binary() | 'master_secret', binary(), @@ -256,9 +260,13 @@ renegotiation(ConnectionPid) -> %% Description: use a ssl sessions TLS PRF to generate key material %%-------------------------------------------------------------------- prf(ConnectionPid, Secret, Label, Seed, WantedLength) -> - sync_send_all_state_event(ConnectionPid, {prf, Secret, Label, Seed, WantedLength}). - + call(ConnectionPid, {prf, Secret, Label, Seed, WantedLength}). +%%-------------------------------------------------------------------- +-spec handle_session(#server_hello{}, ssl_record:ssl_version(), + binary(), #connection_states{}, _,_, #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- handle_session(#server_hello{cipher_suite = CipherSuite, compression_method = Compression}, Version, NewId, ConnectionStates, ProtoExt, Protocol0, @@ -290,61 +298,104 @@ handle_session(#server_hello{cipher_suite = CipherSuite, handle_resumed_session(NewId, State#state{connection_states = ConnectionStates}) end. - + %%-------------------------------------------------------------------- --spec hello(start | #hello_request{} | #server_hello{} | term(), - #state{}, tls_connection | dtls_connection) -> - gen_fsm_state_return(). +-spec ssl_config(#ssl_options{}, client | server, #state{}) -> #state{}. %%-------------------------------------------------------------------- -hello(start, #state{role = server} = State0, Connection) -> - {Record, State} = Connection:next_record(State0), - Connection:next_state(hello, hello, Record, State); +ssl_config(Opts, Role, State) -> + {ok, Ref, CertDbHandle, FileRefHandle, CacheHandle, CRLDbInfo, + OwnCert, Key, DHParams} = + ssl_config:init(Opts, Role), + Handshake = ssl_handshake:init_handshake_history(), + TimeStamp = erlang:monotonic_time(), + Session = State#state.session, + State#state{tls_handshake_history = Handshake, + session = Session#session{own_certificate = OwnCert, + time_stamp = TimeStamp}, + file_ref_db = FileRefHandle, + cert_db_ref = Ref, + cert_db = CertDbHandle, + crl_db = CRLDbInfo, + session_cache = CacheHandle, + private_key = Key, + diffie_hellman_params = DHParams, + ssl_options = Opts}. -hello(#hello_request{}, #state{role = client} = State0, Connection) -> - {Record, State} = Connection:next_record(State0), - Connection:next_state(hello, hello, Record, State); +%%==================================================================== +%% gen_statem state functions +%%==================================================================== +%%-------------------------------------------------------------------- +-spec init(gen_statem:event_type(), + {start, timeout()} | {start, {list(), list()}, timeout()}| term(), + #state{}, tls_connection | dtls_connection) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- -hello({common_client_hello, Type, ServerHelloExt}, - State, Connection) -> +init({call, From}, {start, Timeout}, State0, Connection) -> + Timer = start_or_recv_cancel_timer(Timeout, From), + {Record, State} = Connection:next_record(State0#state{start_or_recv_from = From, + timer = Timer}), + Connection:next_event(hello, Record, State); +init({call, From}, {start, {Opts, EmOpts}, Timeout}, + #state{role = Role} = State0, Connection) -> + try + State = ssl_config(Opts, Role, State0), + init({call, From}, {start, Timeout}, + State#state{ssl_options = Opts, socket_options = EmOpts}, Connection) + catch throw:Error -> + {stop_and_reply, normal, {reply, From, {error, Error}}} + end; +init({call, From}, Msg, State, Connection) -> + handle_call(Msg, From, init, State, Connection); +init(_Type, _Event, _State, _Connection) -> + {keep_state_and_data, [postpone]}. + +%%-------------------------------------------------------------------- +-spec hello(gen_statem:event_type(), + #hello_request{} | #server_hello{} | term(), + #state{}, tls_connection | dtls_connection) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +hello({call, From}, Msg, State, Connection) -> + handle_call(Msg, From, hello, State, Connection); +hello(internal, {common_client_hello, Type, ServerHelloExt}, State, Connection) -> do_server_hello(Type, ServerHelloExt, State, Connection); -hello(timeout, State, _) -> - {next_state, hello, State, hibernate}; - -hello(Msg, State, Connection) -> - Connection:handle_unexpected_message(Msg, hello, State). +hello(info, Msg, State, _) -> + handle_info(Msg, hello, State); +hello(Type, Msg, State, Connection) -> + handle_common_event(Type, Msg, hello, State, Connection). %%-------------------------------------------------------------------- --spec abbreviated(#hello_request{} | #finished{} | term(), +-spec abbreviated(gen_statem:event_type(), + #hello_request{} | #finished{} | term(), #state{}, tls_connection | dtls_connection) -> - gen_fsm_state_return(). + gen_statem:state_function_result(). %%-------------------------------------------------------------------- -abbreviated(#hello_request{}, State0, Connection) -> - {Record, State} = Connection:next_record(State0), - Connection:next_state(abbreviated, hello, Record, State); +abbreviated({call, From}, Msg, State, Connection) -> + handle_call(Msg, From, abbreviated, State, Connection); -abbreviated(#finished{verify_data = Data} = Finished, +abbreviated(internal, #finished{verify_data = Data} = Finished, #state{role = server, negotiated_version = Version, expecting_finished = true, tls_handshake_history = Handshake, session = #session{master_secret = MasterSecret}, connection_states = ConnectionStates0} = - State, Connection) -> + State0, Connection) -> case ssl_handshake:verify_connection(Version, Finished, client, get_current_prf(ConnectionStates0, write), MasterSecret, Handshake) of verified -> ConnectionStates = ssl_record:set_client_verify_data(current_both, Data, ConnectionStates0), - Connection:next_state_connection(abbreviated, - ack_connection( - State#state{connection_states = ConnectionStates, - expecting_finished = false})); + {Record, State} = prepare_connection(State0#state{connection_states = ConnectionStates, + expecting_finished = false}, Connection), + Connection:next_event(connection, Record, State); #alert{} = Alert -> - Connection:handle_own_alert(Alert, Version, abbreviated, State) + Connection:handle_own_alert(Alert, Version, abbreviated, State0) end; -abbreviated(#finished{verify_data = Data} = Finished, +abbreviated(internal, #finished{verify_data = Data} = Finished, #state{role = client, tls_handshake_history = Handshake0, session = #session{master_secret = MasterSecret}, negotiated_version = Version, @@ -355,40 +406,49 @@ abbreviated(#finished{verify_data = Data} = Finished, verified -> ConnectionStates1 = ssl_record:set_server_verify_data(current_read, Data, ConnectionStates0), - State = + State1 = finalize_handshake(State0#state{connection_states = ConnectionStates1}, abbreviated, Connection), - Connection:next_state_connection(abbreviated, - ack_connection(State#state{expecting_finished = false})); - #alert{} = Alert -> + {Record, State} = prepare_connection(State1#state{expecting_finished = false}, Connection), + Connection:next_event(connection, Record, State); + #alert{} = Alert -> Connection:handle_own_alert(Alert, Version, abbreviated, State0) end; %% only allowed to send next_protocol message after change cipher spec %% & before finished message and it is not allowed during renegotiation -abbreviated(#next_protocol{selected_protocol = SelectedProtocol}, +abbreviated(internal, #next_protocol{selected_protocol = SelectedProtocol}, #state{role = server, expecting_next_protocol_negotiation = true} = State0, Connection) -> - {Record, State} = Connection:next_record(State0#state{negotiated_protocol = SelectedProtocol}), - Connection:next_state(abbreviated, abbreviated, Record, State#state{expecting_next_protocol_negotiation = false}); - -abbreviated(timeout, State, _) -> - {next_state, abbreviated, State, hibernate }; - -abbreviated(Msg, State, Connection) -> - Connection:handle_unexpected_message(Msg, abbreviated, State). - + {Record, State} = + Connection:next_record(State0#state{negotiated_protocol = SelectedProtocol}), + Connection:next_event(abbreviated, Record, + State#state{expecting_next_protocol_negotiation = false}); +abbreviated(internal, + #change_cipher_spec{type = <<1>>}, #state{connection_states = ConnectionStates0} = + State0, Connection) -> + ConnectionStates1 = + ssl_record:activate_pending_connection_state(ConnectionStates0, read), + {Record, State} = Connection:next_record(State0#state{connection_states = + ConnectionStates1}), + Connection:next_event(abbreviated, Record, State#state{expecting_finished = true}); +abbreviated(info, Msg, State, _) -> + handle_info(Msg, abbreviated, State); +abbreviated(Type, Msg, State, Connection) -> + handle_common_event(Type, Msg, abbreviated, State, Connection). + %%-------------------------------------------------------------------- --spec certify(#hello_request{} | #certificate{} | #server_key_exchange{} | +-spec certify(gen_statem:event_type(), + #hello_request{} | #certificate{} | #server_key_exchange{} | #certificate_request{} | #server_hello_done{} | #client_key_exchange{} | term(), #state{}, tls_connection | dtls_connection) -> - gen_fsm_state_return(). + gen_statem:state_function_result(). %%-------------------------------------------------------------------- -certify(#hello_request{}, State0, Connection) -> - {Record, State} = Connection:next_record(State0), - Connection:next_state(certify, hello, Record, State); - -certify(#certificate{asn1_certificates = []}, +certify({call, From}, Msg, State, Connection) -> + handle_call(Msg, From, certify, State, Connection); +certify(info, Msg, State, _) -> + handle_info(Msg, certify, State); +certify(internal, #certificate{asn1_certificates = []}, #state{role = server, negotiated_version = Version, ssl_options = #ssl_options{verify = verify_peer, fail_if_no_peer_cert = true}} = @@ -396,15 +456,24 @@ certify(#certificate{asn1_certificates = []}, Alert = ?ALERT_REC(?FATAL,?HANDSHAKE_FAILURE), Connection:handle_own_alert(Alert, Version, certify, State); -certify(#certificate{asn1_certificates = []}, +certify(internal, #certificate{asn1_certificates = []}, #state{role = server, ssl_options = #ssl_options{verify = verify_peer, fail_if_no_peer_cert = false}} = State0, Connection) -> - {Record, State} = Connection:next_record(State0#state{client_certificate_requested = false}), - Connection:next_state(certify, certify, Record, State); + {Record, State} = + Connection:next_record(State0#state{client_certificate_requested = false}), + Connection:next_event(certify, Record, State); + +certify(internal, #certificate{}, + #state{role = server, + negotiated_version = Version, + ssl_options = #ssl_options{verify = verify_none}} = + State, Connection) -> + Alert = ?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE, unrequested_certificate), + Connection:handle_own_alert(Alert, Version, certify, State); -certify(#certificate{} = Cert, +certify(internal, #certificate{} = Cert, #state{negotiated_version = Version, role = Role, cert_db = CertDbHandle, @@ -426,7 +495,7 @@ certify(#certificate{} = Cert, Connection:handle_own_alert(Alert, Version, certify, State) end; -certify(#server_key_exchange{exchange_keys = Keys}, +certify(internal, #server_key_exchange{exchange_keys = Keys}, #state{role = client, negotiated_version = Version, key_algorithm = Alg, public_key_info = PubKeyInfo, @@ -438,44 +507,43 @@ certify(#server_key_exchange{exchange_keys = Keys}, Alg == srp_dss; Alg == srp_rsa; Alg == srp_anon -> Params = ssl_handshake:decode_server_key(Keys, Alg, Version), + %% Use negotiated value if TLS-1.2 otherwhise return default HashSign = negotiated_hashsign(Params#server_key_params.hashsign, Alg, PubKeyInfo, Version), + case is_anonymous(Alg) of true -> calculate_secret(Params#server_key_params.params, State#state{hashsign_algorithm = HashSign}, Connection); false -> - case ssl_handshake:verify_server_key(Params, HashSign, ConnectionStates, Version, PubKeyInfo) of + case ssl_handshake:verify_server_key(Params, HashSign, + ConnectionStates, Version, PubKeyInfo) of true -> calculate_secret(Params#server_key_params.params, - State#state{hashsign_algorithm = HashSign}, Connection); + State#state{hashsign_algorithm = HashSign}, + Connection); false -> Connection:handle_own_alert(?ALERT_REC(?FATAL, ?DECRYPT_ERROR), Version, certify, State) end end; -certify(#server_key_exchange{} = Msg, - #state{role = client, key_algorithm = rsa} = State, Connection) -> - Connection:handle_unexpected_message(Msg, certify_server_keyexchange, State); - -certify(#certificate_request{hashsign_algorithms = HashSigns}, +certify(internal, #certificate_request{} = CertRequest, #state{session = #session{own_certificate = Cert}, - key_algorithm = KeyExAlg, + role = client, ssl_options = #ssl_options{signature_algs = SupportedHashSigns}, negotiated_version = Version} = State0, Connection) -> - - case ssl_handshake:select_hashsign(HashSigns, Cert, KeyExAlg, SupportedHashSigns, Version) of + case ssl_handshake:select_hashsign(CertRequest, Cert, SupportedHashSigns, Version) of #alert {} = Alert -> Connection:handle_own_alert(Alert, Version, certify, State0); NegotiatedHashSign -> {Record, State} = Connection:next_record(State0#state{client_certificate_requested = true}), - Connection:next_state(certify, certify, Record, + Connection:next_event(certify, Record, State#state{cert_hashsign_algorithm = NegotiatedHashSign}) end; %% PSK and RSA_PSK might bypass the Server-Key-Exchange -certify(#server_hello_done{}, +certify(internal, #server_hello_done{}, #state{session = #session{master_secret = undefined}, negotiated_version = Version, psk_identity = PSKIdentity, @@ -493,27 +561,29 @@ certify(#server_hello_done{}, client_certify_and_key_exchange(State, Connection) end; -certify(#server_hello_done{}, +certify(internal, #server_hello_done{}, #state{session = #session{master_secret = undefined}, ssl_options = #ssl_options{user_lookup_fun = PSKLookup}, - negotiated_version = {Major, Minor}, + negotiated_version = {Major, Minor} = Version, psk_identity = PSKIdentity, premaster_secret = undefined, role = client, key_algorithm = Alg} = State0, Connection) when Alg == rsa_psk -> - Rand = ssl:random_bytes(?NUM_OF_PREMASTERSECRET_BYTES-2), + Rand = ssl_cipher:random_bytes(?NUM_OF_PREMASTERSECRET_BYTES-2), RSAPremasterSecret = <<?BYTE(Major), ?BYTE(Minor), Rand/binary>>, - case ssl_handshake:premaster_secret({Alg, PSKIdentity}, PSKLookup, RSAPremasterSecret) of + case ssl_handshake:premaster_secret({Alg, PSKIdentity}, PSKLookup, + RSAPremasterSecret) of #alert{} = Alert -> - Alert; + Connection:handle_own_alert(Alert, Version, certify, State0); PremasterSecret -> - State = master_secret(PremasterSecret, State0#state{premaster_secret = RSAPremasterSecret}), + State = master_secret(PremasterSecret, + State0#state{premaster_secret = RSAPremasterSecret}), client_certify_and_key_exchange(State, Connection) end; %% Master secret was determined with help of server-key exchange msg -certify(#server_hello_done{}, +certify(internal, #server_hello_done{}, #state{session = #session{master_secret = MasterSecret} = Session, connection_states = ConnectionStates0, negotiated_version = Version, @@ -529,7 +599,7 @@ certify(#server_hello_done{}, end; %% Master secret is calculated from premaster_secret -certify(#server_hello_done{}, +certify(internal, #server_hello_done{}, #state{session = Session0, connection_states = ConnectionStates0, negotiated_version = Version, @@ -546,14 +616,15 @@ certify(#server_hello_done{}, Connection:handle_own_alert(Alert, Version, certify, State0) end; -certify(#client_key_exchange{} = Msg, +certify(internal = Type, #client_key_exchange{} = Msg, #state{role = server, client_certificate_requested = true, - ssl_options = #ssl_options{fail_if_no_peer_cert = true}} = State, Connection) -> + ssl_options = #ssl_options{fail_if_no_peer_cert = true}} = State, + Connection) -> %% We expect a certificate here - Connection:handle_unexpected_message(Msg, certify_client_key_exchange, State); + handle_common_event(Type, Msg, certify, State, Connection); -certify(#client_key_exchange{exchange_keys = Keys}, +certify(internal, #client_key_exchange{exchange_keys = Keys}, State = #state{key_algorithm = KeyAlg, negotiated_version = Version}, Connection) -> try certify_client_key_exchange(ssl_handshake:decode_client_key(Keys, KeyAlg, Version), @@ -563,22 +634,23 @@ certify(#client_key_exchange{exchange_keys = Keys}, Connection:handle_own_alert(Alert, Version, certify, State) end; -certify(timeout, State, _) -> - {next_state, certify, State, hibernate}; - -certify(Msg, State, Connection) -> - Connection:handle_unexpected_message(Msg, certify, State). - +certify(Type, Msg, State, Connection) -> + handle_common_event(Type, Msg, certify, State, Connection). + %%-------------------------------------------------------------------- --spec cipher(#hello_request{} | #certificate_verify{} | #finished{} | term(), +-spec cipher(gen_statem:event_type(), + #hello_request{} | #certificate_verify{} | #finished{} | term(), #state{}, tls_connection | dtls_connection) -> - gen_fsm_state_return(). + gen_statem:state_function_result(). %%-------------------------------------------------------------------- -cipher(#hello_request{}, State0, Connection) -> - {Record, State} = Connection:next_record(State0), - Connection:next_state(cipher, hello, Record, State); +cipher({call, From}, Msg, State, Connection) -> + handle_call(Msg, From, cipher, State, Connection); -cipher(#certificate_verify{signature = Signature, hashsign_algorithm = CertHashSign}, +cipher(info, Msg, State, _) -> + handle_info(Msg, cipher, State); + +cipher(internal, #certificate_verify{signature = Signature, + hashsign_algorithm = CertHashSign}, #state{role = server, key_algorithm = KexAlg, public_key_info = PublicKeyInfo, @@ -593,19 +665,20 @@ cipher(#certificate_verify{signature = Signature, hashsign_algorithm = CertHashS Version, HashSign, MasterSecret, Handshake) of valid -> {Record, State} = Connection:next_record(State0), - Connection:next_state(cipher, cipher, Record, + Connection:next_event(cipher, Record, State#state{cert_hashsign_algorithm = HashSign}); #alert{} = Alert -> Connection:handle_own_alert(Alert, Version, cipher, State0) end; %% client must send a next protocol message if we are expecting it -cipher(#finished{}, #state{role = server, expecting_next_protocol_negotiation = true, - negotiated_protocol = undefined, negotiated_version = Version} = State0, +cipher(internal, #finished{}, + #state{role = server, expecting_next_protocol_negotiation = true, + negotiated_protocol = undefined, negotiated_version = Version} = State0, Connection) -> Connection:handle_own_alert(?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE), Version, cipher, State0); -cipher(#finished{verify_data = Data} = Finished, +cipher(internal, #finished{verify_data = Data} = Finished, #state{negotiated_version = Version, host = Host, port = Port, @@ -621,109 +694,166 @@ cipher(#finished{verify_data = Data} = Finished, MasterSecret, Handshake0) of verified -> Session = register_session(Role, Host, Port, Session0), - cipher_role(Role, Data, Session, State#state{expecting_finished = false}, Connection); + cipher_role(Role, Data, Session, + State#state{expecting_finished = false}, Connection); #alert{} = Alert -> Connection:handle_own_alert(Alert, Version, cipher, State) end; %% only allowed to send next_protocol message after change cipher spec %% & before finished message and it is not allowed during renegotiation -cipher(#next_protocol{selected_protocol = SelectedProtocol}, +cipher(internal, #next_protocol{selected_protocol = SelectedProtocol}, #state{role = server, expecting_next_protocol_negotiation = true, expecting_finished = true} = State0, Connection) -> - {Record, State} = Connection:next_record(State0#state{negotiated_protocol = SelectedProtocol}), - Connection:next_state(cipher, cipher, Record, State#state{expecting_next_protocol_negotiation = false}); - -cipher(timeout, State, _) -> - {next_state, cipher, State, hibernate}; - -cipher(Msg, State, Connection) -> - Connection:handle_unexpected_message(Msg, cipher, State). + {Record, State} = + Connection:next_record(State0#state{negotiated_protocol = SelectedProtocol}), + Connection:next_event(cipher, Record, + State#state{expecting_next_protocol_negotiation = false}); +cipher(internal, #change_cipher_spec{type = <<1>>}, #state{connection_states = ConnectionStates0} = + State0, Connection) -> + ConnectionStates1 = + ssl_record:activate_pending_connection_state(ConnectionStates0, read), + {Record, State} = Connection:next_record(State0#state{connection_states = + ConnectionStates1}), + Connection:next_event(cipher, Record, State#state{expecting_finished = true}); +cipher(Type, Msg, State, Connection) -> + handle_common_event(Type, Msg, cipher, State, Connection). %%-------------------------------------------------------------------- --spec connection(term(), #state{}, tls_connection | dtls_connection) -> - gen_fsm_state_return(). +-spec connection(gen_statem:event_type(), term(), + #state{}, tls_connection | dtls_connection) -> + gen_statem:state_function_result(). %%-------------------------------------------------------------------- -connection(timeout, State, _) -> - {next_state, connection, State, hibernate}; +connection({call, From}, {application_data, Data}, + #state{protocol_cb = Connection} = State, Connection) -> + %% We should look into having a worker process to do this to + %% parallize send and receive decoding and not block the receiver + %% if sending is overloading the socket. + try + Connection:write_application_data(Data, From, State) + catch throw:Error -> + hibernate_after(connection, State, [{reply, From, Error}]) + end; +connection({call, RecvFrom}, {recv, N, Timeout}, + #state{protocol_cb = Connection, socket_options = + #socket_options{active = false}} = State0, Connection) -> + Timer = start_or_recv_cancel_timer(Timeout, RecvFrom), + Connection:passive_receive(State0#state{bytes_to_read = N, + start_or_recv_from = RecvFrom, + timer = Timer}, connection); +connection({call, From}, renegotiate, #state{protocol_cb = Connection} = State, + Connection) -> + Connection:renegotiate(State#state{renegotiation = {true, From}}, []); +connection({call, From}, peer_certificate, + #state{session = #session{peer_certificate = Cert}} = State, _) -> + hibernate_after(connection, State, [{reply, From, {ok, Cert}}]); +connection({call, From}, connection_information, State, _) -> + Info = connection_info(State), + hibernate_after(connection, State, [{reply, From, {ok, Info}}]); +connection({call, From}, session_info, #state{session = #session{session_id = Id, + cipher_suite = Suite}} = State, _) -> + SessionInfo = [{session_id, Id}, + {cipher_suite, ssl_cipher:erl_suite_definition(Suite)}], + hibernate_after(connection, State, [{reply, From, SessionInfo}]); +connection({call, From}, negotiated_protocol, + #state{negotiated_protocol = undefined} = State, _) -> + hibernate_after(connection, State, [{reply, From, {error, protocol_not_negotiated}}]); +connection({call, From}, negotiated_protocol, + #state{negotiated_protocol = SelectedProtocol} = State, _) -> + hibernate_after(connection, State, + [{reply, From, {ok, SelectedProtocol}}]); +connection({call, From}, Msg, State, Connection) -> + handle_call(Msg, From, connection, State, Connection); +connection(info, Msg, State, _) -> + handle_info(Msg, connection, State); +connection(internal, {recv, _}, State, Connection) -> + Connection:passive_receive(State, connection); +connection(Type, Msg, State, Connection) -> + handle_common_event(Type, Msg, connection, State, Connection). -connection(Msg, State, Connection) -> - Connection:handle_unexpected_message(Msg, connection, State). +%%-------------------------------------------------------------------- +-spec downgrade(gen_statem:event_type(), term(), + #state{}, tls_connection | dtls_connection) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +downgrade(internal, #alert{description = ?CLOSE_NOTIFY}, + #state{transport_cb = Transport, socket = Socket, + downgrade = {Pid, From}} = State, _) -> + ssl_socket:setopts(Transport, Socket, [{active, false}, {packet, 0}, {mode, binary}]), + Transport:controlling_process(Socket, Pid), + gen_statem:reply(From, {ok, Socket}), + {stop, normal, State}; +downgrade(timeout, downgrade, #state{downgrade = {_, From}} = State, _) -> + gen_statem:reply(From, {error, timeout}), + {stop, normal, State}; +downgrade(Type, Event, State, Connection) -> + handle_common_event(Type, Event, downgrade, State, Connection). %%-------------------------------------------------------------------- -%% Description: Whenever a gen_fsm receives an event sent using -%% gen_fsm:sync_send_all_state_event/2,3, this function is called to handle -%% the event. +%% Event handling functions called by state functions to handle +%% common or unexpected events for the state. %%-------------------------------------------------------------------- -handle_sync_event({application_data, Data}, From, connection, - #state{protocol_cb = Connection} = State) -> - %% We should look into having a worker process to do this to - %% parallize send and receive decoding and not block the receiver - %% if sending is overloading the socket. - try - Connection:write_application_data(Data, From, State) - catch throw:Error -> - {reply, Error, connection, State, get_timeout(State)} +handle_common_event(internal, {handshake, {#hello_request{} = Handshake, _}}, connection = StateName, + #state{role = client} = State, _) -> + %% Should not be included in handshake history + {next_state, StateName, State#state{renegotiation = {true, peer}}, [{next_event, internal, Handshake}]}; +handle_common_event(internal, {handshake, {#hello_request{}, _}}, StateName, #state{role = client}, _) + when StateName =/= connection -> + {keep_state_and_data}; +handle_common_event(internal, {handshake, {Handshake, Raw}}, StateName, + #state{tls_handshake_history = Hs0} = State0, Connection) -> + %% This function handles client SNI hello extension when Handshake is + %% a client_hello, which needs to be determined by the connection callback. + %% In other cases this is a noop + State = Connection:handle_sni_extension(Handshake, State0), + HsHist = ssl_handshake:update_handshake_history(Hs0, Raw), + {next_state, StateName, State#state{tls_handshake_history = HsHist}, + [{next_event, internal, Handshake}]}; +handle_common_event(internal, {tls_record, TLSRecord}, StateName, State, Connection) -> + Connection:handle_common_event(internal, TLSRecord, StateName, State); +handle_common_event(timeout, hibernate, _, _, _) -> + {keep_state_and_data, [hibernate]}; +handle_common_event(internal, {application_data, Data}, StateName, State0, Connection) -> + case Connection:read_application_data(Data, State0) of + {stop, Reason, State} -> + {stop, Reason, State}; + {Record, State} -> + Connection:next_event(StateName, Record, State) end; -handle_sync_event({application_data, Data}, From, StateName, - #state{send_queue = Queue} = State) -> +handle_common_event(internal, #change_cipher_spec{type = <<1>>}, StateName, + #state{negotiated_version = Version} = State, Connection) -> + Connection:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE), Version, + StateName, State); +handle_common_event(internal, _, _, _, _) -> + {keep_state_and_data, [postpone]}; +handle_common_event(_Type, Msg, StateName, #state{negotiated_version = Version} = State, + Connection) -> + Alert = ?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE), + Connection:handle_own_alert(Alert, Version, {StateName, Msg}, State). + +handle_call({application_data, _Data}, _, _, _, _) -> %% In renegotiation priorities handshake, send data when handshake is finished - {next_state, StateName, - State#state{send_queue = queue:in({From, Data}, Queue)}, - get_timeout(State)}; - -handle_sync_event({start, Timeout}, StartFrom, hello, #state{role = Role, - protocol_cb = Connection, - ssl_options = SSLOpts} = State0) -> - try - State = ssl_config(SSLOpts, Role, State0), - Timer = start_or_recv_cancel_timer(Timeout, StartFrom), - Connection:hello(start, State#state{start_or_recv_from = StartFrom, - timer = Timer}) - catch throw:Error -> - {stop, normal, {error, Error}, State0} - end; - -handle_sync_event({start, {Opts, EmOpts}, Timeout}, From, StateName, State) -> - try - handle_sync_event({start, Timeout}, From, StateName, State#state{socket_options = EmOpts, - ssl_options = Opts}) - catch throw:Error -> - {stop, normal, {error, Error}, State} - end; - -%% These two clauses below could happen if a server upgrades a socket in -%% active mode. Note that in this case we are lucky that -%% controlling_process has been evalueated before receiving handshake -%% messages from client. The server should put the socket in passive -%% mode before telling the client that it is willing to upgrade -%% and before calling ssl:ssl_accept/2. These clauses are -%% here to make sure it is the users problem and not owers if -%% they upgrade an active socket. -handle_sync_event({start,_}, _, connection, State) -> - {reply, connected, connection, State, get_timeout(State)}; - -handle_sync_event({start, Timeout}, StartFrom, StateName, #state{role = Role, ssl_options = SslOpts} = State0) -> - try - State = ssl_config(SslOpts, Role, State0), - Timer = start_or_recv_cancel_timer(Timeout, StartFrom), - {next_state, StateName, State#state{start_or_recv_from = StartFrom, - timer = Timer}, get_timeout(State)} - catch throw:Error -> - {stop, normal, {error, Error}, State0} - end; - -handle_sync_event({close, _} = Close, _, StateName, #state{protocol_cb = Connection} = State) -> + {keep_state_and_data, [postpone]}; +handle_call({close, {Pid, Timeout}}, From, StateName, State0, Connection) when is_pid(Pid) -> + %% terminate will send close alert to peer + State = State0#state{downgrade = {Pid, From}}, + Connection:terminate(downgrade, StateName, State), + %% User downgrades connection + %% When downgrading an TLS connection to a transport connection + %% we must recive the close alert from the peer before releasing the + %% transport socket. + {next_state, downgrade, State, [{timeout, Timeout, downgrade}]}; +handle_call({close, _} = Close, From, StateName, State, Connection) -> %% Run terminate before returning so that the reuseaddr - %% inet-option and possible downgrade will work as intended. + %% inet-option Result = Connection:terminate(Close, StateName, State), - {stop, normal, Result, State#state{terminated = true}}; - -handle_sync_event({shutdown, How0}, _, StateName, - #state{transport_cb = Transport, - negotiated_version = Version, - connection_states = ConnectionStates, - socket = Socket} = State) -> + {stop_and_reply, {shutdown, normal}, + {reply, From, Result}, State}; +handle_call({shutdown, How0}, From, _, + #state{transport_cb = Transport, + negotiated_version = Version, + connection_states = ConnectionStates, + socket = Socket}, _) -> case How0 of How when How == write; How == both -> Alert = ?ALERT_REC(?WARNING, ?CLOSE_NOTIFY), @@ -733,89 +863,50 @@ handle_sync_event({shutdown, How0}, _, StateName, _ -> ok end, - + case Transport:shutdown(Socket, How0) of ok -> - {reply, ok, StateName, State, get_timeout(State)}; + {keep_state_and_data, [{reply, From, ok}]}; Error -> - {stop, normal, Error, State} + gen_statem:reply(From, {error, Error}), + {stop, normal} end; -handle_sync_event({recv, _N, _Timeout}, _RecvFrom, StateName, - #state{socket_options = #socket_options{active = Active}} = State) when Active =/= false -> - {reply, {error, einval}, StateName, State, get_timeout(State)}; -handle_sync_event({recv, N, Timeout}, RecvFrom, connection = StateName, - #state{protocol_cb = Connection} = State0) -> - Timer = start_or_recv_cancel_timer(Timeout, RecvFrom), - Connection:passive_receive(State0#state{bytes_to_read = N, - start_or_recv_from = RecvFrom, timer = Timer}, StateName); -%% Doing renegotiate wait with handling request until renegotiate is -%% finished. Will be handled by next_state_is_connection/2. -handle_sync_event({recv, N, Timeout}, RecvFrom, StateName, State) -> +handle_call({recv, _N, _Timeout}, From, _, + #state{socket_options = + #socket_options{active = Active}}, _) when Active =/= false -> + {keep_state_and_data, [{reply, From, {error, einval}}]}; +handle_call({recv, N, Timeout}, RecvFrom, StateName, State, _) -> + %% Doing renegotiate wait with handling request until renegotiate is + %% finished. Timer = start_or_recv_cancel_timer(Timeout, RecvFrom), {next_state, StateName, State#state{bytes_to_read = N, start_or_recv_from = RecvFrom, - timer = Timer}, - get_timeout(State)}; -handle_sync_event({new_user, User}, _From, StateName, - State =#state{user_application = {OldMon, _}}) -> + timer = Timer}, + [{next_event, internal, {recv, RecvFrom}}]}; +handle_call({new_user, User}, From, StateName, + State =#state{user_application = {OldMon, _}}, _) -> NewMon = erlang:monitor(process, User), erlang:demonitor(OldMon, [flush]), - {reply, ok, StateName, State#state{user_application = {NewMon,User}}, - get_timeout(State)}; -handle_sync_event({get_opts, OptTags}, _From, StateName, + {next_state, StateName, State#state{user_application = {NewMon,User}}, + [{reply, From, ok}]}; +handle_call({get_opts, OptTags}, From, _, #state{socket = Socket, transport_cb = Transport, - socket_options = SockOpts} = State) -> + socket_options = SockOpts}, _) -> OptsReply = get_socket_opts(Transport, Socket, OptTags, SockOpts, []), - {reply, OptsReply, StateName, State, get_timeout(State)}; -handle_sync_event(negotiated_protocol, _From, StateName, #state{negotiated_protocol = undefined} = State) -> - {reply, {error, protocol_not_negotiated}, StateName, State, get_timeout(State)}; -handle_sync_event(negotiated_protocol, _From, StateName, #state{negotiated_protocol = SelectedProtocol} = State) -> - {reply, {ok, SelectedProtocol}, StateName, State, get_timeout(State)}; -handle_sync_event({set_opts, Opts0}, _From, StateName0, - #state{socket_options = Opts1, - protocol_cb = Connection, + {keep_state_and_data, [{reply, From, OptsReply}]}; +handle_call({set_opts, Opts0}, From, StateName, + #state{socket_options = Opts1, socket = Socket, - transport_cb = Transport, - user_data_buffer = Buffer} = State0) -> + transport_cb = Transport} = State0, _) -> {Reply, Opts} = set_socket_opts(Transport, Socket, Opts0, Opts1, []), - State1 = State0#state{socket_options = Opts}, - if - Opts#socket_options.active =:= false -> - {reply, Reply, StateName0, State1, get_timeout(State1)}; - Buffer =:= <<>>, Opts1#socket_options.active =:= false -> - %% Need data, set active once - {Record, State2} = Connection:next_record_if_active(State1), - %% Note: Renogotiation may cause StateName0 =/= StateName - case Connection:next_state(StateName0, StateName0, Record, State2) of - {next_state, StateName, State, Timeout} -> - {reply, Reply, StateName, State, Timeout}; - {stop, Reason, State} -> - {stop, Reason, State} - end; - Buffer =:= <<>> -> - %% Active once already set - {reply, Reply, StateName0, State1, get_timeout(State1)}; - true -> - case Connection:read_application_data(<<>>, State1) of - Stop = {stop,_,_} -> - Stop; - {Record, State2} -> - %% Note: Renogotiation may cause StateName0 =/= StateName - case Connection:next_state(StateName0, StateName0, Record, State2) of - {next_state, StateName, State, Timeout} -> - {reply, Reply, StateName, State, Timeout}; - {stop, Reason, State} -> - {stop, Reason, State} - end - end - end; -handle_sync_event(renegotiate, From, connection, #state{protocol_cb = Connection} = State) -> - Connection:renegotiate(State#state{renegotiation = {true, From}}); -handle_sync_event(renegotiate, _, StateName, State) -> - {reply, {error, already_renegotiating}, StateName, State, get_timeout(State)}; -handle_sync_event({prf, Secret, Label, Seed, WantedLength}, _, StateName, - #state{connection_states = ConnectionStates, - negotiated_version = Version} = State) -> + State = State0#state{socket_options = Opts}, + handle_active_option(Opts#socket_options.active, StateName, From, Reply, State); + +handle_call(renegotiate, From, StateName, _, _) when StateName =/= connection -> + {keep_state_and_data, [{reply, From, {error, already_renegotiating}}]}; +handle_call({prf, Secret, Label, Seed, WantedLength}, From, _, + #state{connection_states = ConnectionStates, + negotiated_version = Version}, _) -> ConnectionState = ssl_record:current_connection_state(ConnectionStates, read), SecParams = ConnectionState#connection_state.security_parameters, @@ -838,27 +929,9 @@ handle_sync_event({prf, Secret, Label, Seed, WantedLength}, _, StateName, exit:_ -> {error, badarg}; error:Reason -> {error, Reason} end, - {reply, Reply, StateName, State, get_timeout(State)}; -handle_sync_event(session_info, _, StateName, - #state{session = #session{session_id = Id, - cipher_suite = Suite}} = State) -> - {reply, [{session_id, Id}, - {cipher_suite, ssl_cipher:erl_suite_definition(Suite)}], - StateName, State, get_timeout(State)}; -handle_sync_event(peer_certificate, _, StateName, - #state{session = #session{peer_certificate = Cert}} - = State) -> - {reply, {ok, Cert}, StateName, State, get_timeout(State)}; -handle_sync_event(connection_information, _, StateName, State) -> - Info = connection_info(State), - {reply, {ok, Info}, StateName, State, get_timeout(State)}. - -connection_info(#state{sni_hostname = SNIHostname, - session = #session{cipher_suite = CipherSuite}, - negotiated_version = Version, ssl_options = Opts}) -> - [{protocol, tls_record:protocol_version(Version)}, - {cipher_suite, ssl_cipher:erl_suite_definition(CipherSuite)}, - {sni_hostname, SNIHostname}] ++ ssl_options_list(Opts). + {keep_state_and_data, [{reply, From, Reply}]}; +handle_call(_,_,_,_,_) -> + {keep_state_and_data, [postpone]}. handle_info({ErrorTag, Socket, econnaborted}, StateName, #state{socket = Socket, transport_cb = Transport, @@ -866,7 +939,8 @@ handle_info({ErrorTag, Socket, econnaborted}, StateName, protocol_cb = Connection, error_tag = ErrorTag, tracker = Tracker} = State) when StateName =/= connection -> - Connection:alert_user(Transport, Tracker,Socket, StartFrom, ?ALERT_REC(?FATAL, ?CLOSE_NOTIFY), Role), + Connection:alert_user(Transport, Tracker,Socket, + StartFrom, ?ALERT_REC(?FATAL, ?CLOSE_NOTIFY), Role), {stop, normal, State}; handle_info({ErrorTag, Socket, Reason}, StateName, #state{socket = Socket, @@ -889,47 +963,46 @@ handle_info({'EXIT', Socket, normal}, _StateName, #state{socket = Socket} = Stat {stop, {shutdown, transport_closed}, State}; handle_info(allow_renegotiate, StateName, State) -> - {next_state, StateName, State#state{allow_renegotiate = true}, get_timeout(State)}; + {next_state, StateName, State#state{allow_renegotiate = true}}; handle_info({cancel_start_or_recv, StartFrom}, StateName, #state{renegotiation = {false, first}} = State) when StateName =/= connection -> - gen_fsm:reply(StartFrom, {error, timeout}), - {stop, {shutdown, user_timeout}, State#state{timer = undefined}}; + {stop_and_reply, {shutdown, user_timeout}, + {reply, StartFrom, {error, timeout}}, State#state{timer = undefined}}; -handle_info({cancel_start_or_recv, RecvFrom}, StateName, #state{start_or_recv_from = RecvFrom} = State) -> - gen_fsm:reply(RecvFrom, {error, timeout}), +handle_info({cancel_start_or_recv, RecvFrom}, StateName, + #state{start_or_recv_from = RecvFrom} = State) when RecvFrom =/= undefined -> {next_state, StateName, State#state{start_or_recv_from = undefined, bytes_to_read = undefined, - timer = undefined}, get_timeout(State)}; + timer = undefined}, [{reply, RecvFrom, {error, timeout}}]}; handle_info({cancel_start_or_recv, _RecvFrom}, StateName, State) -> - {next_state, StateName, State#state{timer = undefined}, get_timeout(State)}; + {next_state, StateName, State#state{timer = undefined}}; handle_info(Msg, StateName, #state{socket = Socket, error_tag = Tag} = State) -> Report = io_lib:format("SSL: Got unexpected info: ~p ~n", [{Msg, Tag, Socket}]), error_logger:info_report(Report), - {next_state, StateName, State, get_timeout(State)}. - + {next_state, StateName, State}. +%%-------------------------------------------------------------------- +%% gen_statem callbacks +%%-------------------------------------------------------------------- terminate(_, _, #state{terminated = true}) -> %% Happens when user closes the connection using ssl:close/1 %% we want to guarantee that Transport:close has been called %% when ssl:close/1 returns. ok; + terminate({shutdown, transport_closed} = Reason, - _StateName, #state{send_queue = SendQueue, protocol_cb = Connection, - socket = Socket, transport_cb = Transport, - renegotiation = Renegotiate} = State) -> + _StateName, #state{protocol_cb = Connection, + socket = Socket, transport_cb = Transport} = State) -> handle_trusted_certs_db(State), - notify_senders(SendQueue), - notify_renegotiater(Renegotiate), Connection:close(Reason, Socket, Transport, undefined, undefined); -terminate({shutdown, own_alert}, _StateName, #state{send_queue = SendQueue, protocol_cb = Connection, - socket = Socket, transport_cb = Transport, - renegotiation = Renegotiate} = State) -> +terminate({shutdown, own_alert}, _StateName, #state{%%send_queue = SendQueue, + protocol_cb = Connection, + socket = Socket, + transport_cb = Transport} = State) -> handle_trusted_certs_db(State), - notify_senders(SendQueue), - notify_renegotiater(Renegotiate), case application:get_env(ssl, alert_timeout) of {ok, Timeout} when is_integer(Timeout) -> Connection:close({timeout, Timeout}, Socket, Transport, undefined, undefined); @@ -940,26 +1013,22 @@ terminate(Reason, connection, #state{negotiated_version = Version, protocol_cb = Connection, connection_states = ConnectionStates0, ssl_options = #ssl_options{padding_check = Check}, - transport_cb = Transport, socket = Socket, - send_queue = SendQueue, renegotiation = Renegotiate} = State) -> + transport_cb = Transport, socket = Socket + } = State) -> handle_trusted_certs_db(State), - notify_senders(SendQueue), - notify_renegotiater(Renegotiate), {BinAlert, ConnectionStates} = terminate_alert(Reason, Version, ConnectionStates0), Transport:send(Socket, BinAlert), Connection:close(Reason, Socket, Transport, ConnectionStates, Check); terminate(Reason, _StateName, #state{transport_cb = Transport, protocol_cb = Connection, - socket = Socket, send_queue = SendQueue, - renegotiation = Renegotiate} = State) -> + socket = Socket + } = State) -> handle_trusted_certs_db(State), - notify_senders(SendQueue), - notify_renegotiater(Renegotiate), Connection:close(Reason, Socket, Transport, undefined, undefined). -format_status(normal, [_, State]) -> - [{data, [{"StateData", State}]}]; -format_status(terminate, [_, State]) -> +format_status(normal, [_, StateName, State]) -> + [{data, [{"State", {StateName, State}}]}]; +format_status(terminate, [_, StateName, State]) -> SslOptions = (State#state.ssl_options), NewOptions = SslOptions#ssl_options{password = ?SECRET_PRINTOUT, cert = ?SECRET_PRINTOUT, @@ -968,39 +1037,29 @@ format_status(terminate, [_, State]) -> dh = ?SECRET_PRINTOUT, psk_identity = ?SECRET_PRINTOUT, srp_identity = ?SECRET_PRINTOUT}, - [{data, [{"StateData", State#state{connection_states = ?SECRET_PRINTOUT, - protocol_buffers = ?SECRET_PRINTOUT, - user_data_buffer = ?SECRET_PRINTOUT, - tls_handshake_history = ?SECRET_PRINTOUT, - session = ?SECRET_PRINTOUT, - private_key = ?SECRET_PRINTOUT, - diffie_hellman_params = ?SECRET_PRINTOUT, - diffie_hellman_keys = ?SECRET_PRINTOUT, - srp_params = ?SECRET_PRINTOUT, - srp_keys = ?SECRET_PRINTOUT, - premaster_secret = ?SECRET_PRINTOUT, - ssl_options = NewOptions - }}]}]. + [{data, [{"State", {StateName, State#state{connection_states = ?SECRET_PRINTOUT, + protocol_buffers = ?SECRET_PRINTOUT, + user_data_buffer = ?SECRET_PRINTOUT, + tls_handshake_history = ?SECRET_PRINTOUT, + session = ?SECRET_PRINTOUT, + private_key = ?SECRET_PRINTOUT, + diffie_hellman_params = ?SECRET_PRINTOUT, + diffie_hellman_keys = ?SECRET_PRINTOUT, + srp_params = ?SECRET_PRINTOUT, + srp_keys = ?SECRET_PRINTOUT, + premaster_secret = ?SECRET_PRINTOUT, + ssl_options = NewOptions, + flight_buffer = ?SECRET_PRINTOUT} + }}]}]. %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- -ssl_config(Opts, Role, State) -> - {ok, Ref, CertDbHandle, FileRefHandle, CacheHandle, CRLDbInfo, OwnCert, Key, DHParams} = - ssl_config:init(Opts, Role), - Handshake = ssl_handshake:init_handshake_history(), - TimeStamp = erlang:monotonic_time(), - Session = State#state.session, - State#state{tls_handshake_history = Handshake, - session = Session#session{own_certificate = OwnCert, - time_stamp = TimeStamp}, - file_ref_db = FileRefHandle, - cert_db_ref = Ref, - cert_db = CertDbHandle, - crl_db = CRLDbInfo, - session_cache = CacheHandle, - private_key = Key, - diffie_hellman_params = DHParams, - ssl_options = Opts}. +connection_info(#state{sni_hostname = SNIHostname, + session = #session{cipher_suite = CipherSuite}, + negotiated_version = Version, ssl_options = Opts}) -> + [{protocol, tls_record:protocol_version(Version)}, + {cipher_suite, ssl_cipher:erl_suite_definition(CipherSuite)}, + {sni_hostname, SNIHostname}] ++ ssl_options_list(Opts). do_server_hello(Type, #hello_extensions{next_protocol_negotiation = NextProtocols} = ServerHelloExt, @@ -1034,7 +1093,7 @@ new_server_hello(#server_hello{cipher_suite = CipherSuite, cipher_suite = CipherSuite, compression_method = Compression}, {Record, State} = Connection:next_record(State2#state{session = Session}), - Connection:next_state(hello, certify, Record, State) + Connection:next_event(certify, Record, State) catch #alert{} = Alert -> Connection:handle_own_alert(Alert, Version, hello, State0) @@ -1052,7 +1111,7 @@ resumed_server_hello(#state{session = Session, State2 = finalize_handshake(State1, abbreviated, Connection), {Record, State} = Connection:next_record(State2), - Connection:next_state(hello, abbreviated, Record, State); + Connection:next_event(abbreviated, Record, State); #alert{} = Alert -> Connection:handle_own_alert(Alert, Version, hello, State0) end. @@ -1060,7 +1119,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) -> @@ -1077,7 +1136,7 @@ handle_peer_cert(Role, PeerCert, PublicKeyInfo, State2 = handle_peer_cert_key(Role, PeerCert, PublicKeyInfo, KeyAlg, State1), {Record, State} = Connection:next_record(State2), - Connection:next_state(certify, certify, Record, State). + Connection:next_event(certify, Record, State). handle_peer_cert_key(client, _, {?'id-ecPublicKey', #'ECPoint'{point = _ECPoint} = PublicKey, @@ -1106,7 +1165,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. @@ -1122,7 +1181,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 -> @@ -1140,7 +1199,7 @@ client_certify_and_key_exchange(#state{negotiated_version = Version} = %% Reinitialize client_certificate_requested = false}, {Record, State} = Connection:next_record(State3), - Connection:next_state(certify, cipher, Record, State) + Connection:next_event(cipher, Record, State) catch throw:#alert{} = Alert -> Connection:handle_own_alert(Alert, Version, certify, State0) @@ -1174,20 +1233,25 @@ certify_client_key_exchange(#client_ec_diffie_hellman_public{dh_public = ClientP calculate_master_secret(PremasterSecret, State, Connection, certify, cipher); certify_client_key_exchange(#client_psk_identity{} = ClientKey, - #state{ssl_options = #ssl_options{user_lookup_fun = PSKLookup}} = State0, Connection) -> + #state{ssl_options = + #ssl_options{user_lookup_fun = PSKLookup}} = State0, + Connection) -> PremasterSecret = ssl_handshake:premaster_secret(ClientKey, PSKLookup), calculate_master_secret(PremasterSecret, State0, Connection, certify, cipher); certify_client_key_exchange(#client_dhe_psk_identity{} = ClientKey, #state{diffie_hellman_params = #'DHParameter'{} = Params, diffie_hellman_keys = {_, ServerDhPrivateKey}, - ssl_options = #ssl_options{user_lookup_fun = PSKLookup}} = State0, + ssl_options = + #ssl_options{user_lookup_fun = PSKLookup}} = State0, Connection) -> - PremasterSecret = ssl_handshake:premaster_secret(ClientKey, ServerDhPrivateKey, Params, PSKLookup), + PremasterSecret = + ssl_handshake:premaster_secret(ClientKey, ServerDhPrivateKey, Params, PSKLookup), calculate_master_secret(PremasterSecret, State0, Connection, certify, cipher); certify_client_key_exchange(#client_rsa_psk_identity{} = ClientKey, #state{private_key = Key, - ssl_options = #ssl_options{user_lookup_fun = PSKLookup}} = State0, + ssl_options = + #ssl_options{user_lookup_fun = PSKLookup}} = State0, Connection) -> PremasterSecret = ssl_handshake:premaster_secret(ClientKey, Key, PSKLookup), calculate_master_secret(PremasterSecret, State0, Connection, certify, cipher); @@ -1199,8 +1263,11 @@ certify_client_key_exchange(#client_srp_public{} = ClientKey, PremasterSecret = ssl_handshake:premaster_secret(ClientKey, Key, Params), calculate_master_secret(PremasterSecret, State0, Connection, certify, cipher). -certify_server(#state{key_algorithm = Algo} = State, _) - when Algo == dh_anon; Algo == ecdh_anon; Algo == psk; Algo == dhe_psk; Algo == srp_anon -> +certify_server(#state{key_algorithm = Algo} = State, _) when Algo == dh_anon; + Algo == ecdh_anon; + Algo == psk; + Algo == dhe_psk; + Algo == srp_anon -> State; certify_server(#state{cert_db = CertDbHandle, @@ -1208,7 +1275,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. @@ -1235,7 +1302,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, _) @@ -1260,7 +1327,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, @@ -1282,7 +1349,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}, @@ -1298,11 +1365,12 @@ key_exchange(#state{role = server, key_algorithm = dhe_psk, SecParams = ConnectionState#connection_state.security_parameters, #security_parameters{client_random = ClientRandom, server_random = ServerRandom} = SecParams, - Msg = ssl_handshake:key_exchange(server, Version, {dhe_psk, PskIdentityHint, DHKeys, Params, - HashSignAlgo, ClientRandom, - ServerRandom, - PrivateKey}), - State = Connection:send_handshake(Msg, State0), + Msg = ssl_handshake:key_exchange(server, Version, {dhe_psk, + PskIdentityHint, DHKeys, Params, + HashSignAlgo, ClientRandom, + ServerRandom, + PrivateKey}), + State = Connection:queue_handshake(Msg, State0), State#state{diffie_hellman_keys = DHKeys}; key_exchange(#state{role = server, key_algorithm = rsa_psk, @@ -1324,7 +1392,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}, @@ -1353,7 +1421,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}; @@ -1363,7 +1431,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, @@ -1374,7 +1442,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, @@ -1384,14 +1452,15 @@ 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, key_algorithm = psk, negotiated_version = Version} = State0, Connection) -> - Msg = ssl_handshake:key_exchange(client, Version, {psk, SslOpts#ssl_options.psk_identity}), - Connection:send_handshake(Msg, State0); + Msg = ssl_handshake:key_exchange(client, Version, + {psk, SslOpts#ssl_options.psk_identity}), + Connection:queue_handshake(Msg, State0); key_exchange(#state{role = client, ssl_options = SslOpts, @@ -1399,8 +1468,9 @@ key_exchange(#state{role = client, negotiated_version = Version, diffie_hellman_keys = {DhPubKey, _}} = State0, Connection) -> Msg = ssl_handshake:key_exchange(client, Version, - {dhe_psk, SslOpts#ssl_options.psk_identity, DhPubKey}), - Connection:send_handshake(Msg, State0); + {dhe_psk, + SslOpts#ssl_options.psk_identity, DhPubKey}), + Connection:queue_handshake(Msg, State0); key_exchange(#state{role = client, ssl_options = SslOpts, key_algorithm = rsa_psk, @@ -1410,7 +1480,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, @@ -1421,7 +1491,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; @@ -1437,9 +1507,10 @@ rsa_key_exchange(Version, PremasterSecret, PublicKeyInfo = {Algorithm, _, _}) {premaster_secret, PremasterSecret, PublicKeyInfo}); rsa_key_exchange(_, _, _) -> - throw (?ALERT_REC(?FATAL,?HANDSHAKE_FAILURE)). + throw (?ALERT_REC(?FATAL,?HANDSHAKE_FAILURE, pub_key_is_not_rsa)). -rsa_psk_key_exchange(Version, PskIdentity, PremasterSecret, PublicKeyInfo = {Algorithm, _, _}) +rsa_psk_key_exchange(Version, PskIdentity, PremasterSecret, + PublicKeyInfo = {Algorithm, _, _}) when Algorithm == ?rsaEncryption; Algorithm == ?md2WithRSAEncryption; Algorithm == ?md5WithRSAEncryption; @@ -1453,7 +1524,7 @@ rsa_psk_key_exchange(Version, PskIdentity, PremasterSecret, PublicKeyInfo = {Alg {psk_premaster_secret, PskIdentity, PremasterSecret, PublicKeyInfo}); rsa_psk_key_exchange(_, _, _, _) -> - throw (?ALERT_REC(?FATAL,?HANDSHAKE_FAILURE)). + throw (?ALERT_REC(?FATAL,?HANDSHAKE_FAILURE, pub_key_is_not_rsa)). request_client_cert(#state{ssl_options = #ssl_options{verify = verify_peer, signature_algs = SupportedHashSigns}, @@ -1467,17 +1538,18 @@ 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}} = State, _) -> State. -calculate_master_secret(PremasterSecret, #state{negotiated_version = Version, - connection_states = ConnectionStates0, - session = Session0} = State0, Connection, - Current, Next) -> +calculate_master_secret(PremasterSecret, + #state{negotiated_version = Version, + connection_states = ConnectionStates0, + session = Session0} = State0, Connection, + _Current, Next) -> case ssl_handshake:master_secret(record_cb(Connection), Version, PremasterSecret, ConnectionStates0, server) of {MasterSecret, ConnectionStates} -> @@ -1485,7 +1557,7 @@ calculate_master_secret(PremasterSecret, #state{negotiated_version = Version, State1 = State0#state{connection_states = ConnectionStates, session = Session}, {Record, State} = Connection:next_record(State1), - Connection:next_state(Current, Next, Record, State); + Connection:next_event(Next, Record, State); #alert{} = Alert -> Connection:handle_own_alert(Alert, Version, certify, State0) end. @@ -1510,10 +1582,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, @@ -1536,31 +1608,36 @@ save_verify_data(client, #finished{verify_data = Data}, ConnectionStates, abbrev save_verify_data(server, #finished{verify_data = Data}, ConnectionStates, abbreviated) -> ssl_record:set_server_verify_data(current_write, Data, ConnectionStates). -calculate_secret(#server_dh_params{dh_p = Prime, dh_g = Base, dh_y = ServerPublicDhKey} = Params, - State, Connection) -> +calculate_secret(#server_dh_params{dh_p = Prime, dh_g = Base, + dh_y = ServerPublicDhKey} = Params, + State, Connection) -> Keys = {_, PrivateDhKey} = crypto:generate_key(dh, [Prime, Base]), PremasterSecret = ssl_handshake:premaster_secret(ServerPublicDhKey, PrivateDhKey, Params), calculate_master_secret(PremasterSecret, - State#state{diffie_hellman_keys = Keys}, Connection, certify, certify); + State#state{diffie_hellman_keys = Keys}, + Connection, certify, certify); calculate_secret(#server_ecdh_params{curve = ECCurve, public = ECServerPubKey}, State, Connection) -> ECDHKeys = public_key:generate_key(ECCurve), - PremasterSecret = ssl_handshake:premaster_secret(#'ECPoint'{point = ECServerPubKey}, ECDHKeys), + PremasterSecret = + ssl_handshake:premaster_secret(#'ECPoint'{point = ECServerPubKey}, ECDHKeys), calculate_master_secret(PremasterSecret, - State#state{diffie_hellman_keys = ECDHKeys}, Connection, certify, certify); + State#state{diffie_hellman_keys = ECDHKeys}, + Connection, certify, certify); calculate_secret(#server_psk_params{ - hint = IdentityHint}, + hint = IdentityHint}, State0, Connection) -> %% store for later use {Record, State} = Connection:next_record(State0#state{psk_identity = IdentityHint}), - Connection:next_state(certify, certify, Record, State); + Connection:next_event(certify, Record, State); calculate_secret(#server_dhe_psk_params{ dh_params = #server_dh_params{dh_p = Prime, dh_g = Base}} = ServerKey, - #state{ssl_options = #ssl_options{user_lookup_fun = PSKLookup}} = State, Connection) -> + #state{ssl_options = #ssl_options{user_lookup_fun = PSKLookup}} = + State, Connection) -> Keys = {_, PrivateDhKey} = crypto:generate_key(dh, [Prime, Base]), PremasterSecret = ssl_handshake:premaster_secret(ServerKey, PrivateDhKey, PSKLookup), @@ -1568,10 +1645,12 @@ calculate_secret(#server_dhe_psk_params{ Connection, certify, certify); calculate_secret(#server_srp_params{srp_n = Prime, srp_g = Generator} = ServerKey, - #state{ssl_options = #ssl_options{srp_identity = SRPId}} = State, Connection) -> + #state{ssl_options = #ssl_options{srp_identity = SRPId}} = State, + Connection) -> Keys = generate_srp_client_keys(Generator, Prime, 0), PremasterSecret = ssl_handshake:premaster_secret(ServerKey, Keys, SRPId), - calculate_master_secret(PremasterSecret, State#state{srp_keys = Keys}, Connection, certify, certify). + calculate_master_secret(PremasterSecret, State#state{srp_keys = Keys}, Connection, + certify, certify). master_secret(#alert{} = Alert, _) -> Alert; @@ -1627,21 +1706,23 @@ handle_srp_identity(Username, {Fun, UserState}) -> end. -cipher_role(client, Data, Session, #state{connection_states = ConnectionStates0} = State, +cipher_role(client, Data, Session, #state{connection_states = ConnectionStates0} = State0, Connection) -> - ConnectionStates = ssl_record:set_server_verify_data(current_both, Data, ConnectionStates0), - Connection:next_state_connection(cipher, - ack_connection( - State#state{session = Session, - connection_states = ConnectionStates})); - + ConnectionStates = ssl_record:set_server_verify_data(current_both, Data, + ConnectionStates0), + {Record, State} = prepare_connection(State0#state{session = Session, + connection_states = ConnectionStates}, + Connection), + Connection:next_event(connection, Record, State); cipher_role(server, Data, Session, #state{connection_states = ConnectionStates0} = State0, Connection) -> - ConnectionStates1 = ssl_record:set_client_verify_data(current_read, Data, ConnectionStates0), - State = + ConnectionStates1 = ssl_record:set_client_verify_data(current_read, Data, + ConnectionStates0), + State1 = finalize_handshake(State0#state{connection_states = ConnectionStates1, session = Session}, cipher, Connection), - Connection:next_state_connection(cipher, ack_connection(State#state{session = Session})). + {Record, State} = prepare_connection(State1, Connection), + Connection:next_event(connection, Record, State). select_curve(#state{client_ecc = {[Curve|_], _}}) -> {namedCurve, Curve}; @@ -1675,8 +1756,8 @@ record_cb(tls_connection) -> record_cb(dtls_connection) -> dtls_record. -sync_send_all_state_event(FsmPid, Event) -> - try gen_fsm:sync_send_all_state_event(FsmPid, Event, infinity) +call(FsmPid, Event) -> + try gen_statem:call(FsmPid, Event) catch exit:{noproc, _} -> {error, closed}; @@ -1732,38 +1813,42 @@ set_socket_opts(Transport, Socket, [], SockOpts, Other) -> {{error, {options, {socket_options, Other, Error}}}, SockOpts} end; -set_socket_opts(Transport,Socket, [{mode, Mode}| Opts], SockOpts, Other) when Mode == list; Mode == binary -> +set_socket_opts(Transport,Socket, [{mode, Mode}| Opts], SockOpts, Other) + when Mode == list; Mode == binary -> set_socket_opts(Transport, Socket, Opts, SockOpts#socket_options{mode = Mode}, Other); set_socket_opts(_, _, [{mode, _} = Opt| _], SockOpts, _) -> {{error, {options, {socket_options, Opt}}}, SockOpts}; -set_socket_opts(Transport,Socket, [{packet, Packet}| Opts], SockOpts, Other) when Packet == raw; - Packet == 0; - Packet == 1; - Packet == 2; - Packet == 4; - Packet == asn1; - Packet == cdr; - Packet == sunrm; - Packet == fcgi; - Packet == tpkt; - Packet == line; - Packet == http; - Packet == httph; - Packet == http_bin; - Packet == httph_bin -> +set_socket_opts(Transport,Socket, [{packet, Packet}| Opts], SockOpts, Other) + when Packet == raw; + Packet == 0; + Packet == 1; + Packet == 2; + Packet == 4; + Packet == asn1; + Packet == cdr; + Packet == sunrm; + Packet == fcgi; + Packet == tpkt; + Packet == line; + Packet == http; + Packet == httph; + Packet == http_bin; + Packet == httph_bin -> set_socket_opts(Transport, Socket, Opts, SockOpts#socket_options{packet = Packet}, Other); set_socket_opts(_, _, [{packet, _} = Opt| _], SockOpts, _) -> {{error, {options, {socket_options, Opt}}}, SockOpts}; -set_socket_opts(Transport, Socket, [{header, Header}| Opts], SockOpts, Other) when is_integer(Header) -> +set_socket_opts(Transport, Socket, [{header, Header}| Opts], SockOpts, Other) + when is_integer(Header) -> set_socket_opts(Transport, Socket, Opts, SockOpts#socket_options{header = Header}, Other); set_socket_opts(_, _, [{header, _} = Opt| _], SockOpts, _) -> {{error,{options, {socket_options, Opt}}}, SockOpts}; -set_socket_opts(Transport, Socket, [{active, Active}| Opts], SockOpts, Other) when Active == once; - Active == true; - Active == false -> +set_socket_opts(Transport, Socket, [{active, Active}| Opts], SockOpts, Other) + when Active == once; + Active == true; + Active == false -> set_socket_opts(Transport, Socket, Opts, SockOpts#socket_options{active = Active}, Other); set_socket_opts(_, _, [{active, _} = Opt| _], SockOpts, _) -> @@ -1776,11 +1861,13 @@ start_or_recv_cancel_timer(infinity, _RecvFrom) -> start_or_recv_cancel_timer(Timeout, RecvFrom) -> erlang:send_after(Timeout, self(), {cancel_start_or_recv, RecvFrom}). -get_timeout(#state{ssl_options=#ssl_options{hibernate_after = undefined}}) -> - infinity; -get_timeout(#state{ssl_options=#ssl_options{hibernate_after = HibernateAfter}}) -> - HibernateAfter. - +hibernate_after(connection = StateName, + #state{ssl_options=#ssl_options{hibernate_after = HibernateAfter}} = State, + Actions) -> + {next_state, StateName, State, [{timeout, HibernateAfter, hibernate} | Actions]}; +hibernate_after(StateName, State, Actions) -> + {next_state, StateName, State, Actions}. + terminate_alert(normal, Version, ConnectionStates) -> ssl_alert:encode(?ALERT_REC(?WARNING, ?CLOSE_NOTIFY), Version, ConnectionStates); @@ -1790,10 +1877,12 @@ terminate_alert({Reason, _}, Version, ConnectionStates) when Reason == close; Version, ConnectionStates); terminate_alert(_, Version, ConnectionStates) -> - ssl_alert:encode(?ALERT_REC(?FATAL, ?INTERNAL_ERROR), - Version, ConnectionStates). + {BinAlert, _} = ssl_alert:encode(?ALERT_REC(?FATAL, ?INTERNAL_ERROR), + Version, ConnectionStates), + BinAlert. -handle_trusted_certs_db(#state{ssl_options = #ssl_options{cacertfile = <<>>, cacerts = []}}) -> +handle_trusted_certs_db(#state{ssl_options = + #ssl_options{cacertfile = <<>>, cacerts = []}}) -> %% No trusted certs specified ok; handle_trusted_certs_db(#state{cert_db_ref = Ref, @@ -1803,7 +1892,8 @@ handle_trusted_certs_db(#state{cert_db_ref = Ref, %% with other connections and it is safe to delete them when the connection ends. ssl_pkix_db:remove_trusted_certs(Ref, CertDb); handle_trusted_certs_db(#state{file_ref_db = undefined}) -> - %% Something went wrong early (typically cacertfile does not exist) so there is nothing to handle + %% Something went wrong early (typically cacertfile does not + %% exist) so there is nothing to handle ok; handle_trusted_certs_db(#state{cert_db_ref = Ref, file_ref_db = RefDb, @@ -1815,29 +1905,31 @@ handle_trusted_certs_db(#state{cert_db_ref = Ref, ok end. -notify_senders(SendQueue) -> - lists:foreach(fun({From, _}) -> - gen_fsm:reply(From, {error, closed}) - end, queue:to_list(SendQueue)). - -notify_renegotiater({true, From}) when not is_atom(From) -> - gen_fsm:reply(From, {error, closed}); -notify_renegotiater(_) -> - ok. +prepare_connection(#state{renegotiation = Renegotiate, + start_or_recv_from = RecvFrom} = State0, Connection) + when Renegotiate =/= {false, first}, + RecvFrom =/= undefined -> + State1 = Connection:reinit_handshake_data(State0), + {Record, State} = Connection:next_record(State1), + {Record, ack_connection(State)}; +prepare_connection(State0, Connection) -> + State = Connection:reinit_handshake_data(State0), + {no_record, ack_connection(State)}. ack_connection(#state{renegotiation = {true, Initiater}} = State) when Initiater == internal; Initiater == peer -> State#state{renegotiation = undefined}; ack_connection(#state{renegotiation = {true, From}} = State) -> - gen_fsm:reply(From, ok), + gen_statem:reply(From, ok), State#state{renegotiation = undefined}; ack_connection(#state{renegotiation = {false, first}, start_or_recv_from = StartFrom, timer = Timer} = State) when StartFrom =/= undefined -> - gen_fsm:reply(StartFrom, connected), + gen_statem:reply(StartFrom, connected), cancel_timer(Timer), - State#state{renegotiation = undefined, start_or_recv_from = undefined, timer = undefined}; + State#state{renegotiation = undefined, + start_or_recv_from = undefined, timer = undefined}; ack_connection(State) -> State. @@ -1858,13 +1950,14 @@ register_session(server, _, Port, #session{is_resumable = new} = Session0) -> register_session(_, _, _, Session) -> Session. %% Already registered -handle_new_session(NewId, CipherSuite, Compression, #state{session = Session0, - protocol_cb = Connection} = State0) -> +handle_new_session(NewId, CipherSuite, Compression, + #state{session = Session0, + protocol_cb = Connection} = State0) -> Session = Session0#session{session_id = NewId, cipher_suite = CipherSuite, compression_method = Compression}, {Record, State} = Connection:next_record(State0#state{session = Session}), - Connection:next_state(hello, certify, Record, State). + Connection:next_event(certify, Record, State). handle_resumed_session(SessId, #state{connection_states = ConnectionStates0, negotiated_version = Version, @@ -1880,13 +1973,13 @@ handle_resumed_session(SessId, #state{connection_states = ConnectionStates0, Connection:next_record(State0#state{ connection_states = ConnectionStates, session = Session}), - Connection:next_state(hello, abbreviated, Record, State); + Connection:next_event(abbreviated, Record, State); #alert{} = Alert -> Connection:handle_own_alert(Alert, Version, hello, State0) end. make_premaster_secret({MajVer, MinVer}, rsa) -> - Rand = ssl:random_bytes(?NUM_OF_PREMASTERSECRET_BYTES-2), + Rand = ssl_cipher:random_bytes(?NUM_OF_PREMASTERSECRET_BYTES-2), <<?BYTE(MajVer), ?BYTE(MinVer), Rand/binary>>; make_premaster_secret(_, _) -> undefined. @@ -1927,4 +2020,39 @@ ssl_options_list([ciphers = Key | Keys], [Value | Values], Acc) -> ssl_options_list([Key | Keys], [Value | Values], Acc) -> ssl_options_list(Keys, Values, [{Key, Value} | Acc]). - +handle_active_option(false, connection = StateName, To, Reply, State) -> + hibernate_after(StateName, State, [{reply, To, Reply}]); + +handle_active_option(_, connection = StateName0, To, Reply, #state{protocol_cb = Connection, + user_data_buffer = <<>>} = State0) -> + %% Need data, set active once + {Record, State1} = Connection:next_record_if_active(State0), + %% Note: Renogotiation may cause StateName0 =/= StateName + case Connection:next_event(StateName0, Record, State1) of + {next_state, StateName, State} -> + hibernate_after(StateName, State, [{reply, To, Reply}]); + {next_state, StateName, State, Actions} -> + hibernate_after(StateName, State, [{reply, To, Reply} | Actions]); + {stop, Reason, State} -> + {stop, Reason, State} + end; +handle_active_option(_, StateName, To, Reply, #state{user_data_buffer = <<>>} = State) -> + %% Active once already set + {next_state, StateName, State, [{reply, To, Reply}]}; + +%% user_data_buffer =/= <<>> +handle_active_option(_, StateName0, To, Reply, #state{protocol_cb = Connection} = State0) -> + case Connection:read_application_data(<<>>, State0) of + {stop, Reason, State} -> + {stop, Reason, State}; + {Record, State1} -> + %% Note: Renogotiation may cause StateName0 =/= StateName + case Connection:next_event(StateName0, Record, State1) of + {next_state, StateName, State} -> + hibernate_after(StateName, State, [{reply, To, Reply}]); + {next_state, StateName, State, Actions} -> + hibernate_after(StateName, State, [{reply, To, Reply} | Actions]); + {stop, _, _} = Stop -> + Stop + end + end. diff --git a/lib/ssl/src/ssl_connection.hrl b/lib/ssl/src/ssl_connection.hrl index 9a58f2b8f7..4b54943ddf 100644 --- a/lib/ssl/src/ssl_connection.hrl +++ b/lib/ssl/src/ssl_connection.hrl @@ -48,41 +48,48 @@ socket_options :: #socket_options{}, connection_states :: #connection_states{} | secret_printout(), protocol_buffers :: term() | secret_printout() , %% #protocol_buffers{} from tls_record.hrl or dtls_recor.hrl - tls_handshake_history :: ssl_handshake:ssl_handshake_history() | secret_printout(), - cert_db :: reference(), + tls_handshake_history :: ssl_handshake:ssl_handshake_history() | secret_printout() + | 'undefined', + cert_db :: reference() | 'undefined', session :: #session{} | secret_printout(), session_cache :: db_handle(), session_cache_cb :: atom(), crl_db :: term(), - negotiated_version :: ssl_record:ssl_version(), + negotiated_version :: ssl_record:ssl_version() | 'undefined', client_certificate_requested = false :: boolean(), key_algorithm :: ssl_cipher:key_algo(), hashsign_algorithm = {undefined, undefined}, cert_hashsign_algorithm, - public_key_info :: ssl_handshake:public_key_info(), - private_key :: public_key:private_key() | secret_printout(), + public_key_info :: ssl_handshake:public_key_info() | 'undefined', + private_key :: public_key:private_key() | secret_printout() | 'undefined', diffie_hellman_params:: #'DHParameter'{} | undefined | secret_printout(), diffie_hellman_keys :: {PublicKey :: binary(), PrivateKey :: binary()} | #'ECPrivateKey'{} | undefined | secret_printout(), - psk_identity :: binary(), % server psk identity hint - srp_params :: #srp_user{} | secret_printout(), - srp_keys ::{PublicKey :: binary(), PrivateKey :: binary()} | secret_printout(), - premaster_secret :: binary() | secret_printout() , + psk_identity :: binary() | 'undefined', % server psk identity hint + srp_params :: #srp_user{} | secret_printout() | 'undefined', + srp_keys ::{PublicKey :: binary(), PrivateKey :: binary()} | secret_printout() | 'undefined', + premaster_secret :: binary() | secret_printout() | 'undefined', file_ref_db :: db_handle(), - cert_db_ref :: certdb_ref(), + cert_db_ref :: certdb_ref() | 'undefined', bytes_to_read :: undefined | integer(), %% bytes to read in passive mode user_data_buffer :: undefined | binary() | secret_printout(), renegotiation :: undefined | {boolean(), From::term() | internal | peer}, start_or_recv_from :: term(), timer :: undefined | reference(), % start_or_recive_timer - send_queue :: queue:queue(), + %%send_queue :: queue:queue(), terminated = false ::boolean(), allow_renegotiate = true ::boolean(), expecting_next_protocol_negotiation = false ::boolean(), expecting_finished = false ::boolean(), negotiated_protocol = undefined :: undefined | binary(), client_ecc, % {Curves, PointFmt} - tracker :: pid(), %% Tracker process for listen socket - sni_hostname = undefined + tracker :: pid() | 'undefined', %% Tracker process for listen socket + sni_hostname = undefined, + 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_crl.erl b/lib/ssl/src/ssl_crl.erl index faf5007b16..d9f21e04ac 100644 --- a/lib/ssl/src/ssl_crl.erl +++ b/lib/ssl/src/ssl_crl.erl @@ -39,13 +39,12 @@ trusted_cert_and_path(CRL, {SerialNumber, Issuer},{Db, DbRef} = DbHandle) -> end; trusted_cert_and_path(CRL, issuer_not_found, {Db, DbRef} = DbHandle) -> - try find_issuer(CRL, DbHandle) of - OtpCert -> + case find_issuer(CRL, DbHandle) of + {ok, OtpCert} -> {ok, Root, Chain} = ssl_certificate:certificate_chain(OtpCert, Db, DbRef), - {ok, Root, lists:reverse(Chain)} - catch - throw:_ -> - {error, issuer_not_found} + {ok, Root, lists:reverse(Chain)}; + {error, issuer_not_found} -> + {ok, unknown_crl_ca, []} end. find_issuer(CRL, {Db,_}) -> @@ -61,11 +60,10 @@ find_issuer(CRL, {Db,_}) -> issuer_not_found -> {error, issuer_not_found} catch - {ok, IssuerCert} -> - IssuerCert + {ok, _} = Result -> + Result end. - verify_crl_issuer(CRL, ErlCertCandidate, Issuer, NotIssuer) -> TBSCert = ErlCertCandidate#'OTPCertificate'.tbsCertificate, case public_key:pkix_normalize_name(TBSCert#'OTPTBSCertificate'.subject) of diff --git a/lib/ssl/src/ssl_crl_cache.erl b/lib/ssl/src/ssl_crl_cache.erl index 60e7427737..647e0465fe 100644 --- a/lib/ssl/src/ssl_crl_cache.erl +++ b/lib/ssl/src/ssl_crl_cache.erl @@ -28,7 +28,7 @@ -behaviour(ssl_crl_cache_api). --export([lookup/2, select/2, fresh_crl/2]). +-export([lookup/3, select/2, fresh_crl/2]). -export([insert/1, insert/2, delete/1]). %%==================================================================== @@ -36,9 +36,10 @@ %%==================================================================== lookup(#'DistributionPoint'{distributionPoint = {fullName, Names}}, + _Issuer, CRLDbInfo) -> get_crls(Names, CRLDbInfo); -lookup(_,_) -> +lookup(_,_,_) -> not_available. select(Issuer, {{_Cache, Mapping},_}) -> diff --git a/lib/ssl/src/ssl_crl_cache_api.erl b/lib/ssl/src/ssl_crl_cache_api.erl index d7b31f280e..c3a57e2f49 100644 --- a/lib/ssl/src/ssl_crl_cache_api.erl +++ b/lib/ssl/src/ssl_crl_cache_api.erl @@ -24,8 +24,9 @@ -include_lib("public_key/include/public_key.hrl"). --type db_handle() :: term(). +-type db_handle() :: term(). +-type issuer_name() :: {rdnSequence, [#'AttributeTypeAndValue'{}]}. --callback lookup(#'DistributionPoint'{}, db_handle()) -> not_available | [public_key:der_encoded()]. --callback select(term(), db_handle()) -> [public_key:der_encoded()]. +-callback lookup(#'DistributionPoint'{}, issuer_name(), db_handle()) -> not_available | [public_key:der_encoded()]. +-callback select(issuer_name(), db_handle()) -> [public_key:der_encoded()]. -callback fresh_crl(#'DistributionPoint'{}, public_key:der_encoded()) -> public_key:der_encoded(). diff --git a/lib/ssl/src/ssl_crl_hash_dir.erl b/lib/ssl/src/ssl_crl_hash_dir.erl new file mode 100644 index 0000000000..bb62737232 --- /dev/null +++ b/lib/ssl/src/ssl_crl_hash_dir.erl @@ -0,0 +1,106 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2016-2016. 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. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% + +-module(ssl_crl_hash_dir). + +-include_lib("public_key/include/public_key.hrl"). + +-behaviour(ssl_crl_cache_api). + +-export([lookup/3, select/2, fresh_crl/2]). + +lookup(#'DistributionPoint'{cRLIssuer = CRLIssuer} = DP, CertIssuer, CRLDbInfo) -> + Issuer = + case CRLIssuer of + asn1_NOVALUE -> + %% If the distribution point extension doesn't + %% indicate a CRL issuer, use the certificate issuer. + CertIssuer; + _ -> + CRLIssuer + end, + %% Find all CRLs for this issuer, and return those that match the + %% given distribution point. + AllCRLs = select(Issuer, CRLDbInfo), + lists:filter(fun(DER) -> + public_key:pkix_match_dist_point(DER, DP) + end, AllCRLs). + +fresh_crl(#'DistributionPoint'{}, CurrentCRL) -> + CurrentCRL. + +select(Issuer, {_DbHandle, [{dir, Dir}]}) -> + case find_crls(Issuer, Dir) of + [_|_] = DERs -> + DERs; + [] -> + %% That's okay, just report that we didn't find any CRL. + %% If the crl_check setting is best_effort, ssl_handshake + %% is happy with that, but if it's true, this is an error. + []; + {error, Error} -> + error_logger:error_report( + [{cannot_find_crl, Error}, + {dir, Dir}, + {module, ?MODULE}, + {line, ?LINE}]), + [] + end. + +find_crls(Issuer, Dir) -> + case filelib:is_dir(Dir) of + true -> + Hash = public_key:short_name_hash(Issuer), + find_crls(Issuer, Hash, Dir, 0, []); + false -> + {error, not_a_directory} + end. + +find_crls(Issuer, Hash, Dir, N, Acc) -> + Filename = filename:join(Dir, Hash ++ ".r" ++ integer_to_list(N)), + case file:read_file(Filename) of + {error, enoent} -> + Acc; + {ok, Bin} -> + try maybe_parse_pem(Bin) of + DER when is_binary(DER) -> + %% Found one file. Let's see if there are more. + find_crls(Issuer, Hash, Dir, N + 1, [DER] ++ Acc) + catch + error:Error -> + %% Something is wrong with the file. Report + %% it, and try the next one. + error_logger:error_report( + [{crl_parse_error, Error}, + {filename, Filename}, + {module, ?MODULE}, + {line, ?LINE}]), + find_crls(Issuer, Hash, Dir, N + 1, Acc) + end + end. + +maybe_parse_pem(<<"-----BEGIN", _/binary>> = PEM) -> + %% It's a PEM encoded file. Need to extract the DER + %% encoded data. + [{'CertificateList', DER, not_encrypted}] = public_key:pem_decode(PEM), + DER; +maybe_parse_pem(DER) when is_binary(DER) -> + %% Let's assume it's DER-encoded. + DER. + diff --git a/lib/ssl/src/ssl_dist_sup.erl b/lib/ssl/src/ssl_dist_sup.erl index 435ad27a44..a6eb1be1f6 100644 --- a/lib/ssl/src/ssl_dist_sup.erl +++ b/lib/ssl/src/ssl_dist_sup.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2011-2014. All Rights Reserved. +%% Copyright Ericsson AB 2011-2016. 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. diff --git a/lib/ssl/src/ssl_handshake.erl b/lib/ssl/src/ssl_handshake.erl index 43b0c42f8d..081efda768 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, @@ -74,7 +74,7 @@ ]). %% MISC --export([select_version/3, prf/6, select_hashsign/5, +-export([select_version/3, prf/6, select_hashsign/4, select_hashsign/5, select_hashsign_algs/3, premaster_secret/2, premaster_secret/3, premaster_secret/4]). @@ -167,7 +167,7 @@ certificate(OwnCert, CertDbHandle, CertDbRef, server) -> {ok, _, Chain} -> #certificate{asn1_certificates = Chain}; {error, _} -> - ?ALERT_REC(?FATAL, ?INTERNAL_ERROR) + ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, server_has_no_suitable_certificates) end. %%-------------------------------------------------------------------- @@ -195,7 +195,7 @@ client_certificate_verify(OwnCert, MasterSecret, Version, PrivateKey, {Handshake, _}) -> case public_key:pkix_is_fixed_dh_cert(OwnCert) of true -> - ?ALERT_REC(?FATAL, ?UNSUPPORTED_CERTIFICATE); + ?ALERT_REC(?FATAL, ?UNSUPPORTED_CERTIFICATE, fixed_diffie_hellman_prohibited); false -> Hashes = calc_certificate_verify(Version, HashAlgo, MasterSecret, Handshake), @@ -353,7 +353,7 @@ verify_server_key(#server_key_params{params_bin = EncParams, %% Description: Checks that the certificate_verify message is valid. %%-------------------------------------------------------------------- certificate_verify(_, _, _, undefined, _, _) -> - ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE); + ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, invalid_certificate_verify_message); certificate_verify(Signature, PublicKeyInfo, Version, HashSign = {HashAlgo, _}, MasterSecret, {_, Handshake}) -> @@ -417,7 +417,7 @@ certify(#certificate{asn1_certificates = ASN1Certs}, CertDbHandle, CertDbRef, catch error:_ -> %% ASN-1 decode of certificate somehow failed - ?ALERT_REC(?FATAL, ?CERTIFICATE_UNKNOWN) + ?ALERT_REC(?FATAL, ?CERTIFICATE_UNKNOWN, failed_to_decode_certificate) end. %%-------------------------------------------------------------------- @@ -581,7 +581,7 @@ prf({3,_N}, PRFAlgo, Secret, Label, Seed, WantedLength) -> {atom(), atom()} | undefined | #alert{}. %% -%% Description: Handles signature_algorithms extension +%% Description: Handles signature_algorithms hello extension (server) %%-------------------------------------------------------------------- select_hashsign(_, undefined, _, _, _Version) -> {null, anon}; @@ -593,19 +593,22 @@ select_hashsign(HashSigns, Cert, KeyExAlgo, select_hashsign(#hash_sign_algos{hash_sign_algos = HashSigns}, Cert, KeyExAlgo, SupportedHashSigns, {Major, Minor}) when Major >= 3 andalso Minor >= 3 -> #'OTPCertificate'{tbsCertificate = TBSCert} = public_key:pkix_decode_cert(Cert, otp), - #'OTPSubjectPublicKeyInfo'{algorithm = {_,Algo, _}} = TBSCert#'OTPTBSCertificate'.subjectPublicKeyInfo, - Sign = cert_sign(Algo), - case lists:filter(fun({sha, dsa = S}) when S == Sign -> - true; - ({_, dsa}) -> - false; - ({_, _} = Algos) -> - is_acceptable_hash_sign(Algos, Sign, KeyExAlgo, SupportedHashSigns); + #'OTPCertificate'{tbsCertificate = TBSCert, + signatureAlgorithm = {_,SignAlgo, _}} = public_key:pkix_decode_cert(Cert, otp), + #'OTPSubjectPublicKeyInfo'{algorithm = {_, SubjAlgo, _}} = + TBSCert#'OTPTBSCertificate'.subjectPublicKeyInfo, + + Sign = sign_algo(SignAlgo), + SubSing = sign_algo(SubjAlgo), + + case lists:filter(fun({_, S} = Algos) when S == Sign -> + is_acceptable_hash_sign(Algos, Sign, + SubSing, KeyExAlgo, SupportedHashSigns); (_) -> false end, HashSigns) of [] -> - ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY); + ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_signature_algorithm); [HashSign | _] -> HashSign end; @@ -613,6 +616,49 @@ select_hashsign(_, Cert, _, _, Version) -> #'OTPCertificate'{tbsCertificate = TBSCert} = public_key:pkix_decode_cert(Cert, otp), #'OTPSubjectPublicKeyInfo'{algorithm = {_,Algo, _}} = TBSCert#'OTPTBSCertificate'.subjectPublicKeyInfo, select_hashsign_algs(undefined, Algo, Version). +%%-------------------------------------------------------------------- +-spec select_hashsign(#certificate_request{}, binary(), + [atom()], ssl_record:ssl_version()) -> + {atom(), atom()} | #alert{}. + +%% +%% Description: Handles signature algorithms selection for certificate requests (client) +%%-------------------------------------------------------------------- +select_hashsign(#certificate_request{}, undefined, _, {Major, Minor}) when Major >= 3 andalso Minor >= 3-> + %% There client does not have a certificate and will send an empty reply, the server may fail + %% or accept the connection by its own preference. No signature algorihms needed as there is + %% no certificate to verify. + {undefined, undefined}; + +select_hashsign(#certificate_request{hashsign_algorithms = #hash_sign_algos{hash_sign_algos = HashSigns}, + certificate_types = Types}, Cert, SupportedHashSigns, + {Major, Minor}) when Major >= 3 andalso Minor >= 3-> + #'OTPCertificate'{tbsCertificate = TBSCert} = public_key:pkix_decode_cert(Cert, otp), + #'OTPCertificate'{tbsCertificate = TBSCert, + signatureAlgorithm = {_,SignAlgo, _}} = public_key:pkix_decode_cert(Cert, otp), + #'OTPSubjectPublicKeyInfo'{algorithm = {_, SubjAlgo, _}} = + TBSCert#'OTPTBSCertificate'.subjectPublicKeyInfo, + + Sign = sign_algo(SignAlgo), + SubSign = sign_algo(SubjAlgo), + + case is_acceptable_cert_type(SubSign, HashSigns, Types) andalso is_supported_sign(Sign, HashSigns) of + true -> + case lists:filter(fun({_, S} = Algos) when S == SubSign -> + is_acceptable_hash_sign(Algos, SupportedHashSigns); + (_) -> + false + end, HashSigns) of + [] -> + ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_signature_algorithm); + [HashSign | _] -> + HashSign + end; + false -> + ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_signature_algorithm) + end; +select_hashsign(#certificate_request{}, Cert, _, Version) -> + select_hashsign(undefined, Cert, undefined, [], Version). %%-------------------------------------------------------------------- -spec select_hashsign_algs({atom(), atom()}| undefined, oid(), ssl_record:ssl_version()) -> @@ -648,6 +694,7 @@ select_hashsign_algs(undefined, ?rsaEncryption, _) -> select_hashsign_algs(undefined, ?'id-dsa', _) -> {sha, dsa}. + %%-------------------------------------------------------------------- -spec master_secret(atom(), ssl_record:ssl_version(), #session{} | binary(), #connection_states{}, client | server) -> {binary(), #connection_states{}} | #alert{}. @@ -664,11 +711,8 @@ master_secret(RecordCB, Version, #session{master_secret = Mastersecret}, try master_secret(RecordCB, Version, Mastersecret, SecParams, ConnectionStates, Role) catch - exit:Reason -> - Report = io_lib:format("Key calculation failed due to ~p", - [Reason]), - error_logger:error_report(Report), - ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE) + exit:_ -> + ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, key_calculation_failure) end; master_secret(RecordCB, Version, PremasterSecret, ConnectionStates, Role) -> @@ -683,11 +727,8 @@ master_secret(RecordCB, Version, PremasterSecret, ConnectionStates, Role) -> ClientRandom, ServerRandom), SecParams, ConnectionStates, Role) catch - exit:Reason -> - Report = io_lib:format("Master secret calculation failed" - " due to ~p", [Reason]), - error_logger:error_report(Report), - ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE) + exit:_ -> + ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, master_secret_calculation_failure) end. %%-------------Encode/Decode -------------------------------- @@ -958,8 +999,8 @@ decode_handshake(_Version, ?CLIENT_KEY_EXCHANGE, PKEPMS) -> #client_key_exchange{exchange_keys = PKEPMS}; decode_handshake(_Version, ?FINISHED, VerifyData) -> #finished{verify_data = VerifyData}; -decode_handshake(_, _, _) -> - throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE)). +decode_handshake(_, Message, _) -> + throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, {unknown_or_malformed_handshake, Message})). %%-------------------------------------------------------------------- -spec decode_hello_extensions({client, binary()} | binary()) -> #hello_extensions{}. @@ -1031,8 +1072,8 @@ dec_server_key(<<?UINT16(NLen), N:NLen/binary, params_bin = BinMsg, hashsign = HashSign, signature = Signature}; -dec_server_key(_, _, _) -> - throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE)). +dec_server_key(_, KeyExchange, _) -> + throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, {unknown_or_malformed_key_exchange, KeyExchange})). %%-------------------------------------------------------------------- -spec decode_suites('2_bytes'|'3_bytes', binary()) -> list(). @@ -1149,11 +1190,13 @@ certificate_types(_, {N, M}) when N >= 3 andalso M >= 3 -> end; certificate_types({KeyExchange, _, _, _}, _) when KeyExchange == rsa; + KeyExchange == dh_rsa; KeyExchange == dhe_rsa; KeyExchange == ecdhe_rsa -> <<?BYTE(?RSA_SIGN)>>; -certificate_types({KeyExchange, _, _, _}, _) when KeyExchange == dhe_dss; +certificate_types({KeyExchange, _, _, _}, _) when KeyExchange == dh_dss; + KeyExchange == dhe_dss; KeyExchange == srp_dss -> <<?BYTE(?DSS_SIGN)>>; @@ -1253,8 +1296,12 @@ handle_server_hello_extensions(RecordCB, Random, CipherSuite, Compression, Protocol -> {ConnectionStates, npn, Protocol} end; - _ -> %% {error, _Reason} or a list of 0/2+ protocols. - ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE) + {error, Reason} -> + ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, Reason); + [] -> + ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, no_protocols_in_server_hello); + [_|_] -> + ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, too_many_protocols_in_server_hello) end. select_version(RecordCB, ClientVersion, Versions) -> @@ -1348,14 +1395,14 @@ handle_renegotiation_info(_RecordCB, client, #renegotiation_info{renegotiated_co true -> {ok, ConnectionStates}; false -> - ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE) + ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, client_renegotiation) end; handle_renegotiation_info(_RecordCB, server, #renegotiation_info{renegotiated_connection = ClientVerify}, ConnectionStates, true, _, CipherSuites) -> case is_member(?TLS_EMPTY_RENEGOTIATION_INFO_SCSV, CipherSuites) of true -> - ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE); + ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, {server_renegotiation, empty_renegotiation_info_scsv}); false -> CS = ssl_record:current_connection_state(ConnectionStates, read), Data = CS#connection_state.client_verify_data, @@ -1363,7 +1410,7 @@ handle_renegotiation_info(_RecordCB, server, #renegotiation_info{renegotiated_co true -> {ok, ConnectionStates}; false -> - ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE) + ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, server_renegotiation) end end; @@ -1373,7 +1420,7 @@ handle_renegotiation_info(RecordCB, client, undefined, ConnectionStates, true, S handle_renegotiation_info(RecordCB, server, undefined, ConnectionStates, true, SecureRenegotation, CipherSuites) -> case is_member(?TLS_EMPTY_RENEGOTIATION_INFO_SCSV, CipherSuites) of true -> - ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE); + ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, {server_renegotiation, empty_renegotiation_info_scsv}); false -> handle_renegotiation_info(RecordCB, ConnectionStates, SecureRenegotation) end. @@ -1382,7 +1429,7 @@ handle_renegotiation_info(_RecordCB, ConnectionStates, SecureRenegotation) -> CS = ssl_record:current_connection_state(ConnectionStates, read), case {SecureRenegotation, CS#connection_state.secure_renegotiation} of {_, true} -> - ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE); + ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, already_secure); {true, false} -> ?ALERT_REC(?FATAL, ?NO_RENEGOTIATION); {false, false} -> @@ -1555,8 +1602,8 @@ path_validation_alert({bad_cert, selfsigned_peer}) -> ?ALERT_REC(?FATAL, ?BAD_CERTIFICATE); path_validation_alert({bad_cert, unknown_ca}) -> ?ALERT_REC(?FATAL, ?UNKNOWN_CA); -path_validation_alert(_) -> - ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE). +path_validation_alert(Reason) -> + ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, Reason). encrypted_premaster_secret(Secret, RSAPublicKey) -> try @@ -1565,18 +1612,27 @@ encrypted_premaster_secret(Secret, RSAPublicKey) -> rsa_pkcs1_padding}]), #encrypted_premaster_secret{premaster_secret = PreMasterSecret} catch - _:_-> - throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE)) + _:_-> + throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, premaster_encryption_failed)) + end. + +digitally_signed(Version, Hashes, HashAlgo, PrivateKey) -> + try do_digitally_signed(Version, Hashes, HashAlgo, PrivateKey) of + Signature -> + Signature + catch + error:badkey-> + throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, bad_key(PrivateKey))) end. -digitally_signed({3, Minor}, Hash, HashAlgo, Key) when Minor >= 3 -> +do_digitally_signed({3, Minor}, Hash, HashAlgo, Key) when Minor >= 3 -> public_key:sign({digest, Hash}, HashAlgo, Key); -digitally_signed(_Version, Hash, HashAlgo, #'DSAPrivateKey'{} = Key) -> +do_digitally_signed(_Version, Hash, HashAlgo, #'DSAPrivateKey'{} = Key) -> public_key:sign({digest, Hash}, HashAlgo, Key); -digitally_signed(_Version, Hash, _HashAlgo, #'RSAPrivateKey'{} = Key) -> +do_digitally_signed(_Version, Hash, _HashAlgo, #'RSAPrivateKey'{} = Key) -> public_key:encrypt_private(Hash, Key, [{rsa_pad, rsa_pkcs1_padding}]); -digitally_signed(_Version, Hash, HashAlgo, Key) -> +do_digitally_signed(_Version, Hash, HashAlgo, Key) -> public_key:sign({digest, Hash}, HashAlgo, Key). calc_certificate_verify({3, 0}, HashAlgo, MasterSecret, Handshake) -> @@ -1783,12 +1839,12 @@ dec_client_key(PKEPMS, ?KEY_EXCHANGE_RSA, {3, 0}) -> dec_client_key(<<?UINT16(_), PKEPMS/binary>>, ?KEY_EXCHANGE_RSA, _) -> #encrypted_premaster_secret{premaster_secret = PKEPMS}; dec_client_key(<<>>, ?KEY_EXCHANGE_DIFFIE_HELLMAN, _) -> - throw(?ALERT_REC(?FATAL, ?UNSUPPORTED_CERTIFICATE)); + throw(?ALERT_REC(?FATAL, ?UNSUPPORTED_CERTIFICATE, empty_dh_public)); dec_client_key(<<?UINT16(DH_YLen), DH_Y:DH_YLen/binary>>, ?KEY_EXCHANGE_DIFFIE_HELLMAN, _) -> #client_diffie_hellman_public{dh_public = DH_Y}; dec_client_key(<<>>, ?KEY_EXCHANGE_EC_DIFFIE_HELLMAN, _) -> - throw(?ALERT_REC(?FATAL, ?UNSUPPORTED_CERTIFICATE)); + throw(?ALERT_REC(?FATAL, ?UNSUPPORTED_CERTIFICATE, empty_dh_public)); dec_client_key(<<?BYTE(DH_YLen), DH_Y:DH_YLen/binary>>, ?KEY_EXCHANGE_EC_DIFFIE_HELLMAN, _) -> #client_ec_diffie_hellman_public{dh_public = DH_Y}; @@ -1832,7 +1888,7 @@ dec_server_key_signature(Params, <<?UINT16(0)>>, _) -> dec_server_key_signature(Params, <<?UINT16(Len), Signature:Len/binary>>, _) -> {Params, undefined, Signature}; dec_server_key_signature(_, _, _) -> - throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE)). + throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, failed_to_decrypt_server_key_sign)). dec_hello_extensions(<<>>, Acc) -> Acc; @@ -1987,8 +2043,8 @@ key_exchange_alg(_) -> %%-------------Extension handling -------------------------------- %% Receive protocols, choose one from the list, return it. -handle_alpn_extension(_, {error, _Reason}) -> - ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE); +handle_alpn_extension(_, {error, Reason}) -> + ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, Reason); handle_alpn_extension([], _) -> ?ALERT_REC(?FATAL, ?NO_APPLICATION_PROTOCOL); handle_alpn_extension([ServerProtocol|Tail], ClientProtocols) -> @@ -2008,7 +2064,7 @@ handle_next_protocol(#next_protocol_negotiation{} = NextProtocols, true -> select_next_protocol(decode_next_protocols(NextProtocols), NextProtocolSelector); false -> - ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE) % unexpected next protocol extension + ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, unexpected_next_protocol_extension) end. @@ -2028,17 +2084,17 @@ handle_next_protocol_on_server(#next_protocol_negotiation{extension_data = <<>>} Protocols; handle_next_protocol_on_server(_Hello, _Renegotiation, _SSLOpts) -> - ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE). % unexpected next protocol extension + ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, unexpected_next_protocol_extension). next_protocol_extension_allowed(NextProtocolSelector, Renegotiating) -> NextProtocolSelector =/= undefined andalso not Renegotiating. -select_next_protocol({error, _Reason}, _NextProtocolSelector) -> - ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE); +select_next_protocol({error, Reason}, _NextProtocolSelector) -> + ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, Reason); select_next_protocol(Protocols, NextProtocolSelector) -> case NextProtocolSelector(Protocols) of ?NO_PROTOCOL -> - ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE); + ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, no_next_protocol); Protocol when is_binary(Protocol) -> Protocol end. @@ -2153,13 +2209,14 @@ crl_check_same_issuer(OtpCert, _, Dps, Options) -> public_key:pkix_crls_validate(OtpCert, Dps, Options). dps_and_crls(OtpCert, Callback, CRLDbHandle, ext) -> - case public_key:pkix_dist_points(OtpCert) of - [] -> - no_dps; - DistPoints -> - distpoints_lookup(DistPoints, Callback, CRLDbHandle) - end; - + case public_key:pkix_dist_points(OtpCert) of + [] -> + no_dps; + DistPoints -> + Issuer = OtpCert#'OTPCertificate'.tbsCertificate#'OTPTBSCertificate'.issuer, + distpoints_lookup(DistPoints, Issuer, Callback, CRLDbHandle) + end; + dps_and_crls(OtpCert, Callback, CRLDbHandle, same_issuer) -> DP = #'DistributionPoint'{distributionPoint = {fullName, GenNames}} = public_key:pkix_dist_point(OtpCert), @@ -2170,34 +2227,105 @@ dps_and_crls(OtpCert, Callback, CRLDbHandle, same_issuer) -> end, GenNames), [{DP, {CRL, public_key:der_decode('CertificateList', CRL)}} || CRL <- CRLs]. -distpoints_lookup([], _, _) -> +distpoints_lookup([], _, _, _) -> []; -distpoints_lookup([DistPoint | Rest], Callback, CRLDbHandle) -> - case Callback:lookup(DistPoint, CRLDbHandle) of +distpoints_lookup([DistPoint | Rest], Issuer, Callback, CRLDbHandle) -> + Result = + try Callback:lookup(DistPoint, Issuer, CRLDbHandle) + catch + error:undef -> + %% The callback module still uses the 2-argument + %% version of the lookup function. + Callback:lookup(DistPoint, CRLDbHandle) + end, + case Result of not_available -> - distpoints_lookup(Rest, Callback, CRLDbHandle); + distpoints_lookup(Rest, Issuer, Callback, CRLDbHandle); CRLs -> [{DistPoint, {CRL, public_key:der_decode('CertificateList', CRL)}} || CRL <- CRLs] end. -cert_sign(?rsaEncryption) -> +sign_algo(?rsaEncryption) -> rsa; -cert_sign(?'id-ecPublicKey') -> +sign_algo(?'id-ecPublicKey') -> ecdsa; -cert_sign(?'id-dsa') -> +sign_algo(?'id-dsa') -> dsa; -cert_sign(Alg) -> +sign_algo(Alg) -> {_, Sign} =public_key:pkix_sign_types(Alg), Sign. -is_acceptable_hash_sign({_, Sign} = Algos, Sign, _, SupportedHashSigns) -> - is_acceptable_hash_sign(Algos, SupportedHashSigns); -is_acceptable_hash_sign(Algos,_, KeyExAlgo, SupportedHashSigns) when KeyExAlgo == dh_ecdsa; - KeyExAlgo == ecdh_rsa; - KeyExAlgo == ecdh_ecdsa -> +is_acceptable_hash_sign(Algos, _, _, KeyExAlgo, SupportedHashSigns) when + KeyExAlgo == dh_dss; + KeyExAlgo == dh_rsa; + KeyExAlgo == dh_ecdsa -> + %% dh_* could be called only dh in TLS-1.2 + is_acceptable_hash_sign(Algos, SupportedHashSigns); +is_acceptable_hash_sign(Algos, rsa, ecdsa, ecdh_rsa, SupportedHashSigns) -> + is_acceptable_hash_sign(Algos, SupportedHashSigns); +is_acceptable_hash_sign({_, rsa} = Algos, rsa, _, dhe_rsa, SupportedHashSigns) -> + is_acceptable_hash_sign(Algos, SupportedHashSigns); +is_acceptable_hash_sign({_, rsa} = Algos, rsa, rsa, ecdhe_rsa, SupportedHashSigns) -> + is_acceptable_hash_sign(Algos, SupportedHashSigns); +is_acceptable_hash_sign({_, rsa} = Algos, rsa, rsa, rsa, SupportedHashSigns) -> is_acceptable_hash_sign(Algos, SupportedHashSigns); -is_acceptable_hash_sign(_,_,_,_) -> - false. +is_acceptable_hash_sign({_, rsa} = Algos, rsa, _, srp_rsa, SupportedHashSigns) -> + is_acceptable_hash_sign(Algos, SupportedHashSigns); +is_acceptable_hash_sign({_, rsa} = Algos, rsa, _, rsa_psk, SupportedHashSigns) -> + is_acceptable_hash_sign(Algos, SupportedHashSigns); +is_acceptable_hash_sign({_, dsa} = Algos, dsa, _, dhe_dss, SupportedHashSigns) -> + is_acceptable_hash_sign(Algos, SupportedHashSigns); +is_acceptable_hash_sign({_, dsa} = Algos, dsa, _, srp_dss, SupportedHashSigns) -> + is_acceptable_hash_sign(Algos, SupportedHashSigns); +is_acceptable_hash_sign({_, ecdsa} = Algos, ecdsa, _, dhe_ecdsa, SupportedHashSigns) -> + is_acceptable_hash_sign(Algos, SupportedHashSigns); +is_acceptable_hash_sign({_, ecdsa} = Algos, ecdsa, ecdsa, ecdhe_ecdsa, SupportedHashSigns) -> + is_acceptable_hash_sign(Algos, SupportedHashSigns); +is_acceptable_hash_sign(_, _, _, KeyExAlgo, _) when + KeyExAlgo == psk; + KeyExAlgo == dhe_psk; + KeyExAlgo == srp_anon; + KeyExAlgo == dh_anon; + KeyExAlgo == ecdhe_anon + -> + true; +is_acceptable_hash_sign(_,_, _,_,_) -> + false. + is_acceptable_hash_sign(Algos, SupportedHashSigns) -> lists:member(Algos, SupportedHashSigns). +is_acceptable_cert_type(Sign, _HashSigns, Types) -> + lists:member(sign_type(Sign), binary_to_list(Types)). + +is_supported_sign(Sign, HashSigns) -> + [] =/= lists:dropwhile(fun({_, S}) when S =/= Sign -> + true; + (_)-> + false + end, HashSigns). +sign_type(rsa) -> + ?RSA_SIGN; +sign_type(dsa) -> + ?DSS_SIGN; +sign_type(ecdsa) -> + ?ECDSA_SIGN. + + +bad_key(#'DSAPrivateKey'{}) -> + unacceptable_dsa_key; +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_handshake.hrl b/lib/ssl/src/ssl_handshake.hrl index b74a65939b..fde92035a2 100644 --- a/lib/ssl/src/ssl_handshake.hrl +++ b/lib/ssl/src/ssl_handshake.hrl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2014. All Rights Reserved. +%% Copyright Ericsson AB 2007-2016. 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. @@ -53,7 +53,8 @@ -define(NUM_OF_SESSION_ID_BYTES, 32). % TSL 1.1 & SSL 3 -define(NUM_OF_PREMASTERSECRET_BYTES, 48). -define(DEFAULT_DIFFIE_HELLMAN_GENERATOR, 2). --define(DEFAULT_DIFFIE_HELLMAN_PRIME, 16#FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFF). +-define(DEFAULT_DIFFIE_HELLMAN_PRIME, + 16#FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%% Handsake protocol - RFC 4346 section 7.4 diff --git a/lib/ssl/src/ssl_internal.hrl b/lib/ssl/src/ssl_internal.hrl index 20f0b7d0da..c19c1787ff 100644 --- a/lib/ssl/src/ssl_internal.hrl +++ b/lib/ssl/src/ssl_internal.hrl @@ -93,16 +93,16 @@ validate_extensions_fun, depth :: integer(), certfile :: binary(), - cert :: public_key:der_encoded() | secret_printout(), + cert :: public_key:der_encoded() | secret_printout() | 'undefined', keyfile :: binary(), - key :: {'RSAPrivateKey' | 'DSAPrivateKey' | 'ECPrivateKey' | 'PrivateKeyInfo', public_key:der_encoded()} | secret_printout(), - password :: string() | secret_printout(), - cacerts :: [public_key:der_encoded()] | secret_printout(), + key :: {'RSAPrivateKey' | 'DSAPrivateKey' | 'ECPrivateKey' | 'PrivateKeyInfo', public_key:der_encoded()} | secret_printout() | 'undefined', + password :: string() | secret_printout() | 'undefined', + cacerts :: [public_key:der_encoded()] | secret_printout() | 'undefined', cacertfile :: binary(), dh :: public_key:der_encoded() | secret_printout(), - dhfile :: binary() | secret_printout(), + dhfile :: binary() | secret_printout() | 'undefined', user_lookup_fun, % server option, fun to lookup the user - psk_identity :: binary() | secret_printout() , + psk_identity :: binary() | secret_printout() | 'undefined', srp_identity, % client option {User, Password} ciphers, % %% Local policy for the server if it want's to reuse the session @@ -118,7 +118,7 @@ %% undefined if not hibernating, or number of ms of %% inactivity after which ssl_connection will go into %% hibernation - hibernate_after :: boolean(), + hibernate_after :: timeout(), %% This option should only be set to true by inet_tls_dist erl_dist = false :: boolean(), alpn_advertised_protocols = undefined :: [binary()] | undefined , @@ -133,10 +133,14 @@ %% the client? honor_cipher_order = false :: boolean(), padding_check = true :: boolean(), + %%Should we use 1/n-1 or 0/n splitting to mitigate BEAST, or disable + %%mitigation entirely? + beast_mitigation = one_n_minus_one :: one_n_minus_one | zero_n | disabled, fallback = false :: boolean(), crl_check :: boolean() | peer | best_effort, crl_cache, - signature_algs + signature_algs, + v2_hello_compatible :: boolean() }). -record(socket_options, diff --git a/lib/ssl/src/ssl_listen_tracker_sup.erl b/lib/ssl/src/ssl_listen_tracker_sup.erl index f9a0ba331e..7f685a2ead 100644 --- a/lib/ssl/src/ssl_listen_tracker_sup.erl +++ b/lib/ssl/src/ssl_listen_tracker_sup.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2014-2014. All Rights Reserved. +%% Copyright Ericsson AB 2014-2016. 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. diff --git a/lib/ssl/src/ssl_manager.erl b/lib/ssl/src/ssl_manager.erl index 8ed29cc6b0..c7dcbaabe9 100644 --- a/lib/ssl/src/ssl_manager.erl +++ b/lib/ssl/src/ssl_manager.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2015. All Rights Reserved. +%% Copyright Ericsson AB 2007-2016. 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. @@ -554,7 +554,7 @@ last_delay_timer({_,_}, TRef, {_, LastClient}) -> new_id(_, 0, _, _) -> <<>>; new_id(Port, Tries, Cache, CacheCb) -> - Id = crypto:rand_bytes(?NUM_OF_SESSION_ID_BYTES), + Id = ssl_cipher:random_bytes(?NUM_OF_SESSION_ID_BYTES), case CacheCb:lookup(Cache, {Port, Id}) of undefined -> Now = erlang:monotonic_time(), @@ -613,8 +613,8 @@ server_register_session(Port, Session, #state{session_cache_server_max = Max, do_register_session(Key, Session, Max, Pid, Cache, CacheCb) -> try CacheCb:size(Cache) of - N when N > Max -> - invalidate_session_cache(Pid, CacheCb, Cache); + Max -> + invalidate_session_cache(Pid, CacheCb, Cache); _ -> CacheCb:update(Cache, Key, Session), Pid diff --git a/lib/ssl/src/ssl_record.erl b/lib/ssl/src/ssl_record.erl index ce6b8fb84f..5bb1c92c2d 100644 --- a/lib/ssl/src/ssl_record.erl +++ b/lib/ssl/src/ssl_record.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2013-2015. All Rights Reserved. +%% Copyright Ericsson AB 2013-2016. 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. @@ -30,7 +30,7 @@ -include("ssl_alert.hrl"). %% Connection state handling --export([init_connection_states/1, +-export([init_connection_states/2, current_connection_state/2, pending_connection_state/2, activate_pending_connection_state/2, set_security_params/3, @@ -62,16 +62,18 @@ %%==================================================================== %%-------------------------------------------------------------------- --spec init_connection_states(client | server) -> #connection_states{}. +-spec init_connection_states(client | server, one_n_minus_one | zero_n | disabled ) -> + #connection_states{}. %% %% Description: Creates a connection_states record with appropriate %% values for the initial SSL connection setup. %%-------------------------------------------------------------------- -init_connection_states(Role) -> +init_connection_states(Role, BeastMitigation) -> ConnectionEnd = record_protocol_role(Role), - Current = initial_connection_state(ConnectionEnd), - Pending = empty_connection_state(ConnectionEnd), - #connection_states{current_read = Current, + Current = initial_connection_state(ConnectionEnd, BeastMitigation), + Pending = empty_connection_state(ConnectionEnd, BeastMitigation), + #connection_states{dtls_write_msg_seq = 1, % only used by dtls + current_read = Current, pending_read = Pending, current_write = Current, pending_write = Pending @@ -119,9 +121,10 @@ activate_pending_connection_state(States = read) -> NewCurrent = Pending#connection_state{epoch = dtls_next_epoch(Current), sequence_number = 0}, + BeastMitigation = Pending#connection_state.beast_mitigation, SecParams = Pending#connection_state.security_parameters, ConnectionEnd = SecParams#security_parameters.connection_end, - EmptyPending = empty_connection_state(ConnectionEnd), + EmptyPending = empty_connection_state(ConnectionEnd, BeastMitigation), SecureRenegotation = NewCurrent#connection_state.secure_renegotiation, NewPending = EmptyPending#connection_state{secure_renegotiation = SecureRenegotation}, States#connection_states{current_read = NewCurrent, @@ -134,9 +137,10 @@ activate_pending_connection_state(States = write) -> NewCurrent = Pending#connection_state{epoch = dtls_next_epoch(Current), sequence_number = 0}, + BeastMitigation = Pending#connection_state.beast_mitigation, SecParams = Pending#connection_state.security_parameters, ConnectionEnd = SecParams#security_parameters.connection_end, - EmptyPending = empty_connection_state(ConnectionEnd), + EmptyPending = empty_connection_state(ConnectionEnd, BeastMitigation), SecureRenegotation = NewCurrent#connection_state.secure_renegotiation, NewPending = EmptyPending#connection_state{secure_renegotiation = SecureRenegotation}, States#connection_states{current_write = NewCurrent, @@ -314,16 +318,28 @@ set_pending_cipher_state(#connection_states{pending_read = Read, encode_handshake(Frag, Version, #connection_states{current_write = #connection_state{ + 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), + 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{}}. @@ -352,10 +368,11 @@ encode_change_cipher_spec(Version, ConnectionStates) -> %%-------------------------------------------------------------------- encode_data(Frag, Version, #connection_states{current_write = #connection_state{ + beast_mitigation = BeastMitigation, security_parameters = #security_parameters{bulk_cipher_algorithm = BCA}}} = ConnectionStates) -> - Data = split_bin(Frag, ?MAX_PLAIN_TEXT_LENGTH, Version, BCA), + Data = split_bin(Frag, ?MAX_PLAIN_TEXT_LENGTH, Version, BCA, BeastMitigation), encode_iolist(?APPLICATION_DATA, Data, Version, ConnectionStates). uncompress(?NULL, Data, CS) -> @@ -447,9 +464,10 @@ decipher_aead(Version, CipherFragment, %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- -empty_connection_state(ConnectionEnd) -> +empty_connection_state(ConnectionEnd, BeastMitigation) -> SecParams = empty_security_params(ConnectionEnd), - #connection_state{security_parameters = SecParams}. + #connection_state{security_parameters = SecParams, + beast_mitigation = BeastMitigation}. empty_security_params(ConnectionEnd = ?CLIENT) -> #security_parameters{connection_end = ConnectionEnd, @@ -460,7 +478,7 @@ empty_security_params(ConnectionEnd = ?SERVER) -> random() -> Secs_since_1970 = calendar:datetime_to_gregorian_seconds( calendar:universal_time()) - 62167219200, - Random_28_bytes = crypto:rand_bytes(28), + Random_28_bytes = ssl_cipher:random_bytes(28), <<?UINT32(Secs_since_1970), Random_28_bytes/binary>>. dtls_next_epoch(#connection_state{epoch = undefined}) -> %% SSL/TLS @@ -478,10 +496,11 @@ record_protocol_role(client) -> record_protocol_role(server) -> ?SERVER. -initial_connection_state(ConnectionEnd) -> +initial_connection_state(ConnectionEnd, BeastMitigation) -> #connection_state{security_parameters = initial_security_params(ConnectionEnd), - sequence_number = 0 + sequence_number = 0, + beast_mitigation = BeastMitigation }. initial_security_params(ConnectionEnd) -> @@ -506,11 +525,17 @@ encode_iolist(Type, Data, Version, ConnectionStates0) -> %% 1/n-1 splitting countermeasure Rizzo/Duong-Beast, RC4 chiphers are %% not vulnerable to this attack. -split_bin(<<FirstByte:8, Rest/binary>>, ChunkSize, Version, BCA) when +split_bin(<<FirstByte:8, Rest/binary>>, ChunkSize, Version, BCA, one_n_minus_one) when BCA =/= ?RC4 andalso ({3, 1} == Version orelse {3, 0} == Version) -> do_split_bin(Rest, ChunkSize, [[FirstByte]]); -split_bin(Bin, ChunkSize, _, _) -> +%% 0/n splitting countermeasure for clients that are incompatible with 1/n-1 +%% splitting. +split_bin(Bin, ChunkSize, Version, BCA, zero_n) when + BCA =/= ?RC4 andalso ({3, 1} == Version orelse + {3, 0} == Version) -> + do_split_bin(Bin, ChunkSize, [[<<>>]]); +split_bin(Bin, ChunkSize, _, _, _) -> do_split_bin(Bin, ChunkSize, []). do_split_bin(<<>>, _, Acc) -> diff --git a/lib/ssl/src/ssl_record.hrl b/lib/ssl/src/ssl_record.hrl index af77378f44..a41264ff9b 100644 --- a/lib/ssl/src/ssl_record.hrl +++ b/lib/ssl/src/ssl_record.hrl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2014. All Rights Reserved. +%% Copyright Ericsson AB 2007-2016. 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. @@ -40,10 +40,14 @@ %% RFC 5746 secure_renegotiation, client_verify_data, - server_verify_data + server_verify_data, + %% How to do BEAST mitigation? + beast_mitigation }). -record(connection_states, { + dtls_write_msg_seq, %% Only used by DTLS + current_read, pending_read, current_write, diff --git a/lib/ssl/src/ssl_session.erl b/lib/ssl/src/ssl_session.erl index 2b24bff5ff..c9607489e9 100644 --- a/lib/ssl/src/ssl_session.erl +++ b/lib/ssl/src/ssl_session.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2012. All Rights Reserved. +%% Copyright Ericsson AB 2007-2016. 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. diff --git a/lib/ssl/src/ssl_session_cache.erl b/lib/ssl/src/ssl_session_cache.erl index 9585e613e6..c79ad1523b 100644 --- a/lib/ssl/src/ssl_session_cache.erl +++ b/lib/ssl/src/ssl_session_cache.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2014. All Rights Reserved. +%% Copyright Ericsson AB 2008-2016. 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. diff --git a/lib/ssl/src/ssl_session_cache_api.erl b/lib/ssl/src/ssl_session_cache_api.erl index 8f62c25be5..b68c75a09b 100644 --- a/lib/ssl/src/ssl_session_cache_api.erl +++ b/lib/ssl/src/ssl_session_cache_api.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2011. All Rights Reserved. +%% Copyright Ericsson AB 2008-2016. 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. diff --git a/lib/ssl/src/ssl_socket.erl b/lib/ssl/src/ssl_socket.erl index a5487bfb5c..b2aea2ba9c 100644 --- a/lib/ssl/src/ssl_socket.erl +++ b/lib/ssl/src/ssl_socket.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1998-2014. All Rights Reserved. +%% Copyright Ericsson AB 1998-2016. 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. @@ -24,7 +24,7 @@ -include("ssl_internal.hrl"). -include("ssl_api.hrl"). --export([socket/5, setopts/3, getopts/3, peername/2, sockname/2, port/2]). +-export([socket/5, setopts/3, getopts/3, getstat/3, peername/2, sockname/2, port/2]). -export([emulated_options/0, internal_inet_values/0, default_inet_values/0, init/1, start_link/3, terminate/2, inherit_tracker/3, get_emulated_opts/1, set_emulated_opts/2, get_all_opts/1, handle_call/3, handle_cast/2, @@ -74,6 +74,11 @@ getopts(gen_tcp, Socket, Options) -> getopts(Transport, Socket, Options) -> Transport:getopts(Socket, Options). +getstat(gen_tcp, Socket, Options) -> + inet:getstat(Socket, Options); +getstat(Transport, Socket, Options) -> + Transport:getstat(Socket, Options). + peername(gen_tcp, Socket) -> inet:peername(Socket); peername(Transport, Socket) -> diff --git a/lib/ssl/src/ssl_srp.hrl b/lib/ssl/src/ssl_srp.hrl index f543866085..d6e45adeee 100644 --- a/lib/ssl/src/ssl_srp.hrl +++ b/lib/ssl/src/ssl_srp.hrl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2013. All Rights Reserved. +%% Copyright Ericsson AB 2007-2016. 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. diff --git a/lib/ssl/src/ssl_sup.erl b/lib/ssl/src/ssl_sup.erl index 950a6e0944..7fa1f7dc9e 100644 --- a/lib/ssl/src/ssl_sup.erl +++ b/lib/ssl/src/ssl_sup.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1998-2014. All Rights Reserved. +%% Copyright Ericsson AB 1998-2016. 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. diff --git a/lib/ssl/src/ssl_tls_dist_proxy.erl b/lib/ssl/src/ssl_tls_dist_proxy.erl index 4c789793ec..08947f24dd 100644 --- a/lib/ssl/src/ssl_tls_dist_proxy.erl +++ b/lib/ssl/src/ssl_tls_dist_proxy.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2011-2013. All Rights Reserved. +%% Copyright Ericsson AB 2011-2016. 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. @@ -116,7 +116,8 @@ handle_call({listen, Driver, Name}, _From, State) -> {ok, TcpAddress} = get_tcp_address(Socket), {ok, WorldTcpAddress} = get_tcp_address(World), {_,Port} = WorldTcpAddress#net_address.address, - case erl_epmd:register_node(Name, Port) of + ErlEpmd = net_kernel:epmd_module(), + case ErlEpmd:register_node(Name, Port, Driver) of {ok, Creation} -> {reply, {ok, {Socket, TcpAddress, Creation}}, State#state{listen={Socket, World}}}; @@ -195,6 +196,11 @@ accept_loop(Proxy, erts = Type, Listen, Extra) -> {_Kernel, unsupported_protocol} -> exit(unsupported_protocol) end; + {error, closed} -> + %% The listening socket is closed: the proxy process is + %% shutting down. Exit normally, to avoid generating a + %% spurious error report. + exit(normal); Error -> exit(Error) end, @@ -396,6 +402,18 @@ ssl_options(server, ["server_verify", Value|T]) -> [{verify, atomize(Value)} | ssl_options(server,T)]; ssl_options(client, ["client_verify", Value|T]) -> [{verify, atomize(Value)} | ssl_options(client,T)]; +ssl_options(server, ["server_verify_fun", Value|T]) -> + [{verify_fun, verify_fun(Value)} | ssl_options(server,T)]; +ssl_options(client, ["client_verify_fun", Value|T]) -> + [{verify_fun, verify_fun(Value)} | ssl_options(client,T)]; +ssl_options(server, ["server_crl_check", Value|T]) -> + [{crl_check, atomize(Value)} | ssl_options(server,T)]; +ssl_options(client, ["client_crl_check", Value|T]) -> + [{crl_check, atomize(Value)} | ssl_options(client,T)]; +ssl_options(server, ["server_crl_cache", Value|T]) -> + [{crl_cache, termify(Value)} | ssl_options(server,T)]; +ssl_options(client, ["client_crl_cache", Value|T]) -> + [{crl_cache, termify(Value)} | ssl_options(client,T)]; ssl_options(server, ["server_reuse_sessions", Value|T]) -> [{reuse_sessions, atomize(Value)} | ssl_options(server,T)]; ssl_options(client, ["client_reuse_sessions", Value|T]) -> @@ -420,14 +438,28 @@ ssl_options(server, ["server_dhfile", Value|T]) -> [{dhfile, Value} | ssl_options(server,T)]; ssl_options(server, ["server_fail_if_no_peer_cert", Value|T]) -> [{fail_if_no_peer_cert, atomize(Value)} | ssl_options(server,T)]; -ssl_options(_,_) -> - exit(malformed_ssl_dist_opt). +ssl_options(Type, Opts) -> + error(malformed_ssl_dist_opt, [Type, Opts]). atomize(List) when is_list(List) -> list_to_atom(List); atomize(Atom) when is_atom(Atom) -> Atom. +termify(String) when is_list(String) -> + {ok, Tokens, _} = erl_scan:string(String ++ "."), + {ok, Term} = erl_parse:parse_term(Tokens), + Term. + +verify_fun(Value) -> + case termify(Value) of + {Mod, Func, State} when is_atom(Mod), is_atom(Func) -> + Fun = fun Mod:Func/3, + {Fun, State}; + _ -> + error(malformed_ssl_dist_opt, [Value]) + end. + flush_old_controller(Pid, Socket) -> receive {tcp, Socket, Data} -> diff --git a/lib/ssl/src/ssl_v2.erl b/lib/ssl/src/ssl_v2.erl index 1764da5c63..37134cbe5d 100644 --- a/lib/ssl/src/ssl_v2.erl +++ b/lib/ssl/src/ssl_v2.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2013. All Rights Reserved. +%% Copyright Ericsson AB 2007-2016. 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. diff --git a/lib/ssl/src/ssl_v3.erl b/lib/ssl/src/ssl_v3.erl index f169059a75..82d165f995 100644 --- a/lib/ssl/src/ssl_v3.erl +++ b/lib/ssl/src/ssl_v3.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2015. All Rights Reserved. +%% Copyright Ericsson AB 2007-2016. 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. @@ -143,9 +143,7 @@ suites() -> ?TLS_RSA_WITH_3DES_EDE_CBC_SHA, ?TLS_DHE_RSA_WITH_AES_128_CBC_SHA, ?TLS_DHE_DSS_WITH_AES_128_CBC_SHA, - ?TLS_RSA_WITH_AES_128_CBC_SHA, - ?TLS_DHE_RSA_WITH_DES_CBC_SHA, - ?TLS_RSA_WITH_DES_CBC_SHA + ?TLS_RSA_WITH_AES_128_CBC_SHA ]. %%-------------------------------------------------------------------- diff --git a/lib/ssl/src/tls.erl b/lib/ssl/src/tls.erl index d4cb8788bf..aa41cd1ba6 100644 --- a/lib/ssl/src/tls.erl +++ b/lib/ssl/src/tls.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1999-2013. All Rights Reserved. +%% Copyright Ericsson AB 1999-2016. 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. diff --git a/lib/ssl/src/tls_connection.erl b/lib/ssl/src/tls_connection.erl index 93716d31b8..9880befa94 100644 --- a/lib/ssl/src/tls_connection.erl +++ b/lib/ssl/src/tls_connection.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2015. All Rights Reserved. +%% Copyright Ericsson AB 2007-2016. 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. @@ -28,7 +28,7 @@ -module(tls_connection). --behaviour(gen_fsm). +-behaviour(gen_statem). -include("tls_connection.hrl"). -include("tls_handshake.hrl"). @@ -43,31 +43,34 @@ %% Internal application API %% Setup --export([start_fsm/8]). +-export([start_fsm/8, start_link/7, init/1]). %% State transition handling --export([next_record/1, next_state/4, next_state_connection/2]). +-export([next_record/1, next_event/3]). %% Handshake handling --export([renegotiate/1, send_handshake/2, send_change_cipher/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, - handle_normal_shutdown/3, handle_unexpected_message/3, + handle_normal_shutdown/3, close/5, alert_user/6, alert_user/9 ]). %% Data handling -export([write_application_data/3, read_application_data/2, - passive_receive/2, next_record_if_active/1]). + passive_receive/2, next_record_if_active/1, handle_common_event/4]). -%% Called by tls_connection_sup --export([start_link/7]). - -%% gen_fsm callbacks --export([init/1, hello/2, certify/2, cipher/2, - abbreviated/2, connection/2, handle_event/3, - handle_sync_event/4, handle_info/3, terminate/3, code_change/4, format_status/2]). +%% 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]). + +-define(GEN_STATEM_CB_MODE, state_functions). %%==================================================================== %% Internal application API @@ -100,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, @@ -121,14 +139,15 @@ 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. + %% To reduce memory foot print of a connection reinitialize them. + State#state{ + premaster_secret = undefined, + public_key_info = undefined, + tls_handshake_history = ssl_handshake:init_handshake_history() + }. %%==================================================================== %% tls_connection_sup API @@ -147,23 +166,34 @@ start_link(Role, Host, Port, Socket, Options, User, CbInfo) -> init([Role, Host, Port, Socket, Options, User, CbInfo]) -> process_flag(trap_exit, true), - State = initial_state(Role, Host, Port, Socket, Options, User, CbInfo), - gen_fsm:enter_loop(?MODULE, [], hello, State, get_timeout(State)). + 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) + catch throw:Error -> + gen_statem:enter_loop(?MODULE, [], ?GEN_STATEM_CB_MODE, error, {Error, State0}) + end. %%-------------------------------------------------------------------- -%% Description:There should be one instance of this function for each -%% possible state name. Whenever a gen_fsm receives an event sent -%% using gen_fsm:send_event/2, the instance of this function with the -%% same name as the current state name StateName is called to handle -%% the event. It is also called if a timeout occurs. -%% -hello(start, #state{host = Host, port = Port, role = client, - ssl_options = SslOpts, - session = #session{own_certificate = Cert} = Session0, - session_cache = Cache, session_cache_cb = CacheCb, - transport_cb = Transport, socket = Socket, - connection_states = ConnectionStates0, - renegotiation = {Renegotiation, _}} = State0) -> +%% State functions +%%-------------------------------------------------------------------- +%%-------------------------------------------------------------------- +-spec init(gen_statem:event_type(), + {start, timeout()} | term(), #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- + +init({call, From}, {start, Timeout}, + #state{host = Host, port = Port, role = client, + ssl_options = SslOpts, + session = #session{own_certificate = Cert} = Session0, + transport_cb = Transport, socket = Socket, + connection_states = ConnectionStates0, + renegotiation = {Renegotiation, _}, + session_cache = Cache, + session_cache_cb = CacheCb + } = State0) -> + Timer = ssl_connection:start_or_recv_cancel_timer(Timeout, From), Hello = tls_handshake:client_hello(Host, Port, ConnectionStates0, SslOpts, Cache, CacheCb, Renegotiation, Cert), @@ -177,21 +207,44 @@ hello(start, #state{host = Host, port = Port, role = client, negotiated_version = Version, %% Requested version session = Session0#session{session_id = Hello#client_hello.session_id}, - tls_handshake_history = Handshake}, + tls_handshake_history = Handshake, + start_or_recv_from = From, + timer = Timer}, {Record, State} = next_record(State1), - next_state(hello, hello, Record, State); - -hello(Hello = #client_hello{client_version = ClientVersion, - extensions = #hello_extensions{ec_point_formats = EcPointFormats, - elliptic_curves = EllipticCurves}}, - 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}) -> + next_event(hello, Record, State); +init(Type, Event, State) -> + ssl_connection:init(Type, Event, State, ?MODULE). + +%%-------------------------------------------------------------------- +-spec error(gen_statem:event_type(), + {start, timeout()} | term(), #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- + +error({call, From}, {start, _Timeout}, {Error, State}) -> + {stop_and_reply, normal, {reply, From, {error, Error}}, State}; +error({call, From}, Msg, State) -> + handle_call(Msg, From, error, State); +error(_, _, _) -> + {keep_state_and_data, [postpone]}. + +%%-------------------------------------------------------------------- +-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{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} = State) -> case tls_handshake:hello(Hello, SslOpts, {Port, Session0, Cache, CacheCb, ConnectionStates0, Cert, KeyExAlg}, Renegotiation) of @@ -203,7 +256,8 @@ hello(Hello = #client_hello{client_version = ClientVersion, undefined -> CurrentProtocol; _ -> Protocol0 end, - ssl_connection:hello({common_client_hello, Type, ServerHelloExt}, + + ssl_connection:hello(internal, {common_client_hello, Type, ServerHelloExt}, State#state{connection_states = ConnectionStates, negotiated_version = Version, hashsign_algorithm = HashSign, @@ -211,8 +265,7 @@ hello(Hello = #client_hello{client_version = ClientVersion, client_ecc = {EllipticCurves, EcPointFormats}, negotiated_protocol = Protocol}, ?MODULE) end; - -hello(Hello = #server_hello{}, +hello(internal, #server_hello{} = Hello, #state{connection_states = ConnectionStates0, negotiated_version = ReqVersion, role = client, @@ -225,25 +278,52 @@ hello(Hello = #server_hello{}, ssl_connection:handle_session(Hello, Version, NewId, ConnectionStates, ProtoExt, Protocol, State) end; +hello(info, Event, State) -> + handle_info(Event, hello, State); +hello(Type, Event, State) -> + ssl_connection:hello(Type, Event, State, ?MODULE). -hello(Msg, State) -> - ssl_connection:hello(Msg, State, ?MODULE). - -abbreviated(Msg, State) -> - ssl_connection:abbreviated(Msg, State, ?MODULE). +%%-------------------------------------------------------------------- +-spec abbreviated(gen_statem:event_type(), term(), #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +abbreviated(info, Event, State) -> + handle_info(Event, abbreviated, State); +abbreviated(Type, Event, State) -> + ssl_connection:abbreviated(Type, Event, State, ?MODULE). -certify(Msg, State) -> - ssl_connection:certify(Msg, State, ?MODULE). +%%-------------------------------------------------------------------- +-spec certify(gen_statem:event_type(), term(), #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +certify(info, Event, State) -> + handle_info(Event, certify, State); +certify(Type, Event, State) -> + ssl_connection:certify(Type, Event, State, ?MODULE). -cipher(Msg, State) -> - ssl_connection:cipher(Msg, State, ?MODULE). +%%-------------------------------------------------------------------- +-spec cipher(gen_statem:event_type(), term(), #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +cipher(info, Event, State) -> + handle_info(Event, cipher, State); +cipher(Type, Event, State) -> + ssl_connection:cipher(Type, Event, State, ?MODULE). -connection(#hello_request{}, #state{host = Host, port = Port, - session = #session{own_certificate = Cert} = Session0, - session_cache = Cache, session_cache_cb = CacheCb, - ssl_options = SslOpts, - connection_states = ConnectionStates0, - renegotiation = {Renegotiation, _}} = State0) -> +%%-------------------------------------------------------------------- +-spec connection(gen_statem:event_type(), + #hello_request{} | #client_hello{}| term(), #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +connection(info, Event, State) -> + handle_info(Event, connection, State); +connection(internal, #hello_request{}, + #state{role = client, host = Host, port = Port, + session = #session{own_certificate = Cert} = Session0, + session_cache = Cache, session_cache_cb = CacheCb, + ssl_options = SslOpts, + connection_states = ConnectionStates0, + renegotiation = {Renegotiation, _}} = State0) -> Hello = tls_handshake:client_hello(Host, Port, ConnectionStates0, SslOpts, Cache, CacheCb, Renegotiation, Cert), State1 = send_handshake(Hello, State0), @@ -251,58 +331,51 @@ connection(#hello_request{}, #state{host = Host, port = Port, next_record( State1#state{session = Session0#session{session_id = Hello#client_hello.session_id}}), - next_state(connection, hello, Record, State); - -connection(#client_hello{} = Hello, #state{role = server, allow_renegotiate = true} = State) -> + next_event(hello, Record, State); +connection(internal, #client_hello{} = Hello, + #state{role = server, allow_renegotiate = true} = State0) -> %% Mitigate Computational DoS attack %% http://www.educatedguesswork.org/2011/10/ssltls_and_computational_dos.html %% http://www.thc.org/thc-ssl-dos/ Rather than disabling client %% initiated renegotiation we will disallow many client initiated %% renegotiations immediately after each other. erlang:send_after(?WAIT_TO_ALLOW_RENEGOTIATION, self(), allow_renegotiate), - hello(Hello, State#state{allow_renegotiate = false}); - -connection(#client_hello{}, #state{role = server, allow_renegotiate = false} = State0) -> + {Record, State} = next_record(State0#state{allow_renegotiate = false, + renegotiation = {true, peer}}), + next_event(hello, Record, State, [{next_event, internal, Hello}]); +connection(internal, #client_hello{}, + #state{role = server, allow_renegotiate = false} = State0) -> Alert = ?ALERT_REC(?WARNING, ?NO_RENEGOTIATION), - State = send_alert(Alert, State0), - next_state_connection(connection, State); - -connection(Msg, State) -> - ssl_connection:connection(Msg, State, tls_connection). - -%%-------------------------------------------------------------------- -%% Description: Whenever a gen_fsm receives an event sent using -%% gen_fsm:send_all_state_event/2, this function is called to handle -%% the event. Not currently used! -%%-------------------------------------------------------------------- -handle_event(_Event, StateName, State) -> - {next_state, StateName, State, get_timeout(State)}. + State1 = send_alert(Alert, State0), + {Record, State} = ssl_connection:prepare_connection(State1, ?MODULE), + next_event(connection, Record, State); +connection(Type, Event, State) -> + ssl_connection:connection(Type, Event, State, ?MODULE). %%-------------------------------------------------------------------- -%% Description: Whenever a gen_fsm receives an event sent using -%% gen_fsm:sync_send_all_state_event/2,3, this function is called to handle -%% the event. +-spec downgrade(gen_statem:event_type(), term(), #state{}) -> + gen_statem:state_function_result(). %%-------------------------------------------------------------------- -handle_sync_event(Event, From, StateName, State) -> - ssl_connection:handle_sync_event(Event, From, StateName, State). +downgrade(Type, Event, State) -> + ssl_connection:downgrade(Type, Event, State, ?MODULE). %%-------------------------------------------------------------------- -%% Description: This function is called by a gen_fsm when it receives any -%% other message than a synchronous or asynchronous event -%% (or a system message). +%% Event handling functions called by state functions to handle +%% common or unexpected events for the state. %%-------------------------------------------------------------------- - +handle_call(Event, From, StateName, State) -> + ssl_connection:handle_call(Event, From, StateName, State, ?MODULE). + %% raw data from socket, unpack records handle_info({Protocol, _, Data}, StateName, #state{data_tag = Protocol} = State0) -> case next_tls_record(Data, State0) of {Record, State} -> - next_state(StateName, StateName, Record, State); + next_event(StateName, Record, State); #alert{} = Alert -> handle_normal_shutdown(Alert, StateName, State0), - {stop, {shutdown, own_alert}, State0} + {stop, {shutdown, own_alert}} end; - handle_info({CloseTag, Socket}, StateName, #state{socket = Socket, close_tag = CloseTag, negotiated_version = Version} = State) -> @@ -321,32 +394,72 @@ handle_info({CloseTag, Socket}, StateName, ok end, handle_normal_shutdown(?ALERT_REC(?FATAL, ?CLOSE_NOTIFY), StateName, State), - {stop, {shutdown, transport_closed}, State}; - + {stop, {shutdown, transport_closed}}; 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); + +%%% TLS record protocol level handshake messages +handle_common_event(internal, #ssl_tls{type = ?HANDSHAKE, fragment = Data}, + StateName, #state{protocol_buffers = + #protocol_buffers{tls_handshake_buffer = Buf0} = Buffers, + negotiated_version = Version, + ssl_options = Options} = State0) -> + try + {Packets, Buf} = tls_handshake:get_tls_handshake(Version,Data,Buf0, Options), + State = + 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 + catch throw:#alert{} = Alert -> + 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) -> + {next_state, StateName, State, [{next_event, internal, {application_data, Data}}]}; +%%% TLS record protocol level change cipher messages +handle_common_event(internal, #ssl_tls{type = ?CHANGE_CIPHER_SPEC, fragment = Data}, StateName, State) -> + {next_state, StateName, State, [{next_event, internal, #change_cipher_spec{type = Data}}]}; +%%% 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 + Alerts = [_|_] -> + handle_alerts(Alerts, {next_state, StateName, State}); + #alert{} = Alert -> + handle_own_alert(Alert, 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}. + %%-------------------------------------------------------------------- -%% Description:This function is called by a gen_fsm when it is about -%% to terminate. It should be the opposite of Module:init/1 and do any -%% necessary cleaning up. When it returns, the gen_fsm terminates with -%% Reason. The return value is ignored. +%% gen_statem callbacks %%-------------------------------------------------------------------- terminate(Reason, StateName, State) -> catch ssl_connection:terminate(Reason, StateName, State). +format_status(Type, Data) -> + ssl_connection:format_status(Type, Data). + %%-------------------------------------------------------------------- %% code_change(OldVsn, StateName, State, Extra) -> {ok, StateName, NewState} %% Description: Convert process state when code is changed %%-------------------------------------------------------------------- code_change(_OldVsn, StateName, State0, {Direction, From, To}) -> State = convert_state(State0, Direction, From, To), - {ok, StateName, State}; + {?GEN_STATEM_CB_MODE, StateName, State}; code_change(_OldVsn, StateName, State, _) -> - {ok, StateName, State}. - -format_status(Type, Data) -> - ssl_connection:format_status(Type, Data). + {?GEN_STATEM_CB_MODE, StateName, State}. %%-------------------------------------------------------------------- %%% Internal functions @@ -366,7 +479,8 @@ decode_alerts(Bin) -> initial_state(Role, Host, Port, Socket, {SSLOptions, SocketOptions, Tracker}, User, {CbModule, DataTag, CloseTag, ErrorTag}) -> - ConnectionStates = ssl_record:init_connection_states(Role), + #ssl_options{beast_mitigation = BeastMitigation} = SSLOptions, + ConnectionStates = ssl_record:init_connection_states(Role, BeastMitigation), SessionCacheCb = case application:get_env(ssl, session_cb) of {ok, Cb} when is_atom(Cb) -> @@ -396,9 +510,9 @@ initial_state(Role, Host, Port, Socket, {SSLOptions, SocketOptions, Tracker}, Us renegotiation = {false, first}, allow_renegotiate = SSLOptions#ssl_options.client_renegotiation, start_or_recv_from = undefined, - send_queue = queue:new(), protocol_cb = ?MODULE, - tracker = Tracker + tracker = Tracker, + flight_buffer = [] }. @@ -418,80 +532,6 @@ update_ssl_options_from_sni(OrigSSLOptions, SNIHostname) -> ssl:handle_options(SSLOption, OrigSSLOptions) end. -next_state(Current,_, #alert{} = Alert, #state{negotiated_version = Version} = State) -> - handle_own_alert(Alert, Version, Current, State); - -next_state(_,Next, no_record, State) -> - {next_state, Next, State, get_timeout(State)}; - -next_state(Current, Next, #ssl_tls{type = ?ALERT, fragment = EncAlerts}, #state{negotiated_version = Version} = State) -> - case decode_alerts(EncAlerts) of - Alerts = [_|_] -> - handle_alerts(Alerts, {next_state, Next, State, get_timeout(State)}); - #alert{} = Alert -> - handle_own_alert(Alert, Version, Current, State) - end; -next_state(Current, Next, #ssl_tls{type = ?HANDSHAKE, fragment = Data}, - State0 = #state{protocol_buffers = - #protocol_buffers{tls_handshake_buffer = Buf0} = Buffers, - negotiated_version = Version}) -> - Handle = - fun({#hello_request{} = Packet, _}, {next_state, connection = SName, State}) -> - %% This message should not be included in handshake - %% message hashes. Starts new handshake (renegotiation) - Hs0 = ssl_handshake:init_handshake_history(), - ?MODULE:SName(Packet, State#state{tls_handshake_history=Hs0, - renegotiation = {true, peer}}); - ({#hello_request{} = Packet, _}, {next_state, SName, State}) -> - %% This message should not be included in handshake - %% message hashes. Already in negotiation so it will be ignored! - ?MODULE:SName(Packet, State); - ({#client_hello{} = Packet, Raw}, {next_state, connection = SName, HState0}) -> - HState = handle_sni_extension(Packet, HState0), - Version = Packet#client_hello.client_version, - Hs0 = ssl_handshake:init_handshake_history(), - Hs1 = ssl_handshake:update_handshake_history(Hs0, Raw), - ?MODULE:SName(Packet, HState#state{tls_handshake_history=Hs1, - renegotiation = {true, peer}}); - ({Packet, Raw}, {next_state, SName, HState0 = #state{tls_handshake_history=Hs0}}) -> - HState = handle_sni_extension(Packet, HState0), - Hs1 = ssl_handshake:update_handshake_history(Hs0, Raw), - ?MODULE:SName(Packet, HState#state{tls_handshake_history=Hs1}); - (_, StopState) -> StopState - end, - try - {Packets, Buf} = tls_handshake:get_tls_handshake(Version,Data,Buf0), - State = State0#state{protocol_buffers = - Buffers#protocol_buffers{tls_packets = Packets, - tls_handshake_buffer = Buf}}, - handle_tls_handshake(Handle, Next, State) - catch throw:#alert{} = Alert -> - handle_own_alert(Alert, Version, Current, State0) - end; - -next_state(_, StateName, #ssl_tls{type = ?APPLICATION_DATA, fragment = Data}, State0) -> - case read_application_data(Data, State0) of - Stop = {stop,_,_} -> - Stop; - {Record, State} -> - next_state(StateName, StateName, Record, State) - end; -next_state(Current, Next, #ssl_tls{type = ?CHANGE_CIPHER_SPEC, fragment = <<1>>} = - _ChangeCipher, - #state{connection_states = ConnectionStates0} = State0) - when Next == cipher; Next == abbreviated -> - ConnectionStates1 = - ssl_record:activate_pending_connection_state(ConnectionStates0, read), - {Record, State} = next_record(State0#state{connection_states = ConnectionStates1}), - next_state(Current, Next, Record, State#state{expecting_finished = true}); -next_state(Current, _Next, #ssl_tls{type = ?CHANGE_CIPHER_SPEC, fragment = <<1>>} = - _ChangeCipher, #state{negotiated_version = Version} = State) -> - handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE), Version, Current, State); -next_state(Current, Next, #ssl_tls{type = _Unknown}, State0) -> - %% Ignore unknown type - {Record, State} = next_record(State0), - next_state(Current, Next, Record, State). - 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 @@ -504,11 +544,6 @@ next_tls_record(Data, #state{protocol_buffers = #protocol_buffers{tls_record_buf Alert end. -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}; next_record(#state{protocol_buffers = #protocol_buffers{tls_packets = [], tls_cipher_texts = [CT | Rest]} = Buffers, @@ -522,6 +557,11 @@ next_record(#state{protocol_buffers = #alert{} = Alert -> {Alert, State} end; +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}; next_record(State) -> {no_record, State}. @@ -533,55 +573,36 @@ next_record_if_active(State = next_record_if_active(State) -> next_record(State). -next_state_connection(StateName, #state{send_queue = Queue0, - negotiated_version = Version, - socket = Socket, - transport_cb = Transport, - connection_states = ConnectionStates0 - } = State) -> - %% Send queued up data that was queued while renegotiating - case queue:out(Queue0) of - {{value, {From, Data}}, Queue} -> - {Msgs, ConnectionStates} = - ssl_record:encode_data(Data, Version, ConnectionStates0), - Result = Transport:send(Socket, Msgs), - gen_fsm:reply(From, Result), - next_state_connection(StateName, - State#state{connection_states = ConnectionStates, - send_queue = Queue}); - {empty, Queue0} -> - next_state_is_connection(StateName, State) - end. - -%% In next_state_is_connection/1: clear tls_handshake, -%% premaster_secret and public_key_info (only needed during handshake) -%% to reduce memory foot print of a connection. -next_state_is_connection(_, State = - #state{start_or_recv_from = RecvFrom, - socket_options = - #socket_options{active = false}}) when RecvFrom =/= undefined -> - passive_receive(State#state{premaster_secret = undefined, - public_key_info = undefined, - tls_handshake_history = ssl_handshake:init_handshake_history()}, connection); - -next_state_is_connection(StateName, State0) -> - {Record, State} = next_record_if_active(State0), - next_state(StateName, connection, Record, State#state{premaster_secret = undefined, - public_key_info = undefined, - tls_handshake_history = ssl_handshake:init_handshake_history()}). - passive_receive(State0 = #state{user_data_buffer = Buffer}, StateName) -> case Buffer of <<>> -> {Record, State} = next_record(State0), - next_state(StateName, StateName, Record, State); + next_event(StateName, Record, State); _ -> - case read_application_data(<<>>, State0) of - Stop = {stop, _, _} -> - Stop; - {Record, State} -> - next_state(StateName, StateName, Record, State) - end + {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, {tls_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, {tls_record, Record}} | Actions]}; + #alert{} = Alert -> + {next_state, StateName, State, [{next_event, internal, Alert} | Actions]} end. read_application_data(Data, #state{user_application = {_Mon, Pid}, @@ -625,11 +646,6 @@ read_application_data(Data, #state{user_application = {_Mon, Pid}, {stop, normal, State0} end. -get_timeout(#state{ssl_options=#ssl_options{hibernate_after = undefined}}) -> - infinity; -get_timeout(#state{ssl_options=#ssl_options{hibernate_after = HibernateAfter}}) -> - HibernateAfter. - %% Picks ClientData get_data(_, _, <<>>) -> {more, <<>>}; @@ -736,7 +752,7 @@ header(N, Binary) -> [ByteN | header(N-1, NewBinary)]. send_or_reply(false, _Pid, From, Data) when From =/= undefined -> - gen_fsm:reply(From, Data); + 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, _, _) -> @@ -747,51 +763,31 @@ send_or_reply(_, Pid, _From, Data) -> send_user(Pid, Msg) -> Pid ! Msg. -handle_tls_handshake(Handle, StateName, - #state{protocol_buffers = - #protocol_buffers{tls_packets = [Packet]} = Buffers} = State) -> - FsmReturn = {next_state, StateName, State#state{protocol_buffers = - Buffers#protocol_buffers{tls_packets = []}}}, - Handle(Packet, FsmReturn); - -handle_tls_handshake(Handle, StateName, - #state{protocol_buffers = - #protocol_buffers{tls_packets = [Packet | Packets]} = Buffers} = - State0) -> - FsmReturn = {next_state, StateName, State0#state{protocol_buffers = - Buffers#protocol_buffers{tls_packets = - Packets}}}, - case Handle(Packet, FsmReturn) of - {next_state, NextStateName, State, _Timeout} -> - handle_tls_handshake(Handle, NextStateName, State); - {next_state, NextStateName, State} -> - handle_tls_handshake(Handle, NextStateName, State); - {stop, _,_} = Stop -> - Stop - end; - -handle_tls_handshake(_Handle, _StateName, #state{}) -> - throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE)). +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, - send_queue = SendQueue, 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{send_queue = queue:in_r({From, Data}, SendQueue), - renegotiation = {true, internal}}); + 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), - {reply, Result, - connection, State#state{connection_states = ConnectionStates}, get_timeout(State)} + ssl_connection:hibernate_after(connection, State#state{connection_states = ConnectionStates}, + [{reply, From, Result}]) end. encode_packet(Data, #socket_options{packet=Packet}) -> @@ -823,69 +819,73 @@ is_time_to_renegotiate(N, M) when N < M-> false; is_time_to_renegotiate(_,_) -> true. -renegotiate(#state{role = client} = State) -> +renegotiate(#state{role = client} = State, Actions) -> %% Handle same way as if server requested %% the renegotiation Hs0 = ssl_handshake:init_handshake_history(), - connection(#hello_request{}, State#state{tls_handshake_history = Hs0}); + {next_state, connection, State#state{tls_handshake_history = Hs0}, + [{next_event, internal, #hello_request{}} | Actions]}; + renegotiate(#state{role = server, socket = Socket, transport_cb = Transport, negotiated_version = Version, - connection_states = ConnectionStates0} = State0) -> + connection_states = ConnectionStates0} = State0, Actions) -> HelloRequest = ssl_handshake:hello_request(), 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), - {Record, State} = next_record(State0#state{connection_states = - ConnectionStates, - tls_handshake_history = Hs0}), - next_state(connection, hello, Record, State#state{allow_renegotiate = true}). + State1 = State0#state{connection_states = + ConnectionStates, + tls_handshake_history = Hs0}, + {Record, State} = next_record(State1), + next_event(hello, Record, State, Actions). handle_alerts([], Result) -> Result; -handle_alerts(_, {stop, _, _} = Stop) -> - %% If it is a fatal alert immediately close +handle_alerts(_, {stop,_} = Stop) -> Stop; -handle_alerts([Alert | Alerts], {next_state, StateName, State, _Timeout}) -> - handle_alerts(Alerts, handle_alert(Alert, StateName, State)). - +handle_alerts([Alert | Alerts], {next_state, StateName, State}) -> + handle_alerts(Alerts, 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} = State) -> + 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, State}; + {stop, normal}; handle_alert(#alert{level = ?WARNING, description = ?CLOSE_NOTIFY} = Alert, StateName, State) -> handle_normal_shutdown(Alert, StateName, State), - {stop, {shutdown, peer_close}, 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}, 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_fsm:reply(From, {error, renegotiation_rejected}), + gen_statem:reply(From, {error, renegotiation_rejected}), {Record, State} = next_record(State0), - next_state(StateName, connection, Record, State); + %% Go back to connection! + next_event(connection, Record, 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_state(StateName, StateName, Record, State). + 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); @@ -895,7 +895,7 @@ 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) -> +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. @@ -937,7 +937,7 @@ handle_own_alert(Alert, Version, StateName, catch _:_ -> ok end, - {stop, {shutdown, own_alert}, State}. + {stop, {shutdown, own_alert}}. handle_normal_shutdown(Alert, _, #state{socket = Socket, transport_cb = Transport, @@ -954,11 +954,6 @@ handle_normal_shutdown(Alert, StateName, #state{socket = Socket, start_or_recv_from = RecvFrom, role = Role}) -> alert_user(Transport, Tracker, Socket, StateName, Opts, Pid, RecvFrom, Alert, Role). -handle_unexpected_message(Msg, Info, #state{negotiated_version = Version} = State) -> - Alert = ?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE), - handle_own_alert(Alert, Version, {Info, Msg}, State). - - handle_close_alert(Data, StateName, State0) -> case next_tls_record(Data, State0) of {#ssl_tls{type = ?ALERT, fragment = EncAlerts}, State} -> @@ -979,27 +974,6 @@ invalidate_session(client, Host, Port, Session) -> invalidate_session(server, _, Port, Session) -> ssl_manager:invalidate_session(Port, Session). -%% User downgrades connection -%% When downgrading an TLS connection to a transport connection -%% we must recive the close message before releasing the -%% transport socket. -close({close, {Pid, Timeout}}, Socket, Transport, ConnectionStates, Check) when is_pid(Pid) -> - ssl_socket:setopts(Transport, Socket, [{active, false}, {packet, ssl_tls}]), - case Transport:recv(Socket, 0, Timeout) of - {ok, {ssl_tls, Socket, ?ALERT, Version, Fragment}} -> - case tls_record:decode_cipher_text(#ssl_tls{type = ?ALERT, - version = Version, - fragment = Fragment - }, ConnectionStates, Check) of - {#ssl_tls{fragment = Plain}, _} -> - [Alert| _] = decode_alerts(Plain), - downgrade(Alert, Transport, Socket, Pid) - end; - {error, timeout} -> - {error, timeout}; - _ -> - {error, no_tls_close} - end; %% User closes or recursive call! close({close, Timeout}, Socket, Transport = gen_tcp, _,_) -> ssl_socket:setopts(Transport, Socket, [{active, false}]), @@ -1020,15 +994,11 @@ close({shutdown, own_alert}, Socket, Transport = gen_tcp, ConnectionStates, Chec %% with the network but we want to maximise the odds that %% peer application gets all data sent on the tcp connection. close({close, ?DEFAULT_TIMEOUT}, Socket, Transport, ConnectionStates, Check); +close(downgrade, _,_,_,_) -> + ok; %% Other close(_, Socket, Transport, _,_) -> Transport:close(Socket). -downgrade(#alert{description = ?CLOSE_NOTIFY}, Transport, Socket, Pid) -> - ssl_socket:setopts(Transport, Socket, [{active, false}, {packet, 0}, {mode, binary}]), - Transport:controlling_process(Socket, Pid), - {ok, Socket}; -downgrade(_, _,_,_) -> - {error, no_tls_close}. convert_state(#state{ssl_options = Options} = State, up, "5.3.5", "5.3.6") -> State#state{ssl_options = convert_options_partial_chain(Options, up)}; @@ -1067,6 +1037,5 @@ handle_sni_extension(#client_hello{extensions = HelloExtensions}, State0) -> } end end; -handle_sni_extension(_, State0) -> - State0. - +handle_sni_extension(_, State) -> + State. diff --git a/lib/ssl/src/tls_connection.hrl b/lib/ssl/src/tls_connection.hrl index 3a416401d8..0af2258932 100644 --- a/lib/ssl/src/tls_connection.hrl +++ b/lib/ssl/src/tls_connection.hrl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2013-2013. All Rights Reserved. +%% Copyright Ericsson AB 2013-2016. 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. diff --git a/lib/ssl/src/tls_connection_sup.erl b/lib/ssl/src/tls_connection_sup.erl index 34579a8803..d5b228dc94 100644 --- a/lib/ssl/src/tls_connection_sup.erl +++ b/lib/ssl/src/tls_connection_sup.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2014. All Rights Reserved. +%% Copyright Ericsson AB 2007-2016. 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. diff --git a/lib/ssl/src/tls_handshake.erl b/lib/ssl/src/tls_handshake.erl index f34eebb0e4..566b7db332 100644 --- a/lib/ssl/src/tls_handshake.erl +++ b/lib/ssl/src/tls_handshake.erl @@ -33,7 +33,7 @@ -include_lib("public_key/include/public_key.hrl"). -export([client_hello/8, hello/4, - get_tls_handshake/3, encode_handshake/2, decode_handshake/3]). + get_tls_handshake/4, encode_handshake/2, decode_handshake/4]). -type tls_handshake() :: #client_hello{} | ssl_handshake:ssl_handshake(). @@ -133,17 +133,17 @@ encode_handshake(Package, Version) -> [MsgType, ?uint24(Len), Bin]. %%-------------------------------------------------------------------- --spec get_tls_handshake(tls_record:tls_version(), binary(), binary() | iolist()) -> +-spec get_tls_handshake(tls_record:tls_version(), binary(), binary() | iolist(), #ssl_options{}) -> {[tls_handshake()], binary()}. %% %% Description: Given buffered and new data from ssl_record, collects %% and returns it as a list of handshake messages, also returns leftover %% data. %%-------------------------------------------------------------------- -get_tls_handshake(Version, Data, <<>>) -> - get_tls_handshake_aux(Version, Data, []); -get_tls_handshake(Version, Data, Buffer) -> - get_tls_handshake_aux(Version, list_to_binary([Buffer, Data]), []). +get_tls_handshake(Version, Data, <<>>, Options) -> + get_tls_handshake_aux(Version, Data, Options, []); +get_tls_handshake(Version, Data, Buffer, Options) -> + get_tls_handshake_aux(Version, list_to_binary([Buffer, Data]), Options, []). %%-------------------------------------------------------------------- %%% Internal functions @@ -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, @@ -167,7 +168,7 @@ handle_client_hello(Version, #client_hello{session_id = SugesstedId, SslOpts, Cache, CacheCb, Cert), case CipherSuite of no_suite -> - ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY); + ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_ciphers); _ -> {KeyExAlg,_,_,_} = ssl_cipher:suite_definition(CipherSuite), case ssl_handshake:select_hashsign(ClientHashSigns, Cert, KeyExAlg, SupportedHashSigns, Version) of @@ -184,24 +185,24 @@ handle_client_hello(Version, #client_hello{session_id = SugesstedId, end. get_tls_handshake_aux(Version, <<?BYTE(Type), ?UINT24(Length), - Body:Length/binary,Rest/binary>>, Acc) -> + Body:Length/binary,Rest/binary>>, #ssl_options{v2_hello_compatible = V2Hello} = Opts, Acc) -> Raw = <<?BYTE(Type), ?UINT24(Length), Body/binary>>, - Handshake = decode_handshake(Version, Type, Body), - get_tls_handshake_aux(Version, Rest, [{Handshake,Raw} | Acc]); -get_tls_handshake_aux(_Version, Data, Acc) -> + Handshake = decode_handshake(Version, Type, Body, V2Hello), + get_tls_handshake_aux(Version, Rest, Opts, [{Handshake,Raw} | Acc]); +get_tls_handshake_aux(_Version, Data, _, Acc) -> {lists:reverse(Acc), Data}. -decode_handshake(_, ?HELLO_REQUEST, <<>>) -> +decode_handshake(_, ?HELLO_REQUEST, <<>>, _) -> #hello_request{}; %% Client hello v2. %% The server must be able to receive such messages, from clients that %% are willing to use ssl v3 or higher, but have ssl v2 compatibility. decode_handshake(_Version, ?CLIENT_HELLO, <<?BYTE(Major), ?BYTE(Minor), - ?UINT16(CSLength), ?UINT16(0), - ?UINT16(CDLength), - CipherSuites:CSLength/binary, - ChallengeData:CDLength/binary>>) -> + ?UINT16(CSLength), ?UINT16(0), + ?UINT16(CDLength), + CipherSuites:CSLength/binary, + ChallengeData:CDLength/binary>>, true) -> #client_hello{client_version = {Major, Minor}, random = ssl_v2:client_random(ChallengeData, CDLength), session_id = 0, @@ -209,12 +210,18 @@ decode_handshake(_Version, ?CLIENT_HELLO, <<?BYTE(Major), ?BYTE(Minor), compression_methods = [?NULL], extensions = #hello_extensions{} }; +decode_handshake(_Version, ?CLIENT_HELLO, <<?BYTE(_), ?BYTE(_), + ?UINT16(CSLength), ?UINT16(0), + ?UINT16(CDLength), + _CipherSuites:CSLength/binary, + _ChallengeData:CDLength/binary>>, false) -> + throw(?ALERT_REC(?FATAL, ?PROTOCOL_VERSION, ssl_v2_client_hello_no_supported)); decode_handshake(_Version, ?CLIENT_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary, - ?BYTE(SID_length), Session_ID:SID_length/binary, - ?UINT16(Cs_length), CipherSuites:Cs_length/binary, - ?BYTE(Cm_length), Comp_methods:Cm_length/binary, - Extensions/binary>>) -> - + ?BYTE(SID_length), Session_ID:SID_length/binary, + ?UINT16(Cs_length), CipherSuites:Cs_length/binary, + ?BYTE(Cm_length), Comp_methods:Cm_length/binary, + Extensions/binary>>, _) -> + DecodedExtensions = ssl_handshake:decode_hello_extensions({client, Extensions}), #client_hello{ @@ -226,7 +233,7 @@ decode_handshake(_Version, ?CLIENT_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:3 extensions = DecodedExtensions }; -decode_handshake(Version, Tag, Msg) -> +decode_handshake(Version, Tag, Msg, _) -> ssl_handshake:decode_handshake(Version, Tag, Msg). enc_handshake(#hello_request{}, _Version) -> @@ -278,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. - - diff --git a/lib/ssl/src/tls_handshake.hrl b/lib/ssl/src/tls_handshake.hrl index 5867f9f9ff..f6644f64af 100644 --- a/lib/ssl/src/tls_handshake.hrl +++ b/lib/ssl/src/tls_handshake.hrl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2013-2014. All Rights Reserved. +%% Copyright Ericsson AB 2013-2016. 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. diff --git a/lib/ssl/src/tls_record.hrl b/lib/ssl/src/tls_record.hrl index 3c5cdd3f7a..e296f23673 100644 --- a/lib/ssl/src/tls_record.hrl +++ b/lib/ssl/src/tls_record.hrl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2013-2013. All Rights Reserved. +%% Copyright Ericsson AB 2013-2016. 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. diff --git a/lib/ssl/src/tls_v1.erl b/lib/ssl/src/tls_v1.erl index 543bd33833..711db77708 100644 --- a/lib/ssl/src/tls_v1.erl +++ b/lib/ssl/src/tls_v1.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2015. All Rights Reserved. +%% Copyright Ericsson AB 2007-2016. 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. @@ -209,9 +209,7 @@ suites(Minor) when Minor == 1; Minor == 2 -> ?TLS_DHE_DSS_WITH_AES_128_CBC_SHA, ?TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA, ?TLS_ECDH_RSA_WITH_AES_128_CBC_SHA, - ?TLS_RSA_WITH_AES_128_CBC_SHA, - ?TLS_DHE_RSA_WITH_DES_CBC_SHA, - ?TLS_RSA_WITH_DES_CBC_SHA + ?TLS_RSA_WITH_AES_128_CBC_SHA ]; suites(3) -> [ @@ -300,9 +298,7 @@ default_signature_algs({3, 3} = Version) -> %% SHA {sha, ecdsa}, {sha, rsa}, - {sha, dsa}, - %% MD5 - {md5, rsa}], + {sha, dsa}], signature_algs(Version, Default); default_signature_algs(_) -> undefined. |