diff options
Diffstat (limited to 'lib/ssl/src')
66 files changed, 14772 insertions, 7299 deletions
diff --git a/lib/ssl/src/Makefile b/lib/ssl/src/Makefile index 0c00a650b9..8d1341f594 100644 --- a/lib/ssl/src/Makefile +++ b/lib/ssl/src/Makefile @@ -1,18 +1,19 @@ # # %CopyrightBegin% # -# Copyright Ericsson AB 1999-2014. All Rights Reserved. +# Copyright Ericsson AB 1999-2018. All Rights Reserved. # -# The contents of this file are subject to the Erlang Public License, -# Version 1.1, (the "License"); you may not use this file except in -# compliance with the License. You should have received a copy of the -# Erlang Public License along with this software. If not, it can be -# retrieved online at http://www.erlang.org/. +# 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 # -# Software distributed under the License is distributed on an "AS IS" -# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -# the License for the specific language governing rights and limitations -# under the License. +# 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% # @@ -38,43 +39,54 @@ RELSYSDIR = $(RELEASE_PATH)/lib/ssl-$(VSN) # ---------------------------------------------------- BEHAVIOUR_MODULES= \ - ssl_session_cache_api + ssl_session_cache_api \ + ssl_crl_cache_api MODULES= \ ssl \ - tls \ - dtls \ ssl_alert \ ssl_app \ - ssl_dist_sup\ ssl_sup \ + ssl_admin_sup\ + tls_connection_sup \ + ssl_connection_sup \ + ssl_listen_tracker_sup\ + dtls_connection_sup \ + dtls_packet_demux \ + dtls_listener_sup \ + ssl_dist_sup\ + ssl_dist_admin_sup\ + ssl_dist_connection_sup\ inet_tls_dist \ + inet6_tls_dist \ ssl_certificate\ ssl_pkix_db\ ssl_cipher \ + ssl_cipher_format \ ssl_srp_primes \ tls_connection \ dtls_connection \ + tls_sender\ ssl_config \ ssl_connection \ - tls_connection_sup \ - dtls_connection_sup \ tls_handshake \ dtls_handshake\ ssl_handshake\ ssl_manager \ ssl_session \ ssl_session_cache \ - ssl_socket \ - ssl_listen_tracker_sup \ + ssl_pem_cache \ + ssl_crl\ + ssl_crl_cache \ + ssl_crl_hash_dir \ + tls_socket \ + dtls_socket \ tls_record \ dtls_record \ ssl_record \ - ssl_v2 \ ssl_v3 \ tls_v1 \ - dtls_v1 \ - ssl_tls_dist_proxy + dtls_v1 INTERNAL_HRL_FILES = \ ssl_alert.hrl ssl_cipher.hrl \ @@ -164,5 +176,5 @@ $(EBIN)/ssl_session_cache.$(EMULATOR): ssl_internal.hrl ssl_handshake.hrl $(EBIN)/ssl_session_cache_api.$(EMULATOR): ssl_internal.hrl ssl_handshake.hrl $(EBIN)/ssl_ssl3.$(EMULATOR): ssl_internal.hrl ssl_record.hrl ssl_cipher.hrl $(EBIN)/ssl_tls1.$(EMULATOR): ssl_internal.hrl ssl_record.hrl ssl_cipher.hrl - +$(EBIN)/ssl_cache.$(EMULATOR): ssl_cache.erl ssl_internal.hrl ../../public_key/include/public_key.hrl diff --git a/lib/ssl/src/dtls.erl b/lib/ssl/src/dtls.erl deleted file mode 100644 index 780bddeb10..0000000000 --- a/lib/ssl/src/dtls.erl +++ /dev/null @@ -1,112 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 1999-2013. All Rights Reserved. -%% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. -%% -%% %CopyrightEnd% -%% - -%% - -%%% Purpose : Reflect DTLS specific API options (fairly simple wrapper at the moment) -%% First implementation will support DTLS connections only in a "TLS/TCP like way" - --module(dtls). - --include("ssl_api.hrl"). --include("ssl_internal.hrl"). - --export([connect/2, connect/3, listen/2, accept/1, accept/2, - handshake/1, handshake/2, handshake/3]). - -%%-------------------------------------------------------------------- -%% -%% Description: Connect to a DTLS server. -%%-------------------------------------------------------------------- - --spec connect(host() | port(), [connect_option()]) -> {ok, #sslsocket{}} | - {error, reason()}. - -connect(Socket, Options) when is_port(Socket) -> - connect(Socket, Options, infinity). - --spec connect(host() | port(), [connect_option()] | inet:port_number(), - timeout() | list()) -> - {ok, #sslsocket{}} | {error, reason()}. - -connect(Socket, SslOptions, Timeout) when is_port(Socket) -> - DTLSOpts = [{protocol, dtls} | SslOptions], - ssl:connect(Socket, DTLSOpts, Timeout); -connect(Host, Port, Options) -> - connect(Host, Port, Options, infinity). - --spec connect(host() | port(), inet:port_number(), list(), timeout()) -> - {ok, #sslsocket{}} | {error, reason()}. - -connect(Host, Port, Options, Timeout) -> - DTLSOpts = [{protocol, dtls} | Options], - ssl:connect(Host, Port, DTLSOpts, Timeout). - -%%-------------------------------------------------------------------- --spec listen(inet:port_number(), [listen_option()]) ->{ok, #sslsocket{}} | {error, reason()}. - -%% -%% Description: Creates an ssl listen socket. -%%-------------------------------------------------------------------- -listen(Port, Options) -> - DTLSOpts = [{protocol, dtls} | Options], - ssl:listen(Port, DTLSOpts). - -%%-------------------------------------------------------------------- -%% -%% Description: Performs transport accept on an ssl listen socket -%%-------------------------------------------------------------------- --spec accept(#sslsocket{}) -> {ok, #sslsocket{}} | - {error, reason()}. -accept(ListenSocket) -> - accept(ListenSocket, infinity). - --spec accept(#sslsocket{}, timeout()) -> {ok, #sslsocket{}} | - {error, reason()}. -accept(Socket, Timeout) -> - ssl:transport_accept(Socket, Timeout). - -%%-------------------------------------------------------------------- -%% -%% Description: Performs accept on an ssl listen socket. e.i. performs -%% ssl handshake. -%%-------------------------------------------------------------------- - --spec handshake(#sslsocket{}) -> ok | {error, reason()}. - -handshake(ListenSocket) -> - handshake(ListenSocket, infinity). - - --spec handshake(#sslsocket{} | port(), timeout()| [ssl_option() - | transport_option()]) -> - ok | {ok, #sslsocket{}} | {error, reason()}. - -handshake(#sslsocket{} = Socket, Timeout) -> - ssl:ssl_accept(Socket, Timeout); - -handshake(ListenSocket, SslOptions) when is_port(ListenSocket) -> - handshake(ListenSocket, SslOptions, infinity). - - --spec handshake(port(), [ssl_option()| transport_option()], timeout()) -> - {ok, #sslsocket{}} | {error, reason()}. - -handshake(Socket, SslOptions, Timeout) when is_port(Socket) -> - ssl:ssl_accept(Socket, SslOptions, Timeout). diff --git a/lib/ssl/src/dtls_connection.erl b/lib/ssl/src/dtls_connection.erl index 508983ddac..2583667fa2 100644 --- a/lib/ssl/src/dtls_connection.erl +++ b/lib/ssl/src/dtls_connection.erl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2013-2014. All Rights Reserved. +%% Copyright Ericsson AB 2013-2018. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. +%% 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 %% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% 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% %% @@ -20,7 +21,7 @@ %% Internal application API --behaviour(gen_fsm). +-behaviour(gen_statem). -include("dtls_connection.hrl"). -include("dtls_handshake.hrl"). @@ -35,455 +36,721 @@ %% Internal application API %% Setup --export([start_fsm/8]). +-export([start_fsm/8, start_link/7, init/1, pids/1]). %% State transition handling --export([next_record/1, next_state/4%, - %%next_state_connection/2 - ]). +-export([next_event/3, next_event/4, handle_protocol_record/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/1, reinit_handshake_data/1, select_sni_extension/1, empty_connection_state/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([encode_alert/3, send_alert/2, send_alert_in_connection/2, close/5, protocol_name/0]). %% Data handling --export([%%write_application_data/3, - read_application_data/2%%, -%% passive_receive/2, next_record_if_active/1 - ]). - -%% Called by tls_connection_sup --export([start_link/7]). +-export([encode_data/3, next_record/1, + send/3, socket/5, setopts/3, getopts/3]). -%% 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]). +%% gen_statem state functions +-export([init/3, error/3, downgrade/3, %% Initiation and take down states + hello/3, user_hello/3, certify/3, cipher/3, abbreviated/3, %% Handshake states + connection/3]). +%% gen_statem callbacks +-export([callback_mode/0, terminate/3, code_change/4, format_status/2]). %%==================================================================== %% Internal application API +%%==================================================================== +%%==================================================================== +%% Setup %%==================================================================== -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 = ssl_connection:handshake(SslSocket, Timeout), - {ok, SslSocket} + {ok, SslSocket} = ssl_connection:socket_control(?MODULE, Socket, [Pid], CbModule, Tracker), + ssl_connection:handshake(SslSocket, Timeout) catch error:{badmatch, {error, _} = Error} -> Error - end; + end. -start_fsm(Role, Host, Port, Socket, {#ssl_options{erl_dist = true},_} = 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 = ssl_connection:handshake(SslSocket, Timeout), - {ok, SslSocket} +%%-------------------------------------------------------------------- +-spec start_link(atom(), host(), inet:port_number(), port(), list(), pid(), tuple()) -> + {ok, pid()} | ignore | {error, reason()}. +%% +%% Description: Creates a gen_statem process which calls Module:init/1 to +%% initialize. +%%-------------------------------------------------------------------- +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, Options, User, CbInfo]) -> + process_flag(trap_exit, true), + State0 = #state{protocol_specific = Map} = 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, [], init, State) catch - error:{badmatch, {error, _} = Error} -> - Error + throw:Error -> + EState = State0#state{protocol_specific = Map#{error => Error}}, + gen_statem:enter_loop(?MODULE, [], error, EState) end. -send_handshake(Handshake, #state{negotiated_version = Version, - tls_handshake_history = Hist0, - connection_states = ConnectionStates0} = State0) -> - {BinHandshake, ConnectionStates, Hist} = - encode_handshake(Handshake, Version, ConnectionStates0, Hist0), - send_flight(BinHandshake, State0#state{connection_states = ConnectionStates, - tls_handshake_history = Hist - }). +pids(_) -> + [self()]. + +%%==================================================================== +%% State transition handling +%%==================================================================== +next_record(#state{unprocessed_handshake_events = N} = State) when N > 0 -> + {no_record, State#state{unprocessed_handshake_events = N-1}}; + +next_record(#state{protocol_buffers = + #protocol_buffers{dtls_cipher_texts = [#ssl_tls{epoch = Epoch} = CT | Rest]} + = Buffers, + connection_states = #{current_read := #{epoch := Epoch}} = ConnectionStates} = State) -> + CurrentRead = dtls_record:get_connection_state_by_epoch(Epoch, ConnectionStates, read), + case dtls_record:replay_detect(CT, CurrentRead) of + false -> + decode_cipher_text(State#state{connection_states = ConnectionStates}) ; + true -> + %% Ignore replayed record + next_record(State#state{protocol_buffers = + Buffers#protocol_buffers{dtls_cipher_texts = Rest}, + connection_states = ConnectionStates}) + end; +next_record(#state{protocol_buffers = + #protocol_buffers{dtls_cipher_texts = [#ssl_tls{epoch = Epoch} | Rest]} + = Buffers, + connection_states = #{current_read := #{epoch := CurrentEpoch}} = ConnectionStates} = State) + when Epoch > CurrentEpoch -> + %% TODO Buffer later Epoch message, drop it for now + next_record(State#state{protocol_buffers = + Buffers#protocol_buffers{dtls_cipher_texts = Rest}, + connection_states = ConnectionStates}); +next_record(#state{protocol_buffers = + #protocol_buffers{dtls_cipher_texts = [ _ | Rest]} + = Buffers, + connection_states = ConnectionStates} = State) -> + %% Drop old epoch message + next_record(State#state{protocol_buffers = + Buffers#protocol_buffers{dtls_cipher_texts = Rest}, + connection_states = ConnectionStates}); +next_record(#state{static_env = #static_env{role = server, + socket = {Listener, {Client, _}}}} = State) -> + dtls_packet_demux:active_once(Listener, Client, self()), + {no_record, State}; +next_record(#state{static_env = #static_env{role = client, + socket = {_Server, Socket} = DTLSSocket, + close_tag = CloseTag, + transport_cb = Transport}} = State) -> + case dtls_socket:setopts(Transport, Socket, [{active,once}]) of + ok -> + {no_record, State}; + _ -> + self() ! {CloseTag, DTLSSocket}, + {no_record, State} + end; +next_record(State) -> + {no_record, State}. + +next_event(StateName, Record, State) -> + next_event(StateName, Record, State, []). + +next_event(StateName, no_record, + #state{connection_states = #{current_read := #{epoch := CurrentEpoch}}} = State0, Actions) -> + case next_record(State0) of + {no_record, State} -> + ssl_connection:hibernate_after(StateName, State, Actions); + {#ssl_tls{epoch = CurrentEpoch, + type = ?HANDSHAKE, + version = Version} = Record, State1} -> + State = dtls_version(StateName, Version, State1), + {next_state, StateName, State, + [{next_event, internal, {protocol_record, Record}} | Actions]}; + {#ssl_tls{epoch = CurrentEpoch} = Record, State} -> + {next_state, StateName, State, [{next_event, internal, {protocol_record, Record}} | Actions]}; + {#ssl_tls{epoch = Epoch, + type = ?HANDSHAKE, + version = _Version}, State1} = _Record when Epoch == CurrentEpoch-1 -> + {State, MoreActions} = send_handshake_flight(State1, CurrentEpoch), + next_event(StateName, no_record, State, Actions ++ MoreActions); + %% From FLIGHT perspective CHANGE_CIPHER_SPEC is treated as a handshake + {#ssl_tls{epoch = Epoch, + type = ?CHANGE_CIPHER_SPEC, + version = _Version}, State1} = _Record when Epoch == CurrentEpoch-1 -> + {State, MoreActions} = send_handshake_flight(State1, CurrentEpoch), + next_event(StateName, no_record, State, Actions ++ MoreActions); + {#ssl_tls{epoch = _Epoch, + version = _Version}, State} -> + %% TODO maybe buffer later epoch + next_event(StateName, no_record, State, Actions); + {#alert{} = Alert, State} -> + {next_state, StateName, State, [{next_event, internal, Alert} | Actions]} + end; +next_event(connection = StateName, Record, + #state{connection_states = #{current_read := #{epoch := CurrentEpoch}}} = State0, Actions) -> + case Record of + #ssl_tls{epoch = CurrentEpoch, + type = ?HANDSHAKE, + version = Version} = Record -> + State = dtls_version(StateName, Version, State0), + {next_state, StateName, State, + [{next_event, internal, {protocol_record, Record}} | Actions]}; + #ssl_tls{epoch = CurrentEpoch} -> + {next_state, StateName, State0, [{next_event, internal, {protocol_record, Record}} | Actions]}; + #ssl_tls{epoch = Epoch, + type = ?HANDSHAKE, + version = _Version} when Epoch == CurrentEpoch-1 -> + {State, MoreActions} = send_handshake_flight(State0, CurrentEpoch), + next_event(StateName, no_record, State, Actions ++ MoreActions); + %% From FLIGHT perspective CHANGE_CIPHER_SPEC is treated as a handshake + #ssl_tls{epoch = Epoch, + type = ?CHANGE_CIPHER_SPEC, + version = _Version} when Epoch == CurrentEpoch-1 -> + {State, MoreActions} = send_handshake_flight(State0, CurrentEpoch), + next_event(StateName, no_record, State, Actions ++ MoreActions); + _ -> + next_event(StateName, no_record, State0, Actions) + end; +next_event(StateName, Record, + #state{connection_states = #{current_read := #{epoch := CurrentEpoch}}} = State0, Actions) -> + case Record of + #ssl_tls{epoch = CurrentEpoch, + version = Version} = Record -> + State = dtls_version(StateName, Version, State0), + {next_state, StateName, State, + [{next_event, internal, {protocol_record, Record}} | Actions]}; + #ssl_tls{epoch = _Epoch, + version = _Version} = _Record -> + %% TODO maybe buffer later epoch + next_event(StateName, no_record, State0, Actions); + #alert{} = Alert -> + {next_state, StateName, State0, [{next_event, internal, Alert} | Actions]} + end. + +%%% DTLS record protocol level application data messages + +handle_protocol_record(#ssl_tls{type = ?APPLICATION_DATA, fragment = Data}, StateName0, State0) -> + case ssl_connection:read_application_data(Data, State0) of + {stop, _, _} = Stop-> + Stop; + {Record, State1} -> + {next_state, StateName, State, Actions} = next_event(StateName0, Record, State1), + ssl_connection:hibernate_after(StateName, State, Actions) + end; +%%% DTLS record protocol level handshake messages +handle_protocol_record(#ssl_tls{type = ?HANDSHAKE, + fragment = Data}, + StateName, + #state{protocol_buffers = Buffers0, + negotiated_version = Version} = State0) -> + try + case dtls_handshake:get_dtls_handshake(Version, Data, Buffers0) of + {[], Buffers} -> + next_event(StateName, no_record, State0#state{protocol_buffers = Buffers}); + {Packets, Buffers} -> + State = State0#state{protocol_buffers = Buffers}, + Events = dtls_handshake_events(Packets), + {next_state, StateName, + State#state{unprocessed_handshake_events = unprocessed_events(Events)}, Events} + end + catch throw:#alert{} = Alert -> + handle_own_alert(Alert, Version, StateName, State0) + end; +%%% DTLS record protocol level change cipher messages +handle_protocol_record(#ssl_tls{type = ?CHANGE_CIPHER_SPEC, fragment = Data}, StateName, State) -> + {next_state, StateName, State, [{next_event, internal, #change_cipher_spec{type = Data}}]}; +%%% DTLS record protocol level Alert messages +handle_protocol_record(#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_protocol_record(#ssl_tls{type = _Unknown}, StateName, State) -> + {next_state, StateName, State, []}. + +%%==================================================================== +%% Handshake handling +%%==================================================================== + +renegotiate(#state{static_env = #static_env{role = client}} = State, Actions) -> + %% Handle same way as if server requested + %% the renegotiation + {next_state, connection, State, + [{next_event, internal, #hello_request{}} | Actions]}; + +renegotiate(#state{static_env = #static_env{role = server}} = State0, Actions) -> + HelloRequest = ssl_handshake:hello_request(), + State1 = prepare_flight(State0), + {State, MoreActions} = send_handshake(HelloRequest, State1), + next_event(hello, no_record, State, Actions ++ MoreActions). + +send_handshake(Handshake, #state{connection_states = ConnectionStates} = State) -> + #{epoch := Epoch} = ssl_record:current_connection_state(ConnectionStates, write), + send_handshake_flight(queue_handshake(Handshake, State), Epoch). + +queue_handshake(Handshake0, #state{tls_handshake_history = Hist0, + negotiated_version = Version, + flight_buffer = #{handshakes := HsBuffer0, + change_cipher_spec := undefined, + next_sequence := Seq} = Flight0} = State) -> + Handshake = dtls_handshake:encode_handshake(Handshake0, Version, Seq), + Hist = update_handshake_history(Handshake0, Handshake, Hist0), + State#state{flight_buffer = Flight0#{handshakes => [Handshake | HsBuffer0], + next_sequence => Seq +1}, + tls_handshake_history = Hist}; + +queue_handshake(Handshake0, #state{tls_handshake_history = Hist0, + negotiated_version = Version, + flight_buffer = #{handshakes_after_change_cipher_spec := Buffer0, + next_sequence := Seq} = Flight0} = State) -> + Handshake = dtls_handshake:encode_handshake(Handshake0, Version, Seq), + Hist = update_handshake_history(Handshake0, Handshake, Hist0), + State#state{flight_buffer = Flight0#{handshakes_after_change_cipher_spec => [Handshake | Buffer0], + next_sequence => Seq +1}, + tls_handshake_history = Hist}. + +queue_change_cipher(ChangeCipher, #state{flight_buffer = Flight, + connection_states = ConnectionStates0} = State) -> + ConnectionStates = + dtls_record:next_epoch(ConnectionStates0, write), + State#state{flight_buffer = Flight#{change_cipher_spec => ChangeCipher}, + connection_states = ConnectionStates}. + +reinit(State) -> + %% To be API compatible with TLS NOOP here + reinit_handshake_data(State). +reinit_handshake_data(#state{protocol_buffers = Buffers} = State) -> + State#state{premaster_secret = undefined, + public_key_info = undefined, + tls_handshake_history = ssl_handshake:init_handshake_history(), + flight_state = {retransmit, ?INITIAL_RETRANSMIT_TIMEOUT}, + flight_buffer = new_flight(), + protocol_buffers = + Buffers#protocol_buffers{ + dtls_handshake_next_seq = 0, + dtls_handshake_next_fragments = [], + dtls_handshake_later_fragments = [] + }}. + +select_sni_extension(#client_hello{extensions = HelloExtensions}) -> + HelloExtensions#hello_extensions.sni; +select_sni_extension(_) -> + undefined. + +empty_connection_state(ConnectionEnd, BeastMitigation) -> + Empty = ssl_record:empty_connection_state(ConnectionEnd, BeastMitigation), + dtls_record:empty_connection_state(Empty). + +%%==================================================================== +%% Alert and close handling +%%==================================================================== +encode_alert(#alert{} = Alert, Version, ConnectionStates) -> + dtls_record:encode_alert_record(Alert, Version, ConnectionStates). send_alert(Alert, #state{negotiated_version = Version, - socket = Socket, - transport_cb = Transport, + static_env = #static_env{socket = Socket, + transport_cb = Transport}, connection_states = ConnectionStates0} = State0) -> {BinMsg, ConnectionStates} = - ssl_alert:encode(Alert, Version, ConnectionStates0), - Transport:send(Socket, BinMsg), + encode_alert(Alert, Version, ConnectionStates0), + send(Transport, Socket, BinMsg), State0#state{connection_states = ConnectionStates}. -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}. +send_alert_in_connection(Alert, State) -> + _ = send_alert(Alert, State), + ok. +close(downgrade, _,_,_,_) -> + ok; +%% Other +close(_, Socket, Transport, _,_) -> + dtls_socket:close(Transport,Socket). + +protocol_name() -> + "DTLS". + %%==================================================================== -%% tls_connection_sup API -%%==================================================================== +%% Data handling +%%==================================================================== -%%-------------------------------------------------------------------- --spec start_link(atom(), host(), inet:port_number(), port(), list(), pid(), tuple()) -> - {ok, pid()} | ignore | {error, reason()}. -%% -%% Description: Creates a gen_fsm process which calls Module:init/1 to -%% initialize. To ensure a synchronized start-up procedure, this function -%% does not return until Module:init/1 has returned. -%%-------------------------------------------------------------------- -start_link(Role, Host, Port, Socket, Options, User, CbInfo) -> - {ok, proc_lib:spawn_link(?MODULE, init, [[Role, Host, Port, Socket, Options, User, CbInfo]])}. +encode_data(Data, Version, ConnectionStates0)-> + dtls_record:encode_data(Data, Version, ConnectionStates0). -init([Role, Host, Port, Socket, {SSLOpts0, _} = 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 = calendar:datetime_to_gregorian_seconds({date(), time()}), - try ssl_config:init(SSLOpts0, Role) of - {ok, Ref, CertDbHandle, FileRefHandle, CacheHandle, 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, - session_cache = CacheHandle, - private_key = Key, - diffie_hellman_params = DHParams}, - gen_fsm:enter_loop(?MODULE, [], hello, State, get_timeout(State)) - catch - throw:Error -> - gen_fsm:enter_loop(?MODULE, [], error, {Error,State0}, get_timeout(State0)) - end. +send(Transport, {_, {{_,_}, _} = Socket}, Data) -> + send(Transport, Socket, Data); +send(Transport, Socket, Data) -> + dtls_socket:send(Transport, Socket, Data). + +socket(Pid, Transport, Socket, Connection, _) -> + dtls_socket:socket(Pid, Transport, Socket, Connection). + +setopts(Transport, Socket, Other) -> + dtls_socket:setopts(Transport, Socket, Other). + +getopts(Transport, Socket, Tag) -> + dtls_socket:getopts(Transport, Socket, Tag). %%-------------------------------------------------------------------- -%% 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(enter, _, State) -> + {keep_state, State}; +init({call, From}, {start, Timeout}, + #state{static_env = #static_env{host = Host, + port = Port, + role = client, + session_cache = Cache, + session_cache_cb = CacheCb}, + ssl_options = SslOpts, + session = #session{own_certificate = Cert} = Session0, + connection_states = ConnectionStates0, + renegotiation = {Renegotiation, _} + } = State0) -> + Timer = ssl_connection:start_or_recv_cancel_timer(Timeout, From), Hello = dtls_handshake:client_hello(Host, Port, ConnectionStates0, SslOpts, Cache, CacheCb, Renegotiation, Cert), - + Version = Hello#client_hello.client_version, - Handshake0 = ssl_handshake:init_handshake_history(), - {BinMsg, ConnectionStates, Handshake} = - encode_handshake(Hello, Version, ConnectionStates0, Handshake0), - Transport:send(Socket, BinMsg), - State1 = State0#state{connection_states = ConnectionStates, - negotiated_version = Version, %% Requested version + HelloVersion = dtls_record:hello_version(Version, SslOpts#ssl_options.versions), + State1 = prepare_flight(State0#state{negotiated_version = Version}), + {State2, Actions} = send_handshake(Hello, State1#state{negotiated_version = HelloVersion}), + State3 = State2#state{negotiated_version = Version, %% Requested version session = Session0#session{session_id = Hello#client_hello.session_id}, - tls_handshake_history = Handshake}, - {Record, State} = next_record(State1), - next_state(hello, hello, Record, State); - -hello(Hello = #client_hello{client_version = ClientVersion, - extensions = #hello_extensions{hash_signs = HashSigns}}, - State = #state{connection_states = ConnectionStates0, - port = Port, session = #session{own_certificate = Cert} = Session0, - renegotiation = {Renegotiation, _}, - session_cache = Cache, - session_cache_cb = CacheCb, - 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_handshake:select_hashsign(HashSigns, Cert, - dtls_v1:corresponding_tls_version(Version)), - ssl_connection:hello({common_client_hello, Type, ServerHelloExt, HashSign}, - State#state{connection_states = ConnectionStates, - negotiated_version = Version, - session = Session, - client_ecc = {EllipticCurves, EcPointFormats}}, ?MODULE); - #alert{} = Alert -> - handle_own_alert(Alert, ClientVersion, hello, State) + start_or_recv_from = From, + timer = Timer, + flight_state = {retransmit, ?INITIAL_RETRANSMIT_TIMEOUT} + }, + {Record, State} = next_record(State3), + next_event(hello, Record, State, Actions); +init({call, _} = Type, Event, #state{static_env = #static_env{role = server, + data_tag = udp}} = State) -> + Result = gen_handshake(?FUNCTION_NAME, Type, Event, + State#state{flight_state = {retransmit, ?INITIAL_RETRANSMIT_TIMEOUT}, + protocol_specific = #{current_cookie_secret => dtls_v1:cookie_secret(), + previous_cookie_secret => <<>>, + ignored_alerts => 0, + max_ignored_alerts => 10}}), + erlang:send_after(dtls_v1:cookie_timeout(), self(), new_cookie_secret), + Result; +init({call, _} = Type, Event, #state{static_env = #static_env{role = server}} = State) -> + %% I.E. DTLS over sctp + gen_handshake(?FUNCTION_NAME, Type, Event, State#state{flight_state = reliable}); +init(Type, Event, State) -> + gen_handshake(?FUNCTION_NAME, Type, Event, State). + +%%-------------------------------------------------------------------- +-spec error(gen_statem:event_type(), + {start, timeout()} | term(), #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +error(enter, _, State) -> + {keep_state, State}; +error({call, From}, {start, _Timeout}, + #state{protocol_specific = #{error := Error}} = State) -> + {stop_and_reply, {shutdown, normal}, + [{reply, From, {error, Error}}], State}; +error({call, _} = Call, Msg, State) -> + gen_handshake(?FUNCTION_NAME, Call, Msg, 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(enter, _, #state{static_env = #static_env{role = server}} = State) -> + {keep_state, State}; +hello(enter, _, #state{static_env = #static_env{role = client}} = State0) -> + {State, Actions} = handle_flight_timer(State0), + {keep_state, State, Actions}; +hello(internal, #client_hello{cookie = <<>>, + client_version = Version} = Hello, + #state{static_env = #static_env{role = server, + transport_cb = Transport, + socket = Socket}, + protocol_specific = #{current_cookie_secret := Secret}} = State0) -> + {ok, {IP, Port}} = dtls_socket:peername(Transport, Socket), + Cookie = dtls_handshake:cookie(Secret, IP, Port, Hello), + %% FROM RFC 6347 regarding HelloVerifyRequest message: + %% The server_version field has the same syntax as in TLS. However, in + %% order to avoid the requirement to do version negotiation in the + %% initial handshake, DTLS 1.2 server implementations SHOULD use DTLS + %% version 1.0 regardless of the version of TLS that is expected to be + %% negotiated. + VerifyRequest = dtls_handshake:hello_verify_request(Cookie, ?HELLO_VERIFY_REQUEST_VERSION), + State1 = prepare_flight(State0#state{negotiated_version = Version}), + {State2, Actions} = send_handshake(VerifyRequest, State1), + {Record, State} = next_record(State2), + next_event(?FUNCTION_NAME, Record, State#state{tls_handshake_history = ssl_handshake:init_handshake_history()}, Actions); +hello(internal, #hello_verify_request{cookie = Cookie}, #state{static_env = #static_env{role = client, + host = Host, + port = Port, + session_cache = Cache, + session_cache_cb = CacheCb}, + ssl_options = SslOpts, + session = #session{own_certificate = OwnCert} + = Session0, + connection_states = ConnectionStates0, + renegotiation = {Renegotiation, _} + } = State0) -> + + Hello = dtls_handshake:client_hello(Host, Port, Cookie, ConnectionStates0, + SslOpts, + Cache, CacheCb, Renegotiation, OwnCert), + Version = Hello#client_hello.client_version, + State1 = prepare_flight(State0#state{tls_handshake_history = ssl_handshake:init_handshake_history()}), + + {State2, Actions} = send_handshake(Hello, State1), + State = State2#state{negotiated_version = Version, %% Requested version + session = + Session0#session{session_id = + Hello#client_hello.session_id}}, + next_event(?FUNCTION_NAME, no_record, State, Actions); +hello(internal, #client_hello{extensions = Extensions} = Hello, + #state{ssl_options = #ssl_options{handshake = hello}, + start_or_recv_from = From} = State) -> + {next_state, user_hello, State#state{start_or_recv_from = undefined, + hello = Hello}, + [{reply, From, {ok, ssl_connection:map_extensions(Extensions)}}]}; +hello(internal, #server_hello{extensions = Extensions} = Hello, + #state{ssl_options = #ssl_options{handshake = hello}, + start_or_recv_from = From} = State) -> + {next_state, user_hello, State#state{start_or_recv_from = undefined, + hello = Hello}, + [{reply, From, {ok, ssl_connection:map_extensions(Extensions)}}]}; + +hello(internal, #client_hello{cookie = Cookie} = Hello, #state{static_env = #static_env{role = server, + transport_cb = Transport, + socket = Socket}, + protocol_specific = #{current_cookie_secret := Secret, + previous_cookie_secret := PSecret} + } = State0) -> + {ok, {IP, Port}} = dtls_socket:peername(Transport, Socket), + case dtls_handshake:cookie(Secret, IP, Port, Hello) of + Cookie -> + handle_client_hello(Hello, State0); + _ -> + case dtls_handshake:cookie(PSecret, IP, Port, Hello) of + Cookie -> + handle_client_hello(Hello, State0); + _ -> + %% Handle bad cookie as new cookie request RFC 6347 4.1.2 + hello(internal, Hello#client_hello{cookie = <<>>}, State0) + end end; -hello(Hello, - #state{connection_states = ConnectionStates0, - negotiated_version = ReqVersion, - role = client, - renegotiation = {Renegotiation, _}, - ssl_options = SslOptions} = State) -> +hello(internal, #server_hello{} = Hello, + #state{ + static_env = #static_env{role = client}, + connection_states = ConnectionStates0, + negotiated_version = ReqVersion, + renegotiation = {Renegotiation, _}, + ssl_options = SslOptions} = State) -> case dtls_handshake:hello(Hello, SslOptions, ConnectionStates0, Renegotiation) of #alert{} = Alert -> - handle_own_alert(Alert, ReqVersion, hello, State); - {Version, NewId, ConnectionStates, NextProtocol} -> + handle_own_alert(Alert, ReqVersion, ?FUNCTION_NAME, State); + {Version, NewId, ConnectionStates, ProtoExt, Protocol} -> ssl_connection:handle_session(Hello, - Version, NewId, ConnectionStates, NextProtocol, State) + Version, NewId, ConnectionStates, ProtoExt, Protocol, State) end; +hello(internal, {handshake, {#client_hello{cookie = <<>>} = Handshake, _}}, State) -> + %% Initial hello should not be in handshake history + {next_state, ?FUNCTION_NAME, State, [{next_event, internal, Handshake}]}; +hello(internal, {handshake, {#hello_verify_request{} = Handshake, _}}, State) -> + %% hello_verify should not be in handshake history + {next_state, ?FUNCTION_NAME, State, [{next_event, internal, Handshake}]}; +hello(internal, #change_cipher_spec{type = <<1>>}, State0) -> + {State1, Actions0} = send_handshake_flight(State0, retransmit_epoch(?FUNCTION_NAME, State0)), + {next_state, ?FUNCTION_NAME, State, Actions} = next_event(?FUNCTION_NAME, no_record, State1, Actions0), + %% This will reset the retransmission timer by repeating the enter state event + {repeat_state, State, Actions}; +hello(info, Event, State) -> + gen_info(Event, ?FUNCTION_NAME, State); +hello(state_timeout, Event, State) -> + handle_state_timeout(Event, ?FUNCTION_NAME, State); +hello(Type, Event, State) -> + gen_handshake(?FUNCTION_NAME, Type, Event, State). -hello(Msg, State) -> - ssl_connection:hello(Msg, State, ?MODULE). +user_hello(enter, _, State) -> + {keep_state, State}; +user_hello(Type, Event, State) -> + gen_handshake(?FUNCTION_NAME, Type, Event, State). -abbreviated(Msg, State) -> - ssl_connection:abbreviated(Msg, State, ?MODULE). +%%-------------------------------------------------------------------- +-spec abbreviated(gen_statem:event_type(), term(), #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +abbreviated(enter, _, State0) -> + {State, Actions} = handle_flight_timer(State0), + {keep_state, State, Actions}; +abbreviated(info, Event, State) -> + gen_info(Event, ?FUNCTION_NAME, State); +abbreviated(internal = Type, + #change_cipher_spec{type = <<1>>} = Event, + #state{connection_states = ConnectionStates0} = State) -> + ConnectionStates1 = dtls_record:save_current_connection_state(ConnectionStates0, read), + ConnectionStates = dtls_record:next_epoch(ConnectionStates1, read), + gen_handshake(?FUNCTION_NAME, Type, Event, State#state{connection_states = ConnectionStates}); +abbreviated(internal = Type, #finished{} = Event, #state{connection_states = ConnectionStates} = State) -> + gen_handshake(?FUNCTION_NAME, Type, Event, + prepare_flight(State#state{connection_states = ConnectionStates, + flight_state = connection})); +abbreviated(state_timeout, Event, State) -> + handle_state_timeout(Event, ?FUNCTION_NAME, State); +abbreviated(Type, Event, State) -> + gen_handshake(?FUNCTION_NAME, Type, Event, State). +%%-------------------------------------------------------------------- +-spec certify(gen_statem:event_type(), term(), #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +certify(enter, _, State0) -> + {State, Actions} = handle_flight_timer(State0), + {keep_state, State, Actions}; +certify(info, Event, State) -> + gen_info(Event, ?FUNCTION_NAME, State); +certify(internal = Type, #server_hello_done{} = Event, State) -> + ssl_connection:certify(Type, Event, prepare_flight(State), ?MODULE); +certify(internal, #change_cipher_spec{type = <<1>>}, State0) -> + {State1, Actions0} = send_handshake_flight(State0, retransmit_epoch(?FUNCTION_NAME, State0)), + {next_state, ?FUNCTION_NAME, State, Actions} = next_event(?FUNCTION_NAME, no_record, State1, Actions0), + %% This will reset the retransmission timer by repeating the enter state event + {repeat_state, State, Actions}; +certify(state_timeout, Event, State) -> + handle_state_timeout(Event, ?FUNCTION_NAME, State); +certify(Type, Event, State) -> + gen_handshake(?FUNCTION_NAME, Type, Event, State). -certify(Msg, State) -> - ssl_connection:certify(Msg, State, ?MODULE). +%%-------------------------------------------------------------------- +-spec cipher(gen_statem:event_type(), term(), #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +cipher(enter, _, State0) -> + {State, Actions} = handle_flight_timer(State0), + {keep_state, State, Actions}; +cipher(info, Event, State) -> + gen_info(Event, ?FUNCTION_NAME, State); +cipher(internal = Type, #change_cipher_spec{type = <<1>>} = Event, + #state{connection_states = ConnectionStates0} = State) -> + ConnectionStates1 = dtls_record:save_current_connection_state(ConnectionStates0, read), + ConnectionStates = dtls_record:next_epoch(ConnectionStates1, read), + ssl_connection:?FUNCTION_NAME(Type, Event, State#state{connection_states = ConnectionStates}, ?MODULE); +cipher(internal = Type, #finished{} = Event, #state{connection_states = ConnectionStates} = State) -> + ssl_connection:?FUNCTION_NAME(Type, Event, + prepare_flight(State#state{connection_states = ConnectionStates, + flight_state = connection}), + ?MODULE); +cipher(state_timeout, Event, State) -> + handle_state_timeout(Event, ?FUNCTION_NAME, State); +cipher(Type, Event, State) -> + ssl_connection:?FUNCTION_NAME(Type, Event, State, ?MODULE). -cipher(Msg, State) -> - ssl_connection:cipher(Msg, State, ?MODULE). +%%-------------------------------------------------------------------- +-spec connection(gen_statem:event_type(), + #hello_request{} | #client_hello{}| term(), #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +connection(enter, _, State) -> + {keep_state, State}; +connection(info, Event, State) -> + gen_info(Event, ?FUNCTION_NAME, State); +connection(internal, #hello_request{}, #state{static_env = #static_env{host = Host, + port = Port, + session_cache = Cache, + session_cache_cb = CacheCb + }, + session = #session{own_certificate = Cert} = Session0, -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) -> + ssl_options = SslOpts, + connection_states = ConnectionStates0, + 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, + Version = Hello#client_hello.client_version, + HelloVersion = dtls_record:hello_version(Version, SslOpts#ssl_options.versions), + State1 = prepare_flight(State0), + {State2, Actions} = send_handshake(Hello, State1#state{negotiated_version = HelloVersion}), {Record, State} = next_record( - State1#state{session = Session0#session{session_id + State2#state{flight_state = {retransmit, ?INITIAL_RETRANSMIT_TIMEOUT}, + 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, Actions); +connection(internal, #client_hello{} = Hello, #state{static_env = #static_env{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}); - -connection(#client_hello{}, #state{role = server, allow_renegotiate = false} = State0) -> + {next_state, hello, State#state{allow_renegotiate = false, renegotiation = {true, peer}}, + [{next_event, internal, Hello}]}; +connection(internal, #client_hello{}, #state{static_env = #static_env{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(?FUNCTION_NAME, Record, State); +connection({call, From}, {application_data, Data}, State) -> + try + send_application_data(Data, From, ?FUNCTION_NAME, State) + catch throw:Error -> + ssl_connection:hibernate_after(?FUNCTION_NAME, State, [{reply, From, Error}]) + end; +connection(Type, Event, State) -> + ssl_connection:?FUNCTION_NAME(Type, Event, State, ?MODULE). +%%TODO does this make sense for DTLS ? %%-------------------------------------------------------------------- -%% 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(enter, _, State) -> + {keep_state, State}; +downgrade(Type, Event, State) -> + ssl_connection:?FUNCTION_NAME(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). +%% gen_statem callbacks %%-------------------------------------------------------------------- +callback_mode() -> + [state_functions, state_enter]. -%% 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); - -handle_info({CloseTag, Socket}, StateName, - #state{socket = Socket, close_tag = CloseTag, - negotiated_version = _Version} = State) -> - handle_normal_shutdown(?ALERT_REC(?FATAL, ?CLOSE_NOTIFY), StateName, State), - {stop, {shutdown, transport_closed}, State}; - -handle_info(Msg, StateName, State) -> - ssl_connection:handle_info(Msg, 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. -%%-------------------------------------------------------------------- terminate(Reason, StateName, State) -> ssl_connection:terminate(Reason, StateName, State). -%%-------------------------------------------------------------------- -%% code_change(OldVsn, StateName, State, Extra) -> {ok, StateName, NewState} -%% Description: Convert process state when code is changed -%%-------------------------------------------------------------------- code_change(_OldVsn, StateName, State, _Extra) -> {ok, 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), - Hist = ssl_handshake:update_handshake_history(Hist0, EncHandshake), - {Encoded, ConnectionStates} = - dtls_record:encode_handshake(FragmentedHandshake, - Version, ConnectionStates0), - {Encoded, ConnectionStates, Hist}. - -next_record(#state{%%flight = #flight{state = finished}, - protocol_buffers = - #protocol_buffers{dtls_packets = [], dtls_cipher_texts = [CT | Rest]} - = Buffers, - connection_states = ConnStates0} = State) -> - case dtls_record:decode_cipher_text(CT, ConnStates0) of - {Plain, ConnStates} -> - {Plain, State#state{protocol_buffers = - Buffers#protocol_buffers{dtls_cipher_texts = Rest}, - connection_states = ConnStates}}; - #alert{} = Alert -> - {Alert, State} - end; -next_record(#state{socket = Socket, - transport_cb = Transport} = State) -> %% when FlightState =/= finished - ssl_socket:setopts(Transport, Socket, [{active,once}]), - {no_record, State}; - - -next_record(State) -> - {no_record, State}. - -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) - 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 - end. - - -send_flight(Fragments, #state{transport_cb = Transport, socket = Socket, - protocol_buffers = _PBuffers} = State) -> - Transport:send(Socket, Fragments), - %% Start retransmission - %% State#state{protocol_buffers = - %% (PBuffers#protocol_buffers){ #flight{state = waiting}}}}. - State. - -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). - -initial_state(Role, Host, Port, Socket, {SSLOptions, SocketOptions}, User, +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 = dtls_record:init_connection_states(Role, BeastMitigation), SessionCacheCb = case application:get_env(ssl, session_cb) of {ok, Cb} when is_atom(Cb) -> @@ -493,42 +760,423 @@ initial_state(Role, Host, Port, Socket, {SSLOptions, SocketOptions}, User, end, Monitor = erlang:monitor(process, User), + InitStatEnv = #static_env{ + role = Role, + transport_cb = CbModule, + protocol_cb = ?MODULE, + data_tag = DataTag, + close_tag = CloseTag, + error_tag = ErrorTag, + host = Host, + port = Port, + socket = Socket, + session_cache_cb = SessionCacheCb + }, - #state{socket_options = SocketOptions, + #state{static_env = InitStatEnv, + socket_options = SocketOptions, %% We do not want to save the password in the state so that %% could be written in the clear into error logs. ssl_options = SSLOptions#ssl_options{password = undefined}, session = #session{is_resumable = new}, - transport_cb = CbModule, - data_tag = DataTag, - close_tag = CloseTag, - error_tag = ErrorTag, - role = Role, - host = Host, - port = Port, - socket = Socket, connection_states = ConnectionStates, protocol_buffers = #protocol_buffers{}, user_application = {Monitor, User}, user_data_buffer = <<>>, - session_cache_cb = SessionCacheCb, renegotiation = {false, first}, + allow_renegotiate = SSLOptions#ssl_options.client_renegotiation, start_or_recv_from = undefined, - send_queue = queue:new(), - protocol_cb = ?MODULE + flight_buffer = new_flight(), + flight_state = {retransmit, ?INITIAL_RETRANSMIT_TIMEOUT} }. -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. +next_dtls_record(Data, StateName, #state{protocol_buffers = #protocol_buffers{ + dtls_record_buffer = Buf0, + dtls_cipher_texts = CT0} = Buffers} = State0) -> + case dtls_record:get_dtls_records(Data, + acceptable_record_versions(StateName, State0), + 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. + +acceptable_record_versions(hello, _) -> + [dtls_record:protocol_version(Vsn) || Vsn <- ?ALL_DATAGRAM_SUPPORTED_VERSIONS]; +acceptable_record_versions(_, #state{negotiated_version = Version}) -> + [Version]. + +dtls_handshake_events(Packets) -> + lists:map(fun(Packet) -> + {next_event, internal, {handshake, Packet}} + end, Packets). -next_state_connection(_, State) -> %% Place holder - {next_state, connection, State, get_timeout(State)}. +decode_cipher_text(#state{protocol_buffers = #protocol_buffers{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. + +dtls_version(hello, Version, #state{static_env = #static_env{role = server}} = State) -> + State#state{negotiated_version = Version}; %%Inital version +dtls_version(_,_, State) -> + State. + +handle_client_hello(#client_hello{client_version = ClientVersion} = Hello, + #state{connection_states = ConnectionStates0, + static_env = #static_env{port = Port, + session_cache = Cache, + session_cache_cb = CacheCb}, + session = #session{own_certificate = Cert} = Session0, + renegotiation = {Renegotiation, _}, + + negotiated_protocol = CurrentProtocol, + key_algorithm = KeyExAlg, + ssl_options = SslOpts} = State0) -> + + case dtls_handshake:hello(Hello, SslOpts, {Port, Session0, Cache, CacheCb, + ConnectionStates0, Cert, KeyExAlg}, Renegotiation) of + #alert{} = Alert -> + handle_own_alert(Alert, ClientVersion, hello, State0); + {Version, {Type, Session}, + ConnectionStates, Protocol0, ServerHelloExt, HashSign} -> + Protocol = case Protocol0 of + undefined -> CurrentProtocol; + _ -> Protocol0 + end, + + State = prepare_flight(State0#state{connection_states = ConnectionStates, + negotiated_version = Version, + hashsign_algorithm = HashSign, + client_hello_version = ClientVersion, + session = Session, + negotiated_protocol = Protocol}), + + ssl_connection:hello(internal, {common_client_hello, Type, ServerHelloExt}, + State, ?MODULE) + end. + + +%% raw data from socket, unpack records +handle_info({Protocol, _, _, _, Data}, StateName, + #state{static_env = #static_env{data_tag = Protocol}} = State0) -> + case next_dtls_record(Data, StateName, State0) of + {Record, State} -> + next_event(StateName, Record, State); + #alert{} = Alert -> + ssl_connection:handle_normal_shutdown(Alert, StateName, State0), + {stop, {shutdown, own_alert}, State0} + end; +handle_info({CloseTag, Socket}, StateName, + #state{static_env = #static_env{socket = Socket, + close_tag = CloseTag}, + socket_options = #socket_options{active = Active}, + protocol_buffers = #protocol_buffers{dtls_cipher_texts = CTs}, + 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 (Active == false) andalso (CTs =/= []) of + false -> + 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, + ssl_connection:handle_normal_shutdown(?ALERT_REC(?FATAL, ?CLOSE_NOTIFY), StateName, State), + {stop, {shutdown, transport_closed}, State}; + true -> + %% Fixes non-delivery of final DTLS record in {active, once}. + %% Basically allows the application the opportunity to set {active, once} again + %% and then receive the final message. + next_event(StateName, no_record, State) + end; + +handle_info(new_cookie_secret, StateName, + #state{protocol_specific = #{current_cookie_secret := Secret} = CookieInfo} = State) -> + erlang:send_after(dtls_v1:cookie_timeout(), self(), new_cookie_secret), + {next_state, StateName, State#state{protocol_specific = + CookieInfo#{current_cookie_secret => dtls_v1:cookie_secret(), + previous_cookie_secret => Secret}}}; +handle_info(Msg, StateName, State) -> + ssl_connection:StateName(info, Msg, State, ?MODULE). + +handle_state_timeout(flight_retransmission_timeout, StateName, + #state{flight_state = {retransmit, NextTimeout}} = State0) -> + {State1, Actions0} = send_handshake_flight(State0#state{flight_state = {retransmit, NextTimeout}}, + retransmit_epoch(StateName, State0)), + {next_state, StateName, State, Actions} = next_event(StateName, no_record, State1, Actions0), + %% This will reset the retransmission timer by repeating the enter state event + {repeat_state, State, Actions}. + +handle_alerts([], Result) -> + Result; +handle_alerts(_, {stop, _, _} = Stop) -> + Stop; +handle_alerts([Alert | Alerts], {next_state, StateName, State}) -> + handle_alerts(Alerts, ssl_connection:handle_alert(Alert, StateName, State)); +handle_alerts([Alert | Alerts], {next_state, StateName, State, _Actions}) -> + handle_alerts(Alerts, ssl_connection:handle_alert(Alert, StateName, State)). + +handle_own_alert(Alert, Version, StateName, #state{static_env = #static_env{data_tag = udp, + role = Role}, + ssl_options = Options} = State0) -> + case ignore_alert(Alert, State0) of + {true, State} -> + log_ignore_alert(Options#ssl_options.log_alert, StateName, Alert, Role), + {next_state, StateName, State}; + {false, State} -> + ssl_connection:handle_own_alert(Alert, Version, StateName, State) + end; +handle_own_alert(Alert, Version, StateName, State) -> + ssl_connection:handle_own_alert(Alert, Version, StateName, State). + +encode_handshake_flight(Flight, Version, MaxFragmentSize, Epoch, ConnectionStates) -> + Fragments = lists:map(fun(Handshake) -> + dtls_handshake:fragment_handshake(Handshake, MaxFragmentSize) + end, Flight), + dtls_record:encode_handshake(Fragments, Version, Epoch, ConnectionStates). + +encode_change_cipher(#change_cipher_spec{}, Version, Epoch, ConnectionStates) -> + dtls_record:encode_change_cipher_spec(Version, Epoch, ConnectionStates). + +decode_alerts(Bin) -> + ssl_alert:decode(Bin). + +gen_handshake(StateName, Type, Event, + #state{negotiated_version = Version} = State) -> + try ssl_connection:StateName(Type, Event, State, ?MODULE) of + Result -> + Result + catch + _:_ -> + ssl_connection:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, + malformed_handshake_data), + Version, StateName, State) + end. + +gen_info(Event, connection = StateName, #state{negotiated_version = Version} = State) -> + try handle_info(Event, StateName, State) of + Result -> + Result + catch + _:_ -> + ssl_connection:handle_own_alert(?ALERT_REC(?FATAL, ?INTERNAL_ERROR, + malformed_data), + Version, StateName, State) + end; + +gen_info(Event, StateName, #state{negotiated_version = Version} = State) -> + try handle_info(Event, StateName, State) of + Result -> + Result + catch + _:_ -> + ssl_connection:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, + malformed_handshake_data), + Version, StateName, State) + end. +unprocessed_events(Events) -> + %% The first handshake event will be processed immediately + %% as it is entered first in the event queue and + %% when it is processed there will be length(Events)-1 + %% handshake events left to process before we should + %% process more TLS-records received on the socket. + erlang:length(Events)-1. + +update_handshake_history(#hello_verify_request{}, _, Hist) -> + Hist; +update_handshake_history(_, Handshake, Hist) -> + ssl_handshake:update_handshake_history(Hist, iolist_to_binary(Handshake)). +prepare_flight(#state{flight_buffer = Flight, + connection_states = ConnectionStates0, + protocol_buffers = + #protocol_buffers{} = Buffers} = State) -> + ConnectionStates = dtls_record:save_current_connection_state(ConnectionStates0, write), + State#state{flight_buffer = next_flight(Flight), + connection_states = ConnectionStates, + protocol_buffers = Buffers#protocol_buffers{ + dtls_handshake_next_fragments = [], + dtls_handshake_later_fragments = []}}. +new_flight() -> + #{next_sequence => 0, + handshakes => [], + change_cipher_spec => undefined, + handshakes_after_change_cipher_spec => []}. + +next_flight(Flight) -> + Flight#{handshakes => [], + change_cipher_spec => undefined, + handshakes_after_change_cipher_spec => []}. + +handle_flight_timer(#state{static_env = #static_env{data_tag = udp}, + flight_state = {retransmit, Timeout}} = State) -> + start_retransmision_timer(Timeout, State); +handle_flight_timer(#state{static_env = #static_env{data_tag = udp}, + flight_state = connection} = State) -> + {State, []}; +handle_flight_timer(State) -> + %% No retransmision needed i.e DTLS over SCTP + {State#state{flight_state = reliable}, []}. + +start_retransmision_timer(Timeout, State) -> + {State#state{flight_state = {retransmit, new_timeout(Timeout)}}, + [{state_timeout, Timeout, flight_retransmission_timeout}]}. + +new_timeout(N) when N =< 30 -> + N * 2; +new_timeout(_) -> + 60. + +send_handshake_flight(#state{static_env = #static_env{socket = Socket, + transport_cb = Transport}, + flight_buffer = #{handshakes := Flight, + change_cipher_spec := undefined}, + negotiated_version = Version, + connection_states = ConnectionStates0} = State0, Epoch) -> + %% TODO remove hardcoded Max size + {Encoded, ConnectionStates} = + encode_handshake_flight(lists:reverse(Flight), Version, 1400, Epoch, ConnectionStates0), + send(Transport, Socket, Encoded), + {State0#state{connection_states = ConnectionStates}, []}; + +send_handshake_flight(#state{static_env = #static_env{socket = Socket, + transport_cb = Transport}, + flight_buffer = #{handshakes := [_|_] = Flight0, + change_cipher_spec := ChangeCipher, + handshakes_after_change_cipher_spec := []}, + negotiated_version = Version, + connection_states = ConnectionStates0} = State0, Epoch) -> + {HsBefore, ConnectionStates1} = + encode_handshake_flight(lists:reverse(Flight0), Version, 1400, Epoch, ConnectionStates0), + {EncChangeCipher, ConnectionStates} = encode_change_cipher(ChangeCipher, Version, Epoch, ConnectionStates1), + + send(Transport, Socket, [HsBefore, EncChangeCipher]), + {State0#state{connection_states = ConnectionStates}, []}; + +send_handshake_flight(#state{static_env = #static_env{socket = Socket, + transport_cb = Transport}, + flight_buffer = #{handshakes := [_|_] = Flight0, + change_cipher_spec := ChangeCipher, + handshakes_after_change_cipher_spec := Flight1}, + negotiated_version = Version, + connection_states = ConnectionStates0} = State0, Epoch) -> + {HsBefore, ConnectionStates1} = + encode_handshake_flight(lists:reverse(Flight0), Version, 1400, Epoch-1, ConnectionStates0), + {EncChangeCipher, ConnectionStates2} = + encode_change_cipher(ChangeCipher, Version, Epoch-1, ConnectionStates1), + {HsAfter, ConnectionStates} = + encode_handshake_flight(lists:reverse(Flight1), Version, 1400, Epoch, ConnectionStates2), + send(Transport, Socket, [HsBefore, EncChangeCipher, HsAfter]), + {State0#state{connection_states = ConnectionStates}, []}; + +send_handshake_flight(#state{static_env = #static_env{socket = Socket, + transport_cb = Transport}, + flight_buffer = #{handshakes := [], + change_cipher_spec := ChangeCipher, + handshakes_after_change_cipher_spec := Flight1}, + negotiated_version = Version, + connection_states = ConnectionStates0} = State0, Epoch) -> + {EncChangeCipher, ConnectionStates1} = + encode_change_cipher(ChangeCipher, Version, Epoch-1, ConnectionStates0), + {HsAfter, ConnectionStates} = + encode_handshake_flight(lists:reverse(Flight1), Version, 1400, Epoch, ConnectionStates1), + send(Transport, Socket, [EncChangeCipher, HsAfter]), + {State0#state{connection_states = ConnectionStates}, []}. + +retransmit_epoch(_StateName, #state{connection_states = ConnectionStates}) -> + #{epoch := Epoch} = + ssl_record:current_connection_state(ConnectionStates, write), + Epoch. + +ignore_alert(#alert{level = ?FATAL}, #state{protocol_specific = #{ignored_alerts := N, + max_ignored_alerts := N}} = State) -> + {false, State}; +ignore_alert(#alert{level = ?FATAL} = Alert, + #state{protocol_specific = #{ignored_alerts := N} = PS} = State) -> + case is_ignore_alert(Alert) of + true -> + {true, State#state{protocol_specific = PS#{ignored_alerts => N+1}}}; + false -> + {false, State} + end; +ignore_alert(_, State) -> + {false, State}. + +%% RFC 6347 4.1.2.7. Handling Invalid Records +%% recommends to silently ignore invalid DTLS records when +%% upd is the transport. Note we do not support compression so no need +%% include ?DECOMPRESSION_FAILURE +is_ignore_alert(#alert{description = ?BAD_RECORD_MAC}) -> + true; +is_ignore_alert(#alert{description = ?RECORD_OVERFLOW}) -> + true; +is_ignore_alert(#alert{description = ?DECODE_ERROR}) -> + true; +is_ignore_alert(#alert{description = ?DECRYPT_ERROR}) -> + true; +is_ignore_alert(#alert{description = ?ILLEGAL_PARAMETER}) -> + true; +is_ignore_alert(_) -> + false. + +log_ignore_alert(true, StateName, Alert, Role) -> + Txt = ssl_alert:alert_txt(Alert), + error_logger:format("DTLS over UDP ~p: In state ~p ignored to send ALERT ~s as DoS-attack mitigation \n", + [Role, StateName, Txt]); +log_ignore_alert(false, _, _,_) -> + ok. + +send_application_data(Data, From, _StateName, + #state{static_env = #static_env{socket = Socket, + protocol_cb = Connection, + transport_cb = Transport}, + negotiated_version = Version, + connection_states = ConnectionStates0, + ssl_options = #ssl_options{renegotiate_at = RenegotiateAt}} = State0) -> + + case time_to_renegotiate(Data, ConnectionStates0, RenegotiateAt) of + true -> + renegotiate(State0#state{renegotiation = {true, internal}}, + [{next_event, {call, From}, {application_data, Data}}]); + false -> + {Msgs, ConnectionStates} = + Connection:encode_data(Data, Version, ConnectionStates0), + State = State0#state{connection_states = ConnectionStates}, + case Connection:send(Transport, Socket, Msgs) of + ok -> + ssl_connection:hibernate_after(connection, State, [{reply, From, ok}]); + Result -> + ssl_connection:hibernate_after(connection, State, [{reply, From, Result}]) + end + end. + +time_to_renegotiate(_Data, + #{current_write := #{sequence_number := Num}}, + RenegotiateAt) -> + + %% We could do test: + %% is_time_to_renegotiate((erlang:byte_size(_Data) div + %% ?MAX_PLAIN_TEXT_LENGTH) + 1, RenegotiateAt), but we chose to + %% have a some what lower renegotiateAt and a much cheaper test + is_time_to_renegotiate(Num, RenegotiateAt). -sequence(_) -> - %%TODO real imp - 1. +is_time_to_renegotiate(N, M) when N < M-> + false; +is_time_to_renegotiate(_,_) -> + true. diff --git a/lib/ssl/src/dtls_connection.hrl b/lib/ssl/src/dtls_connection.hrl index 08707dc8de..3dd78235d0 100644 --- a/lib/ssl/src/dtls_connection.hrl +++ b/lib/ssl/src/dtls_connection.hrl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2013-2014. All Rights Reserved. +%% Copyright Ericsson AB 2013-2016. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. +%% 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 %% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% 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% %% @@ -28,19 +29,14 @@ -include("ssl_connection.hrl"). -record(protocol_buffers, { - dtls_packets = [], %%::[binary()], % Not yet handled decode ssl/tls packets. - dtls_record_buffer = <<>>, %%:: binary(), % Buffer of incomplete records - dtls_handshake_buffer = <<>>, %%:: binary(), % Buffer of incomplete handshakes - dtls_cipher_texts = [], %%:: [binary()], - dtls_cipher_texts_next %%:: [binary()] % Received for Epoch not yet active + dtls_record_buffer = <<>>, %% Buffer of incomplete records + dtls_handshake_next_seq = 0, + dtls_flight_last, + dtls_handshake_next_fragments = [], %% Fragments of the next handshake message + dtls_handshake_later_fragments = [], %% Fragments of handsake messages come after the one in next buffer + dtls_cipher_texts = [] %%:: [binary()], }). --record(flight, { - last_retransmit, - last_read_seq, - msl_timer, - state, - buffer % buffer of not yet ACKed TLS records - }). +-define(INITIAL_RETRANSMIT_TIMEOUT, 1000). %1 sec -endif. % -ifdef(dtls_connection). diff --git a/lib/ssl/src/dtls_connection_sup.erl b/lib/ssl/src/dtls_connection_sup.erl index 0b4711cfb4..7d7be5743d 100644 --- a/lib/ssl/src/dtls_connection_sup.erl +++ b/lib/ssl/src/dtls_connection_sup.erl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2014. All Rights Reserved. +%% Copyright Ericsson AB 2007-2016. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% 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% %% @@ -59,7 +60,7 @@ init(_O) -> StartFunc = {dtls_connection, start_link, []}, Restart = temporary, % E.g. should not be restarted Shutdown = 4000, - Modules = [dtls_connection], + Modules = [dtls_connection, ssl_connection], Type = worker, ChildSpec = {Name, StartFunc, Restart, Shutdown, Type, Modules}, diff --git a/lib/ssl/src/dtls_handshake.erl b/lib/ssl/src/dtls_handshake.erl index 31d525b295..3f70eaec8a 100644 --- a/lib/ssl/src/dtls_handshake.erl +++ b/lib/ssl/src/dtls_handshake.erl @@ -1,40 +1,52 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2013-2014. All Rights Reserved. +%% Copyright Ericsson AB 2013-2018. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. +%% 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 %% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% 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% + +%%---------------------------------------------------------------------- +%% Purpose: Help funtions for handling the DTLS (specific parts of) +%%% SSL/TLS/DTLS handshake protocol +%%---------------------------------------------------------------------- -module(dtls_handshake). +-include("dtls_connection.hrl"). -include("dtls_handshake.hrl"). -include("dtls_record.hrl"). -include("ssl_internal.hrl"). -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, - encode_handshake/3]). +%% Handshake handling +-export([client_hello/8, client_hello/9, cookie/4, hello/4, + hello_verify_request/2]). + +%% Handshake encoding +-export([fragment_handshake/2, encode_handshake/3]). + +%% Handshake decodeing +-export([get_dtls_handshake/3]). -type dtls_handshake() :: #client_hello{} | #hello_verify_request{} | ssl_handshake:ssl_handshake(). %%==================================================================== -%% Internal application API +%% Handshake handling %%==================================================================== %%-------------------------------------------------------------------- --spec client_hello(host(), inet:port_number(), #connection_states{}, +-spec client_hello(host(), inet:port_number(), ssl_record:connection_states(), #ssl_options{}, integer(), atom(), boolean(), der_cert()) -> #client_hello{}. %% @@ -47,7 +59,7 @@ client_hello(Host, Port, ConnectionStates, SslOpts, Cache, CacheCb, Renegotiation, OwnCert). %%-------------------------------------------------------------------- --spec client_hello(host(), inet:port_number(), term(), #connection_states{}, +-spec client_hello(host(), inet:port_number(), term(), ssl_record:connection_states(), #ssl_options{}, integer(), atom(), boolean(), der_cert()) -> #client_hello{}. %% @@ -55,22 +67,26 @@ client_hello(Host, Port, ConnectionStates, SslOpts, %%-------------------------------------------------------------------- client_hello(Host, Port, Cookie, ConnectionStates, #ssl_options{versions = Versions, - ciphers = UserSuites + ciphers = UserSuites, + fallback = Fallback } = SslOpts, Cache, CacheCb, Renegotiation, OwnCert) -> Version = dtls_record:highest_protocol_version(Versions), Pending = ssl_record:pending_connection_state(ConnectionStates, read), - SecParams = Pending#connection_state.security_parameters, - CipherSuites = ssl_handshake:available_suites(UserSuites, Version), - - Extensions = ssl_handshake:client_hello_extensions(Host, dtls_v1:corresponding_tls_version(Version), CipherSuites, - SslOpts, ConnectionStates, Renegotiation), + SecParams = maps:get(security_parameters, Pending), + TLSVersion = dtls_v1:corresponding_tls_version(Version), + CipherSuites = ssl_handshake:available_suites(UserSuites, TLSVersion), + Extensions = ssl_handshake:client_hello_extensions(TLSVersion, CipherSuites, + SslOpts, ConnectionStates, + Renegotiation), Id = ssl_session:client_id({Host, Port, SslOpts}, Cache, CacheCb, OwnCert), #client_hello{session_id = Id, client_version = Version, - cipher_suites = ssl_handshake:cipher_suites(CipherSuites, Renegotiation), + cipher_suites = + ssl_handshake:cipher_suites(CipherSuites, + Renegotiation, Fallback), compression_methods = ssl_record:compressions(), random = SecParams#security_parameters.client_random, cookie = Cookie, @@ -86,393 +102,422 @@ hello(#server_hello{server_version = Version, random = Random, case dtls_record:is_acceptable_version(Version, SupportedVersions) of true -> handle_server_hello_extensions(Version, SessionId, Random, CipherSuite, - Compression, HelloExt, SslOpt, ConnectionStates0, Renegotiation); + Compression, HelloExt, SslOpt, + ConnectionStates0, Renegotiation); false -> ?ALERT_REC(?FATAL, ?PROTOCOL_VERSION) end; +hello(#client_hello{client_version = ClientVersion} = Hello, + #ssl_options{versions = Versions} = SslOpts, + Info, Renegotiation) -> + Version = ssl_handshake:select_version(dtls_record, ClientVersion, Versions), + handle_client_hello(Version, Hello, SslOpts, Info, Renegotiation). + +cookie(Key, Address, Port, #client_hello{client_version = {Major, Minor}, + random = Random, + session_id = SessionId, + cipher_suites = CipherSuites, + compression_methods = CompressionMethods}) -> + CookieData = [address_to_bin(Address, Port), + <<?BYTE(Major), ?BYTE(Minor)>>, + Random, SessionId, CipherSuites, CompressionMethods], + crypto:hmac(sha, Key, CookieData). +%%-------------------------------------------------------------------- +-spec hello_verify_request(binary(), dtls_record:dtls_version()) -> #hello_verify_request{}. +%% +%% Description: Creates a hello verify request message sent by server to +%% verify client +%%-------------------------------------------------------------------- +hello_verify_request(Cookie, Version) -> + #hello_verify_request{protocol_version = Version, cookie = Cookie}. -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. - {ClientVersion, {new, #session{}}, ConnectionStates, #hello_extensions{}}. - -%% 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. - -%% %%-------------------------------------------------------------------- -encode_handshake(Handshake, Version, MsgSeq) -> +%%-------------------------------------------------------------------- +%%% Handshake encoding +%%-------------------------------------------------------------------- + +fragment_handshake(Bin, _) when is_binary(Bin)-> + %% This is the change_cipher_spec not a "real handshake" but part of the flight + Bin; +fragment_handshake([MsgType, Len, Seq, _, Len, Bin], Size) -> + Bins = bin_fragments(Bin, Size), + handshake_fragments(MsgType, Seq, Len, Bins, []). +encode_handshake(Handshake, Version, Seq) -> {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}. + [MsgType, ?uint24(Len), ?uint16(Seq), ?uint24(0), ?uint24(Len), Bin]. + +%%-------------------------------------------------------------------- +%%% Handshake decodeing +%%-------------------------------------------------------------------- %%-------------------------------------------------------------------- --spec get_dtls_handshake(#ssl_tls{}, #dtls_hs_state{} | binary()) -> - {[dtls_handshake()], #dtls_hs_state{}} | {retransmit, #dtls_hs_state{}}. +-spec get_dtls_handshake(dtls_record:dtls_version(), binary(), #protocol_buffers{}) -> + {[dtls_handshake()], #protocol_buffers{}}. %% -%% 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 +%% Description: Given buffered and new data from dtls_record, collects +%% and returns it as a list of handshake messages, also returns +%% possible leftover data in the new "protocol_buffers". %%-------------------------------------------------------------------- -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). - -%% %%-------------------------------------------------------------------- -%% -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 = []}. +get_dtls_handshake(Version, Fragment, ProtocolBuffers) -> + handle_fragments(Version, Fragment, ProtocolBuffers, []). %%-------------------------------------------------------------------- %%% 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, + eccs = SupportedECCs, + honor_ecc_order = ECCOrder} = SslOpts, + {Port, Session0, Cache, CacheCb, ConnectionStates0, Cert, _}, + Renegotiation) -> + case dtls_record:is_acceptable_version(Version, Versions) of + true -> + TLSVersion = dtls_v1:corresponding_tls_version(Version), + AvailableHashSigns = ssl_handshake:available_signature_algs( + ClientHashSigns, SupportedHashSigns, Cert,TLSVersion), + ECCCurve = ssl_handshake:select_curve(Curves, SupportedECCs, ECCOrder), + {Type, #session{cipher_suite = CipherSuite} = Session1} + = ssl_handshake:select_session(SugesstedId, CipherSuites, + AvailableHashSigns, Compressions, + Port, Session0#session{ecc = ECCCurve}, TLSVersion, + SslOpts, Cache, CacheCb, Cert), + case CipherSuite of + no_suite -> + ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY); + _ -> + #{key_exchange := KeyExAlg} = ssl_cipher_format:suite_definition(CipherSuite), + case ssl_handshake:select_hashsign(ClientHashSigns, Cert, KeyExAlg, + SupportedHashSigns, TLSVersion) 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; - {ConnectionStates, Protocol} -> - {Version, SessionId, ConnectionStates, Protocol} + {ConnectionStates, ProtoExt, Protocol} -> + {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_aux(#ssl_tls{version = Version, - sequence_number = SeqNo, - fragment = Data}, HsState) -> - get_dtls_handshake_aux(Version, SeqNo, Data, HsState). - -get_dtls_handshake_aux(Version, SeqNo, - <<?BYTE(Type), ?UINT24(Length), - ?UINT16(MessageSeq), - ?UINT24(FragmentOffset), ?UINT24(FragmentLength), - Body:FragmentLength/binary, Rest/binary>>, - HsState0) -> - case reassemble_dtls_fragment(SeqNo, Type, Length, MessageSeq, - FragmentOffset, FragmentLength, - Body, HsState0) of - {retransmit, HsState1} -> - case Rest of - <<>> -> - {retransmit, HsState1}; - _ -> - get_dtls_handshake_aux(Version, SeqNo, Rest, HsState1) - end; - {HsState1, HighestSeqNo, MsgBody} -> - HsState2 = dec_dtls_fragment(Version, HighestSeqNo, Type, Length, MessageSeq, MsgBody, HsState1), - HsState3 = process_dtls_fragments(Version, HsState2), - get_dtls_handshake_aux(Version, SeqNo, Rest, HsState3); - HsState2 -> - HsState3 = process_dtls_fragments(Version, HsState2), - get_dtls_handshake_aux(Version, SeqNo, Rest, HsState3) - end; - -get_dtls_handshake_aux(_Version, _SeqNo, <<>>, HsState) -> - {lists:reverse(HsState#dtls_hs_state.completed), - HsState#dtls_hs_state{completed = []}}. - -dec_dtls_fragment(Version, SeqNo, Type, Length, MessageSeq, MsgBody, - HsState = #dtls_hs_state{highest_record_seq = HighestSeqNo, completed = Acc}) -> - Raw = <<?BYTE(Type), ?UINT24(Length), ?UINT16(MessageSeq), ?UINT24(0), ?UINT24(Length), MsgBody/binary>>, - H = decode_handshake(Version, Type, MsgBody), - HsState#dtls_hs_state{completed = [{H,Raw}|Acc], highest_record_seq = erlang:max(HighestSeqNo, SeqNo)}. - -process_dtls_fragments(Version, - HsState0 = #dtls_hs_state{current_read_seq = CurrentReadSeq, - fragments = Fragments0}) -> - case gb_trees:is_empty(Fragments0) of - true -> - HsState0; - _ -> - case gb_trees:smallest(Fragments0) of - {CurrentReadSeq, {SeqNo, Type, Length, CurrentReadSeq, {Length, [{0, Length}], MsgBody}}} -> - HsState1 = dtls_hs_state_process_seq(HsState0), - HsState2 = dec_dtls_fragment(Version, SeqNo, Type, Length, CurrentReadSeq, MsgBody, HsState1), - process_dtls_fragments(Version, HsState2); - _ -> - HsState0 - end - end. - -dtls_hs_state_process_seq(HsState0 = #dtls_hs_state{current_read_seq = CurrentReadSeq, - fragments = Fragments0}) -> - Fragments1 = gb_trees:delete_any(CurrentReadSeq, Fragments0), - HsState0#dtls_hs_state{current_read_seq = CurrentReadSeq + 1, - fragments = Fragments1}. - -dtls_hs_state_add_fragment(MessageSeq, Fragment, HsState0 = #dtls_hs_state{fragments = Fragments0}) -> - Fragments1 = gb_trees:enter(MessageSeq, Fragment, Fragments0), - HsState0#dtls_hs_state{fragments = Fragments1}. - -reassemble_dtls_fragment(SeqNo, Type, Length, MessageSeq, 0, Length, - Body, HsState0 = #dtls_hs_state{current_read_seq = undefined}) - when Type == ?CLIENT_HELLO; - Type == ?SERVER_HELLO; - Type == ?HELLO_VERIFY_REQUEST -> - %% First message, should be client hello - %% return the current message and set the next expected Sequence - %% - %% Note: this could (should?) be restricted further, ClientHello and - %% HelloVerifyRequest have to have message_seq = 0, ServerHello - %% can have a message_seq of 0 or 1 - %% - {HsState0#dtls_hs_state{current_read_seq = MessageSeq + 1}, SeqNo, Body}; - -reassemble_dtls_fragment(_SeqNo, _Type, Length, _MessageSeq, _, Length, - _Body, HsState = #dtls_hs_state{current_read_seq = undefined}) -> - %% not what we expected, drop it - HsState; - -reassemble_dtls_fragment(SeqNo, _Type, Length, MessageSeq, 0, Length, - Body, HsState0 = - #dtls_hs_state{starting_read_seq = StartingReadSeq}) - when MessageSeq < StartingReadSeq -> - %% this has to be the start of a new flight, let it through - %% - %% Note: this could (should?) be restricted further, the first message of a - %% new flight has to have message_seq = 0 - %% - HsState = dtls_hs_state_process_seq(HsState0), - {HsState, SeqNo, Body}; - -reassemble_dtls_fragment(_SeqNo, _Type, Length, MessageSeq, 0, Length, - _Body, HsState = - #dtls_hs_state{current_read_seq = CurrentReadSeq}) - when MessageSeq < CurrentReadSeq -> - {retransmit, HsState}; - -reassemble_dtls_fragment(_SeqNo, _Type, Length, MessageSeq, 0, Length, - _Body, HsState = #dtls_hs_state{current_read_seq = CurrentReadSeq}) - when MessageSeq < CurrentReadSeq -> - HsState; - -reassemble_dtls_fragment(SeqNo, _Type, Length, MessageSeq, 0, Length, - Body, HsState0 = #dtls_hs_state{current_read_seq = MessageSeq}) -> - %% Message fully contained and it's the current seq - HsState1 = dtls_hs_state_process_seq(HsState0), - {HsState1, SeqNo, Body}; - -reassemble_dtls_fragment(SeqNo, Type, Length, MessageSeq, 0, Length, - Body, HsState) -> - %% Message fully contained and it's the NOT the current seq -> buffer - Fragment = {SeqNo, Type, Length, MessageSeq, - dtls_fragment_init(Length, 0, Length, Body)}, - dtls_hs_state_add_fragment(MessageSeq, Fragment, HsState); - -reassemble_dtls_fragment(_SeqNo, _Type, Length, MessageSeq, FragmentOffset, FragmentLength, - _Body, - HsState = #dtls_hs_state{current_read_seq = CurrentReadSeq}) - when FragmentOffset + FragmentLength == Length andalso MessageSeq == (CurrentReadSeq - 1) -> - {retransmit, HsState}; - -reassemble_dtls_fragment(_SeqNo, _Type, _Length, MessageSeq, _FragmentOffset, _FragmentLength, - _Body, - HsState = #dtls_hs_state{current_read_seq = CurrentReadSeq}) - when MessageSeq < CurrentReadSeq -> - HsState; - -reassemble_dtls_fragment(SeqNo, Type, Length, MessageSeq, - FragmentOffset, FragmentLength, - Body, - HsState = #dtls_hs_state{fragments = Fragments0}) -> - case gb_trees:lookup(MessageSeq, Fragments0) of - {value, Fragment} -> - dtls_fragment_reassemble(SeqNo, Type, Length, MessageSeq, - FragmentOffset, FragmentLength, - Body, Fragment, HsState); - none -> - dtls_fragment_start(SeqNo, Type, Length, MessageSeq, - FragmentOffset, FragmentLength, - Body, HsState) - end. -dtls_fragment_start(SeqNo, Type, Length, MessageSeq, - FragmentOffset, FragmentLength, - Body, HsState = #dtls_hs_state{fragments = Fragments0}) -> - Fragment = {SeqNo, Type, Length, MessageSeq, - dtls_fragment_init(Length, FragmentOffset, FragmentLength, Body)}, - Fragments1 = gb_trees:insert(MessageSeq, Fragment, Fragments0), - HsState#dtls_hs_state{fragments = Fragments1}. - -dtls_fragment_reassemble(SeqNo, Type, Length, MessageSeq, - FragmentOffset, FragmentLength, - Body, - {LastSeqNo, Type, Length, MessageSeq, FragBuffer0}, - HsState = #dtls_hs_state{fragments = Fragments0}) -> - FragBuffer1 = dtls_fragment_add(FragBuffer0, FragmentOffset, FragmentLength, Body), - Fragment = {erlang:max(SeqNo, LastSeqNo), Type, Length, MessageSeq, FragBuffer1}, - Fragments1 = gb_trees:enter(MessageSeq, Fragment, Fragments0), - HsState#dtls_hs_state{fragments = Fragments1}; - -%% Type, Length or Seq mismatch, drop everything... -%% Note: the RFC is not clear on how to handle this... -dtls_fragment_reassemble(_SeqNo, _Type, _Length, MessageSeq, - _FragmentOffset, _FragmentLength, _Body, _Fragment, - HsState = #dtls_hs_state{fragments = Fragments0}) -> - Fragments1 = gb_trees:delete_any(MessageSeq, Fragments0), - HsState#dtls_hs_state{fragments = Fragments1}. - -dtls_fragment_add({Length, FragmentList0, Bin0}, FragmentOffset, FragmentLength, Body) -> - Bin1 = dtls_fragment_bin_add(FragmentOffset, FragmentLength, Body, Bin0), - FragmentList1 = add_fragment(FragmentList0, {FragmentOffset, FragmentLength}), - {Length, FragmentList1, Bin1}. - -dtls_fragment_init(Length, 0, Length, Body) -> - {Length, [{0, Length}], Body}; -dtls_fragment_init(Length, FragmentOffset, FragmentLength, Body) -> - Bin = dtls_fragment_bin_add(FragmentOffset, FragmentLength, Body, <<0:(Length*8)>>), - {Length, [{FragmentOffset, FragmentOffset + FragmentLength}], Bin}. - -dtls_fragment_bin_add(FragmentOffset, FragmentLength, Add, Buffer) -> - <<First:FragmentOffset/bytes, _:FragmentLength/bytes, Rest/binary>> = Buffer, - <<First/binary, Add/binary, Rest/binary>>. - -merge_fragment_list([], Fragment, Acc) -> - lists:reverse([Fragment|Acc]); - -merge_fragment_list([H = {_, HEnd}|Rest], Frag = {FStart, _}, Acc) - when FStart > HEnd -> - merge_fragment_list(Rest, Frag, [H|Acc]); - -merge_fragment_list(Rest = [{HStart, _HEnd}|_], Frag = {_FStart, FEnd}, Acc) - when FEnd < HStart -> - lists:reverse(Acc) ++ [Frag|Rest]; - -merge_fragment_list([{HStart, HEnd}|Rest], _Frag = {FStart, FEnd}, Acc) - when - FStart =< HEnd orelse FEnd >= HStart -> - Start = erlang:min(HStart, FStart), - End = erlang:max(HEnd, FEnd), - NewFrag = {Start, End}, - merge_fragment_list(Rest, NewFrag, Acc). - -add_fragment(List, {FragmentOffset, FragmentLength}) -> - merge_fragment_list(List, {FragmentOffset, FragmentOffset + FragmentLength}, []). +%%-------------------------------------------------------------------- enc_handshake(#hello_verify_request{protocol_version = {Major, Minor}, cookie = Cookie}, _Version) -> - CookieLength = byte_size(Cookie), + CookieLength = byte_size(Cookie), {?HELLO_VERIFY_REQUEST, <<?BYTE(Major), ?BYTE(Minor), ?BYTE(CookieLength), - Cookie/binary>>}; - + Cookie:CookieLength/binary>>}; +enc_handshake(#hello_request{}, _Version) -> + {?HELLO_REQUEST, <<>>}; enc_handshake(#client_hello{client_version = {Major, Minor}, random = Random, session_id = SessionID, cookie = Cookie, cipher_suites = CipherSuites, compression_methods = CompMethods, - extensions = HelloExtensions}, Version) -> + extensions = HelloExtensions}, _Version) -> SIDLength = byte_size(SessionID), - BinCookie = enc_client_hello_cookie(Version, Cookie), + CookieLength = byte_size(Cookie), BinCompMethods = list_to_binary(CompMethods), CmLength = byte_size(BinCompMethods), BinCipherSuites = list_to_binary(CipherSuites), CsLength = byte_size(BinCipherSuites), ExtensionsBin = ssl_handshake:encode_hello_extensions(HelloExtensions), - + {?CLIENT_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary, ?BYTE(SIDLength), SessionID/binary, - BinCookie/binary, + ?BYTE(CookieLength), Cookie/binary, ?UINT16(CsLength), BinCipherSuites/binary, ?BYTE(CmLength), BinCompMethods/binary, ExtensionsBin/binary>>}; +enc_handshake(#server_hello{} = HandshakeMsg, Version) -> + {Type, <<?BYTE(Major), ?BYTE(Minor), Rest/binary>>} = + ssl_handshake:encode_handshake(HandshakeMsg, Version), + {DTLSMajor, DTLSMinor} = dtls_v1:corresponding_dtls_version({Major, Minor}), + {Type, <<?BYTE(DTLSMajor), ?BYTE(DTLSMinor), Rest/binary>>}; enc_handshake(HandshakeMsg, Version) -> - ssl_handshake:encode_handshake(HandshakeMsg, Version). + ssl_handshake:encode_handshake(HandshakeMsg, dtls_v1:corresponding_tls_version(Version)). + +handshake_bin(#handshake_fragment{ + type = Type, + length = Len, + message_seq = Seq, + fragment_length = Len, + fragment_offset = 0, + fragment = Fragment}) -> + handshake_bin(Type, Len, Seq, Fragment). +handshake_bin(Type, Length, Seq, FragmentData) -> + <<?BYTE(Type), ?UINT24(Length), + ?UINT16(Seq), ?UINT24(0), ?UINT24(Length), + FragmentData:Length/binary>>. + +bin_fragments(Bin, Size) -> + bin_fragments(Bin, size(Bin), Size, 0, []). +bin_fragments(Bin, BinSize, FragSize, Offset, Fragments) -> + case (BinSize - Offset - FragSize) > 0 of + true -> + Frag = binary:part(Bin, {Offset, FragSize}), + bin_fragments(Bin, BinSize, FragSize, Offset + FragSize, [{Frag, Offset} | Fragments]); + false -> + Frag = binary:part(Bin, {Offset, BinSize-Offset}), + lists:reverse([{Frag, Offset} | Fragments]) + end. -enc_client_hello_cookie(_, <<>>) -> - <<>>; -enc_client_hello_cookie(_, Cookie) -> - CookieLength = byte_size(Cookie), - <<?BYTE(CookieLength), Cookie/binary>>. +handshake_fragments(_, _, _, [], Acc) -> + lists:reverse(Acc); +handshake_fragments(MsgType, Seq, Len, [{Bin, Offset} | Bins], Acc) -> + FragLen = size(Bin), + handshake_fragments(MsgType, Seq, Len, Bins, + [<<?BYTE(MsgType), Len/binary, Seq/binary, ?UINT24(Offset), + ?UINT24(FragLen), Bin/binary>> | Acc]). + +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>>. + +%%-------------------------------------------------------------------- + +handle_fragments(Version, FragmentData, Buffers0, Acc) -> + Fragments = decode_handshake_fragments(FragmentData), + do_handle_fragments(Version, Fragments, Buffers0, Acc). + +do_handle_fragments(_, [], Buffers, Acc) -> + {lists:reverse(Acc), Buffers}; +do_handle_fragments(Version, [Fragment | Fragments], Buffers0, Acc) -> + case reassemble(Version, Fragment, Buffers0) of + {more_data, Buffers} when Fragments == [] -> + {lists:reverse(Acc), Buffers}; + {more_data, Buffers} -> + do_handle_fragments(Version, Fragments, Buffers, Acc); + {HsPacket, Buffers} -> + do_handle_fragments(Version, Fragments, Buffers, [HsPacket | Acc]) + end. + +decode_handshake(Version, <<?BYTE(Type), Bin/binary>>) -> + decode_handshake(Version, Type, Bin). -decode_handshake(_Version, ?CLIENT_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary, +decode_handshake(_, ?HELLO_REQUEST, <<>>) -> + #hello_request{}; +decode_handshake(_Version, ?CLIENT_HELLO, <<?UINT24(_), ?UINT16(_), + ?UINT24(_), ?UINT24(_), + ?BYTE(Major), ?BYTE(Minor), Random:32/binary, ?BYTE(SID_length), Session_ID:SID_length/binary, - ?BYTE(Cookie_length), Cookie:Cookie_length/binary, + ?BYTE(CookieLength), Cookie:CookieLength/binary, ?UINT16(Cs_length), CipherSuites:Cs_length/binary, ?BYTE(Cm_length), Comp_methods:Cm_length/binary, Extensions/binary>>) -> - DecodedExtensions = ssl_handshake:decode_hello_extensions(Extensions), - + DecodedExtensions = ssl_handshake:decode_hello_extensions({client, Extensions}), + #client_hello{ client_version = {Major,Minor}, random = Random, - session_id = Session_ID, cookie = Cookie, + session_id = Session_ID, cipher_suites = ssl_handshake:decode_suites('2_bytes', CipherSuites), compression_methods = Comp_methods, extensions = DecodedExtensions - }; + }; +decode_handshake(_Version, ?HELLO_VERIFY_REQUEST, <<?UINT24(_), ?UINT16(_), + ?UINT24(_), ?UINT24(_), + ?BYTE(Major), ?BYTE(Minor), + ?BYTE(CookieLength), + Cookie:CookieLength/binary>>) -> + #hello_verify_request{protocol_version = {Major, Minor}, + cookie = Cookie}; +decode_handshake(Version, Tag, <<?UINT24(_), ?UINT16(_), + ?UINT24(_), ?UINT24(_), Msg/binary>>) -> + %% DTLS specifics stripped + decode_tls_thandshake(Version, Tag, Msg). + +decode_tls_thandshake(Version, Tag, Msg) -> + TLSVersion = dtls_v1:corresponding_tls_version(Version), + ssl_handshake:decode_handshake(TLSVersion, Tag, Msg). + +decode_handshake_fragments(<<>>) -> + [<<>>]; +decode_handshake_fragments(<<?BYTE(Type), ?UINT24(Length), + ?UINT16(MessageSeq), + ?UINT24(FragmentOffset), ?UINT24(FragmentLength), + Fragment:FragmentLength/binary, Rest/binary>>) -> + [#handshake_fragment{type = Type, + length = Length, + message_seq = MessageSeq, + fragment_offset = FragmentOffset, + fragment_length = FragmentLength, + fragment = Fragment} | decode_handshake_fragments(Rest)]. + +reassemble(Version, #handshake_fragment{message_seq = Seq} = Fragment, + #protocol_buffers{dtls_handshake_next_seq = Seq, + dtls_handshake_next_fragments = Fragments0, + dtls_handshake_later_fragments = LaterFragments0} = + Buffers0)-> + case reassemble_fragments(Fragment, Fragments0) of + {more_data, Fragments} -> + {more_data, Buffers0#protocol_buffers{dtls_handshake_next_fragments = Fragments}}; + {raw, RawHandshake} -> + Handshake = decode_handshake(Version, RawHandshake), + {NextFragments, LaterFragments} = next_fragments(LaterFragments0), + {{Handshake, RawHandshake}, Buffers0#protocol_buffers{dtls_handshake_next_seq = Seq + 1, + dtls_handshake_next_fragments = NextFragments, + dtls_handshake_later_fragments = LaterFragments}} + end; +reassemble(_, #handshake_fragment{message_seq = FragSeq} = Fragment, + #protocol_buffers{dtls_handshake_next_seq = Seq, + dtls_handshake_later_fragments = LaterFragments} + = Buffers0) when FragSeq > Seq-> + {more_data, + Buffers0#protocol_buffers{dtls_handshake_later_fragments = [Fragment | LaterFragments]}}; +reassemble(_, _, Buffers) -> + %% Disregard fragments FragSeq < Seq + {more_data, Buffers}. + +reassemble_fragments(Current, Fragments0) -> + [Frag1 | Frags] = lists:keysort(#handshake_fragment.fragment_offset, [Current | Fragments0]), + [Fragment | _] = Fragments = merge_fragment(Frag1, Frags), + case is_complete_handshake(Fragment) of + true -> + {raw, handshake_bin(Fragment)}; + false -> + {more_data, Fragments} + end. + +merge_fragment(Frag0, []) -> + [Frag0]; +merge_fragment(Frag0, [Frag1 | Rest]) -> + case merge_fragments(Frag0, Frag1) of + [_|_] = Frags -> + Frags ++ Rest; + Frag -> + merge_fragment(Frag, Rest) + end. +%% Duplicate +merge_fragments(#handshake_fragment{ + fragment_offset = PreviousOffSet, + fragment_length = PreviousLen, + fragment = PreviousData + } = Previous, + #handshake_fragment{ + fragment_offset = PreviousOffSet, + fragment_length = PreviousLen, + fragment = PreviousData}) -> + Previous; + +%% Lager fragment save new data +merge_fragments(#handshake_fragment{ + fragment_offset = PreviousOffSet, + fragment_length = PreviousLen, + fragment = PreviousData + } = Previous, + #handshake_fragment{ + fragment_offset = PreviousOffSet, + fragment_length = CurrentLen, + fragment = CurrentData}) when CurrentLen > PreviousLen -> + NewLength = CurrentLen - PreviousLen, + <<_:PreviousLen/binary, NewData/binary>> = CurrentData, + Previous#handshake_fragment{ + fragment_length = PreviousLen + NewLength, + fragment = <<PreviousData/binary, NewData/binary>> + }; + +%% Smaller fragment +merge_fragments(#handshake_fragment{ + fragment_offset = PreviousOffSet, + fragment_length = PreviousLen + } = Previous, + #handshake_fragment{ + fragment_offset = PreviousOffSet, + fragment_length = CurrentLen}) when CurrentLen < PreviousLen -> + Previous; +%% Next fragment, might be overlapping +merge_fragments(#handshake_fragment{ + fragment_offset = PreviousOffSet, + fragment_length = PreviousLen, + fragment = PreviousData + } = Previous, + #handshake_fragment{ + fragment_offset = CurrentOffSet, + fragment_length = CurrentLen, + fragment = CurrentData}) + when PreviousOffSet + PreviousLen >= CurrentOffSet andalso + PreviousOffSet + PreviousLen < CurrentOffSet + CurrentLen -> + CurrentStart = PreviousOffSet + PreviousLen - CurrentOffSet, + <<_:CurrentStart/bytes, Data/binary>> = CurrentData, + Previous#handshake_fragment{ + fragment_length = PreviousLen + CurrentLen - CurrentStart, + fragment = <<PreviousData/binary, Data/binary>>}; +%% already fully contained fragment +merge_fragments(#handshake_fragment{ + fragment_offset = PreviousOffSet, + fragment_length = PreviousLen + } = Previous, + #handshake_fragment{ + fragment_offset = CurrentOffSet, + fragment_length = CurrentLen}) + when PreviousOffSet + PreviousLen >= CurrentOffSet andalso + PreviousOffSet + PreviousLen >= CurrentOffSet + CurrentLen -> + Previous; + +%% No merge there is a gap +merge_fragments(Previous, Current) -> + [Previous, Current]. + +next_fragments(LaterFragments) -> + case lists:keysort(#handshake_fragment.message_seq, LaterFragments) of + [] -> + {[], []}; + [#handshake_fragment{message_seq = Seq} | _] = Fragments -> + split_frags(Fragments, Seq, []) + end. + +split_frags([#handshake_fragment{message_seq = Seq} = Frag | Rest], Seq, Acc) -> + split_frags(Rest, Seq, [Frag | Acc]); +split_frags(Frags, _, Acc) -> + {lists:reverse(Acc), Frags}. + +is_complete_handshake(#handshake_fragment{length = Length, fragment_length = Length}) -> + true; +is_complete_handshake(_) -> + false. + -decode_handshake(_Version, ?HELLO_VERIFY_REQUEST, <<?BYTE(Major), ?BYTE(Minor), - ?BYTE(CookieLength), Cookie:CookieLength/binary>>) -> - #hello_verify_request{ - protocol_version = {Major,Minor}, - cookie = Cookie}; -decode_handshake(Version, Tag, Msg) -> - ssl_handshake:decode_handshake(Version, Tag, Msg). -%% 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>>. + diff --git a/lib/ssl/src/dtls_handshake.hrl b/lib/ssl/src/dtls_handshake.hrl index 3b57575b6d..50e92027d2 100644 --- a/lib/ssl/src/dtls_handshake.hrl +++ b/lib/ssl/src/dtls_handshake.hrl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2013-2014. All Rights Reserved. +%% Copyright Ericsson AB 2013-2017. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. +%% 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 %% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% 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% %% @@ -28,6 +29,7 @@ -include("ssl_handshake.hrl"). %% Common TLS and DTLS records and Constantes -define(HELLO_VERIFY_REQUEST, 3). +-define(HELLO_VERIFY_REQUEST_VERSION, {254, 255}). -record(client_hello, { client_version, @@ -45,12 +47,13 @@ cookie }). --record(dtls_hs_state, - {current_read_seq, - starting_read_seq, - highest_record_seq, - fragments, - completed - }). +-record(handshake_fragment, { + type, + length, + message_seq, + fragment_offset, + fragment_length, + fragment + }). -endif. % -ifdef(dtls_handshake). diff --git a/lib/ssl/src/dtls_listener_sup.erl b/lib/ssl/src/dtls_listener_sup.erl new file mode 100644 index 0000000000..dc30696a2c --- /dev/null +++ b/lib/ssl/src/dtls_listener_sup.erl @@ -0,0 +1,62 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2016-2018. 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% +%% + +%% +%%---------------------------------------------------------------------- +%% Purpose: Supervisor for a procsses dispatching upd datagrams to +%% correct DTLS handler +%%---------------------------------------------------------------------- +-module(dtls_listener_sup). + +-behaviour(supervisor). + +%% API +-export([start_link/0]). +-export([start_child/1]). + +%% Supervisor callback +-export([init/1]). + +%%%========================================================================= +%%% API +%%%========================================================================= +start_link() -> + supervisor:start_link({local, ?MODULE}, ?MODULE, []). + +start_child(Args) -> + supervisor:start_child(?MODULE, Args). + +%%%========================================================================= +%%% Supervisor callback +%%%========================================================================= +init(_O) -> + RestartStrategy = simple_one_for_one, + MaxR = 0, + MaxT = 3600, + + Name = undefined, % As simple_one_for_one is used. + StartFunc = {dtls_packet_demux, start_link, []}, + Restart = temporary, % E.g. should not be restarted + Shutdown = 4000, + Modules = [dtls_packet_demux], + Type = worker, + + ChildSpec = {Name, StartFunc, Restart, Shutdown, Type, Modules}, + {ok, {{RestartStrategy, MaxR, MaxT}, [ChildSpec]}}. diff --git a/lib/ssl/src/dtls_packet_demux.erl b/lib/ssl/src/dtls_packet_demux.erl new file mode 100644 index 0000000000..1497c77cf3 --- /dev/null +++ b/lib/ssl/src/dtls_packet_demux.erl @@ -0,0 +1,309 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2016-2018. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% + +-module(dtls_packet_demux). + +-behaviour(gen_server). + +-include("ssl_internal.hrl"). + +%% API +-export([start_link/5, active_once/3, accept/2, sockname/1, close/1, + get_all_opts/1, get_sock_opts/2, set_sock_opts/2]). + +%% gen_server callbacks +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, + terminate/2, code_change/3]). + +-record(state, + {port, + listener, + transport, + dtls_options, + emulated_options, + dtls_msq_queues = kv_new(), + clients = set_new(), + dtls_processes = kv_new(), + accepters = queue:new(), + first, + close + }). + +%%%=================================================================== +%%% API +%%%=================================================================== + +start_link(Port, TransportInfo, EmOpts, InetOptions, DTLSOptions) -> + gen_server:start_link(?MODULE, [Port, TransportInfo, EmOpts, InetOptions, DTLSOptions], []). + +active_once(PacketSocket, Client, Pid) -> + gen_server:cast(PacketSocket, {active_once, Client, Pid}). + +accept(PacketSocket, Accepter) -> + call(PacketSocket, {accept, Accepter}). + +sockname(PacketSocket) -> + call(PacketSocket, sockname). +close(PacketSocket) -> + call(PacketSocket, close). +get_sock_opts(PacketSocket, SplitSockOpts) -> + call(PacketSocket, {get_sock_opts, SplitSockOpts}). +get_all_opts(PacketSocket) -> + call(PacketSocket, get_all_opts). +set_sock_opts(PacketSocket, Opts) -> + call(PacketSocket, {set_sock_opts, Opts}). + +%%%=================================================================== +%%% gen_server callbacks +%%%=================================================================== + +init([Port, {TransportModule, _,_,_} = TransportInfo, EmOpts, InetOptions, DTLSOptions]) -> + try + {ok, Socket} = TransportModule:open(Port, InetOptions), + {ok, #state{port = Port, + first = true, + transport = TransportInfo, + dtls_options = DTLSOptions, + emulated_options = EmOpts, + listener = Socket, + close = false}} + catch _:_ -> + {stop, {shutdown, {error, closed}}} + end. +handle_call({accept, _}, _, #state{close = true} = State) -> + {reply, {error, closed}, State}; + +handle_call({accept, Accepter}, From, #state{first = true, + accepters = Accepters, + listener = Socket} = State0) -> + next_datagram(Socket), + State = State0#state{first = false, + accepters = queue:in({Accepter, From}, Accepters)}, + {noreply, State}; + +handle_call({accept, Accepter}, From, #state{accepters = Accepters} = State0) -> + State = State0#state{accepters = queue:in({Accepter, From}, Accepters)}, + {noreply, State}; +handle_call(sockname, _, #state{listener = Socket} = State) -> + Reply = inet:sockname(Socket), + {reply, Reply, State}; +handle_call(close, _, #state{dtls_processes = Processes, + accepters = Accepters} = State) -> + case kv_empty(Processes) of + true -> + {stop, normal, ok, State#state{close=true}}; + false -> + lists:foreach(fun({_, From}) -> + gen_server:reply(From, {error, closed}) + end, queue:to_list(Accepters)), + {reply, ok, State#state{close = true, accepters = queue:new()}} + end; +handle_call({get_sock_opts, {SocketOptNames, EmOptNames}}, _, #state{listener = Socket, + emulated_options = EmOpts} = State) -> + case get_socket_opts(Socket, SocketOptNames) of + {ok, Opts} -> + {reply, {ok, emulated_opts_list(EmOpts, EmOptNames, []) ++ Opts}, State}; + {error, Reason} -> + {reply, {error, Reason}, State} + end; +handle_call(get_all_opts, _, #state{dtls_options = DTLSOptions, + emulated_options = EmOpts} = State) -> + {reply, {ok, EmOpts, DTLSOptions}, State}; +handle_call({set_sock_opts, {SocketOpts, NewEmOpts}}, _, #state{listener = Socket, emulated_options = EmOpts0} = State) -> + set_socket_opts(Socket, SocketOpts), + EmOpts = do_set_emulated_opts(NewEmOpts, EmOpts0), + {reply, ok, State#state{emulated_options = EmOpts}}. + +handle_cast({active_once, Client, Pid}, State0) -> + State = handle_active_once(Client, Pid, State0), + {noreply, State}. + +handle_info({Transport, Socket, IP, InPortNo, _} = Msg, #state{listener = Socket, transport = {_,Transport,_,_}} = State0) -> + State = handle_datagram({IP, InPortNo}, Msg, State0), + next_datagram(Socket), + {noreply, State}; + +%% UDP socket does not have a connection and should not receive an econnreset +%% This does however happens on some windows versions. Just ignoring it +%% appears to make things work as expected! +handle_info({Error, Socket, econnreset = Error}, #state{listener = Socket, transport = {_,_,_, udp_error}} = State) -> + Report = io_lib:format("Ignore SSL UDP Listener: Socket error: ~p ~n", [Error]), + error_logger:info_report(Report), + {noreply, State}; +handle_info({Error, Socket, Error}, #state{listener = Socket, transport = {_,_,_, Error}} = State) -> + Report = io_lib:format("SSL Packet muliplxer shutdown: Socket error: ~p ~n", [Error]), + error_logger:info_report(Report), + {noreply, State#state{close=true}}; + +handle_info({'DOWN', _, process, Pid, _}, #state{clients = Clients, + dtls_processes = Processes0, + dtls_msq_queues = MsgQueues0, + close = ListenClosed} = State) -> + Client = kv_get(Pid, Processes0), + Processes = kv_delete(Pid, Processes0), + MsgQueues = kv_delete(Client, MsgQueues0), + case ListenClosed andalso kv_empty(Processes) of + true -> + {stop, normal, State}; + false -> + {noreply, State#state{clients = set_delete(Client, Clients), + dtls_processes = Processes, + dtls_msq_queues = MsgQueues}} + end. + +terminate(_Reason, _State) -> + ok. + +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== +handle_datagram(Client, Msg, #state{clients = Clients, + accepters = AcceptorsQueue0} = State) -> + case set_is_member(Client, Clients) of + false -> + case queue:out(AcceptorsQueue0) of + {{value, {UserPid, From}}, AcceptorsQueue} -> + setup_new_connection(UserPid, From, Client, Msg, + State#state{accepters = AcceptorsQueue}); + {empty, _} -> + %% Drop packet client will resend + State + end; + true -> + dispatch(Client, Msg, State) + end. + +dispatch(Client, Msg, #state{dtls_msq_queues = MsgQueues} = State) -> + case kv_lookup(Client, MsgQueues) of + {value, Queue0} -> + case queue:out(Queue0) of + {{value, Pid}, Queue} when is_pid(Pid) -> + Pid ! Msg, + State#state{dtls_msq_queues = + kv_update(Client, Queue, MsgQueues)}; + {{value, _}, Queue} -> + State#state{dtls_msq_queues = + kv_update(Client, queue:in(Msg, Queue), MsgQueues)}; + {empty, Queue} -> + State#state{dtls_msq_queues = + kv_update(Client, queue:in(Msg, Queue), MsgQueues)} + end + end. +next_datagram(Socket) -> + inet:setopts(Socket, [{active, once}]). + +handle_active_once(Client, Pid, #state{dtls_msq_queues = MsgQueues} = State0) -> + Queue0 = kv_get(Client, MsgQueues), + case queue:out(Queue0) of + {{value, Pid}, _} when is_pid(Pid) -> + State0; + {{value, Msg}, Queue} -> + Pid ! Msg, + State0#state{dtls_msq_queues = kv_update(Client, Queue, MsgQueues)}; + {empty, Queue0} -> + State0#state{dtls_msq_queues = kv_update(Client, queue:in(Pid, Queue0), MsgQueues)} + end. + +setup_new_connection(User, From, Client, Msg, #state{dtls_processes = Processes, + clients = Clients, + dtls_msq_queues = MsgQueues, + dtls_options = DTLSOpts, + port = Port, + listener = Socket, + emulated_options = EmOpts} = State) -> + ConnArgs = [server, "localhost", Port, {self(), {Client, Socket}}, + {DTLSOpts, EmOpts, dtls_listener}, User, dtls_socket:default_cb_info()], + case dtls_connection_sup:start_child(ConnArgs) of + {ok, Pid} -> + erlang:monitor(process, Pid), + gen_server:reply(From, {ok, Pid, {Client, Socket}}), + Pid ! Msg, + State#state{clients = set_insert(Client, Clients), + dtls_msq_queues = kv_insert(Client, queue:new(), MsgQueues), + dtls_processes = kv_insert(Pid, Client, Processes)}; + {error, Reason} -> + gen_server:reply(From, {error, Reason}), + State + end. + +kv_update(Key, Value, Store) -> + gb_trees:update(Key, Value, Store). +kv_lookup(Key, Store) -> + gb_trees:lookup(Key, Store). +kv_insert(Key, Value, Store) -> + gb_trees:insert(Key, Value, Store). +kv_get(Key, Store) -> + gb_trees:get(Key, Store). +kv_delete(Key, Store) -> + gb_trees:delete(Key, Store). +kv_new() -> + gb_trees:empty(). +kv_empty(Store) -> + gb_trees:is_empty(Store). + +set_new() -> + gb_sets:empty(). +set_insert(Item, Set) -> + gb_sets:insert(Item, Set). +set_delete(Item, Set) -> + gb_sets:delete(Item, Set). +set_is_member(Item, Set) -> + gb_sets:is_member(Item, Set). + +call(Server, Msg) -> + try + gen_server:call(Server, Msg, infinity) + catch + exit:{noproc, _} -> + {error, closed}; + exit:{normal, _} -> + {error, closed}; + exit:{{shutdown, _},_} -> + {error, closed} + end. + +set_socket_opts(_, []) -> + ok; +set_socket_opts(Socket, SocketOpts) -> + inet:setopts(Socket, SocketOpts). + +get_socket_opts(_, []) -> + {ok, []}; +get_socket_opts(Socket, SocketOpts) -> + inet:getopts(Socket, SocketOpts). + +do_set_emulated_opts([], Opts) -> + Opts; +do_set_emulated_opts([{mode, Value} | Rest], Opts) -> + do_set_emulated_opts(Rest, Opts#socket_options{mode = Value}); +do_set_emulated_opts([{active, Value} | Rest], Opts) -> + do_set_emulated_opts(Rest, Opts#socket_options{active = Value}). + +emulated_opts_list(_,[], Acc) -> + Acc; +emulated_opts_list( Opts, [mode | Rest], Acc) -> + emulated_opts_list(Opts, Rest, [{mode, Opts#socket_options.mode} | Acc]); +emulated_opts_list(Opts, [active | Rest], Acc) -> + emulated_opts_list(Opts, Rest, [{active, Opts#socket_options.active} | Acc]). + diff --git a/lib/ssl/src/dtls_record.erl b/lib/ssl/src/dtls_record.erl index ae35dd7ea4..b7346d3ec8 100644 --- a/lib/ssl/src/dtls_record.erl +++ b/lib/ssl/src/dtls_record.erl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2013-2015. All Rights Reserved. +%% Copyright Ericsson AB 2013-2018. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. +%% 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 %% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% 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% @@ -29,153 +30,222 @@ -include("ssl_cipher.hrl"). %% Handling of incoming data --export([get_dtls_records/2]). +-export([get_dtls_records/3, init_connection_states/2, empty_connection_state/1]). -%% Decoding --export([decode_cipher_text/2]). +-export([save_current_connection_state/2, next_epoch/2, get_connection_state_by_epoch/3, replay_detect/2, + init_connection_state_seq/2, current_connection_state_epoch/2]). %% Encoding --export([encode_plain_text/4, encode_handshake/3, encode_change_cipher_spec/2]). +-export([encode_handshake/4, encode_alert_record/3, + encode_change_cipher_spec/3, encode_data/3, encode_plain_text/5]). + +%% Decoding +-export([decode_cipher_text/2]). %% Protocol version handling --export([protocol_version/1, lowest_protocol_version/2, - highest_protocol_version/1, supported_protocol_versions/0, - is_acceptable_version/2]). +-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, hello_version/2]). -%% DTLS Epoch handling --export([init_connection_state_seq/2, current_connection_state_epoch/2, - set_connection_state_by_epoch/3, connection_state_by_epoch/3]). -export_type([dtls_version/0, dtls_atom_version/0]). -type dtls_version() :: ssl_record:ssl_version(). -type dtls_atom_version() :: dtlsv1 | 'dtlsv1.2'. +-define(REPLAY_WINDOW_SIZE, 64). + -compile(inline). %%==================================================================== -%% Internal application API +%% Handling of incoming data %%==================================================================== +%%-------------------------------------------------------------------- +-spec init_connection_states(client | server, one_n_minus_one | zero_n | disabled) -> + ssl_record:connection_states(). +%% % + % +%% Description: Creates a connection_states record with appropriate +%% values for the initial SSL connection setup. +%%-------------------------------------------------------------------- +init_connection_states(Role, BeastMitigation) -> + ConnectionEnd = ssl_record:record_protocol_role(Role), + Initial = initial_connection_state(ConnectionEnd, BeastMitigation), + Current = Initial#{epoch := 0}, + InitialPending = ssl_record:empty_connection_state(ConnectionEnd, BeastMitigation), + Pending = empty_connection_state(InitialPending), + #{saved_read => Current, + current_read => Current, + pending_read => Pending, + saved_write => Current, + current_write => Current, + pending_write => Pending}. + +empty_connection_state(Empty) -> + Empty#{epoch => undefined, replay_window => init_replay_window(?REPLAY_WINDOW_SIZE)}. + +%%-------------------------------------------------------------------- +-spec save_current_connection_state(ssl_record:connection_states(), read | write) -> + ssl_record:connection_states(). +%% +%% Description: Returns the instance of the connection_state map +%% where the current read|write state has been copied to the save state. +%%-------------------------------------------------------------------- +save_current_connection_state(#{current_read := Current} = States, read) -> + States#{saved_read := Current}; + +save_current_connection_state(#{current_write := Current} = States, write) -> + States#{saved_write := Current}. + +next_epoch(#{pending_read := Pending, + current_read := #{epoch := Epoch}} = States, read) -> + States#{pending_read := Pending#{epoch := Epoch + 1, + replay_window := init_replay_window(?REPLAY_WINDOW_SIZE)}}; + +next_epoch(#{pending_write := Pending, + current_write := #{epoch := Epoch}} = States, write) -> + States#{pending_write := Pending#{epoch := Epoch + 1, + replay_window := init_replay_window(?REPLAY_WINDOW_SIZE)}}. + +get_connection_state_by_epoch(Epoch, #{current_write := #{epoch := Epoch} = Current}, + write) -> + Current; +get_connection_state_by_epoch(Epoch, #{saved_write := #{epoch := Epoch} = Saved}, + write) -> + Saved; +get_connection_state_by_epoch(Epoch, #{current_read := #{epoch := Epoch} = Current}, + read) -> + Current; +get_connection_state_by_epoch(Epoch, #{saved_read := #{epoch := Epoch} = Saved}, + read) -> + Saved. + +set_connection_state_by_epoch(WriteState, Epoch, #{current_write := #{epoch := Epoch}} = States, + write) -> + States#{current_write := WriteState}; +set_connection_state_by_epoch(WriteState, Epoch, #{saved_write := #{epoch := Epoch}} = States, + write) -> + States#{saved_write := WriteState}; +set_connection_state_by_epoch(ReadState, Epoch, #{current_read := #{epoch := Epoch}} = States, + read) -> + States#{current_read := ReadState}; +set_connection_state_by_epoch(ReadState, Epoch, #{saved_read := #{epoch := Epoch}} = States, + read) -> + States#{saved_read := ReadState}. + +%%-------------------------------------------------------------------- +-spec init_connection_state_seq(dtls_version(), ssl_record:connection_states()) -> + ssl_record:connection_state(). +%% +%% Description: Copy the read sequence number to the write sequence number +%% This is only valid for DTLS in the first client_hello +%%-------------------------------------------------------------------- +init_connection_state_seq({254, _}, + #{current_read := #{epoch := 0, sequence_number := Seq}, + current_write := #{epoch := 0} = Write} = ConnnectionStates0) -> + ConnnectionStates0#{current_write => Write#{sequence_number => Seq}}; +init_connection_state_seq(_, ConnnectionStates) -> + ConnnectionStates. + +%%-------------------------------------------------------- +-spec current_connection_state_epoch(ssl_record:connection_states(), read | write) -> + integer(). +%% +%% Description: Returns the epoch the connection_state record +%% that is currently defined as the current connection state. +%%-------------------------------------------------------------------- +current_connection_state_epoch(#{current_read := #{epoch := Epoch}}, + read) -> + Epoch; +current_connection_state_epoch(#{current_write := #{epoch := Epoch}}, + write) -> + Epoch. %%-------------------------------------------------------------------- --spec get_dtls_records(binary(), binary()) -> {[binary()], binary()} | #alert{}. +-spec get_dtls_records(binary(), [dtls_version()], binary()) -> {[binary()], binary()} | #alert{}. %% %% Description: Given old buffer and new data from UDP/SCTP, packs up a records %% and returns it as a list of tls_compressed binaries also returns leftover %% data %%-------------------------------------------------------------------- -get_dtls_records(Data, <<>>) -> - get_dtls_records_aux(Data, []); -get_dtls_records(Data, Buffer) -> - get_dtls_records_aux(list_to_binary([Buffer, Data]), []). - -get_dtls_records_aux(<<?BYTE(?APPLICATION_DATA),?BYTE(MajVer),?BYTE(MinVer), - ?UINT16(Epoch), ?UINT48(SequenceNumber), - ?UINT16(Length), Data:Length/binary, Rest/binary>>, - Acc) -> - get_dtls_records_aux(Rest, [#ssl_tls{type = ?APPLICATION_DATA, - version = {MajVer, MinVer}, - epoch = Epoch, sequence_number = SequenceNumber, - fragment = Data} | Acc]); -get_dtls_records_aux(<<?BYTE(?HANDSHAKE),?BYTE(MajVer),?BYTE(MinVer), - ?UINT16(Epoch), ?UINT48(SequenceNumber), - ?UINT16(Length), - Data:Length/binary, Rest/binary>>, Acc) when MajVer >= 128 -> - get_dtls_records_aux(Rest, [#ssl_tls{type = ?HANDSHAKE, - version = {MajVer, MinVer}, - epoch = Epoch, sequence_number = SequenceNumber, - fragment = Data} | Acc]); -get_dtls_records_aux(<<?BYTE(?ALERT),?BYTE(MajVer),?BYTE(MinVer), - ?UINT16(Epoch), ?UINT48(SequenceNumber), - ?UINT16(Length), Data:Length/binary, - Rest/binary>>, Acc) -> - get_dtls_records_aux(Rest, [#ssl_tls{type = ?ALERT, - version = {MajVer, MinVer}, - epoch = Epoch, sequence_number = SequenceNumber, - fragment = Data} | Acc]); -get_dtls_records_aux(<<?BYTE(?CHANGE_CIPHER_SPEC),?BYTE(MajVer),?BYTE(MinVer), - ?UINT16(Epoch), ?UINT48(SequenceNumber), - ?UINT16(Length), Data:Length/binary, Rest/binary>>, - Acc) -> - get_dtls_records_aux(Rest, [#ssl_tls{type = ?CHANGE_CIPHER_SPEC, - version = {MajVer, MinVer}, - epoch = Epoch, sequence_number = SequenceNumber, - fragment = Data} | Acc]); - -get_dtls_records_aux(<<0:1, _CT:7, ?BYTE(_MajVer), ?BYTE(_MinVer), - ?UINT16(Length), _/binary>>, - _Acc) when Length > ?MAX_CIPHER_TEXT_LENGTH -> - ?ALERT_REC(?FATAL, ?RECORD_OVERFLOW); +get_dtls_records(Data, Versions, Buffer) -> + BinData = list_to_binary([Buffer, Data]), + case erlang:byte_size(BinData) of + N when N >= 3 -> + case assert_version(BinData, Versions) of + true -> + get_dtls_records_aux(BinData, []); + false -> + ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC) + end; + _ -> + get_dtls_records_aux(BinData, []) + end. -get_dtls_records_aux(<<1:1, Length0:15, _/binary>>,_Acc) - when Length0 > ?MAX_CIPHER_TEXT_LENGTH -> - ?ALERT_REC(?FATAL, ?RECORD_OVERFLOW); +%%==================================================================== +%% Encoding DTLS records +%%==================================================================== -get_dtls_records_aux(Data, Acc) -> - case size(Data) =< ?MAX_CIPHER_TEXT_LENGTH + ?INITIAL_BYTES of - true -> - {lists:reverse(Acc), Data}; - false -> - ?ALERT_REC(?FATAL, ?UNEXPECTED_MESSAGE) - end. +%%-------------------------------------------------------------------- +-spec encode_handshake(iolist(), dtls_version(), integer(), ssl_record:connection_states()) -> + {iolist(), ssl_record:connection_states()}. +% +%% Description: Encodes a handshake message to send on the ssl-socket. +%%-------------------------------------------------------------------- +encode_handshake(Frag, Version, Epoch, ConnectionStates) -> + encode_plain_text(?HANDSHAKE, Version, Epoch, Frag, ConnectionStates). -encode_plain_text(Type, Version, Data, - #connection_states{current_write=#connection_state{ - epoch = Epoch, - sequence_number = Seq, - compression_state=CompS0, - security_parameters= - #security_parameters{compression_algorithm=CompAlg} - }= WriteState0} = ConnectionStates) -> - {Comp, CompS1} = ssl_record:compress(CompAlg, Data, CompS0), - WriteState1 = WriteState0#connection_state{compression_state = CompS1}, - MacHash = calc_mac_hash(WriteState1, Type, Version, Epoch, Seq, Comp), - {CipherFragment, WriteState} = ssl_record:cipher(dtls_v1:corresponding_tls_version(Version), - Comp, WriteState1, MacHash), - CipherText = encode_tls_cipher_text(Type, Version, Epoch, Seq, CipherFragment), - {CipherText, ConnectionStates#connection_states{current_write = - WriteState#connection_state{sequence_number = Seq +1}}}. -decode_cipher_text(#ssl_tls{type = Type, version = Version, - epoch = Epoch, - sequence_number = Seq, - fragment = CipherFragment} = CipherText, - #connection_states{current_read = - #connection_state{compression_state = CompressionS0, - security_parameters = SecParams} = ReadState0} - = ConnnectionStates0) -> - CompressAlg = SecParams#security_parameters.compression_algorithm, - {PlainFragment, Mac, ReadState1} = ssl_record:decipher(dtls_v1:corresponding_tls_version(Version), - CipherFragment, ReadState0, true), - MacHash = calc_mac_hash(ReadState1, Type, Version, Epoch, Seq, PlainFragment), - case ssl_record:is_correct_mac(Mac, MacHash) of - true -> - {Plain, CompressionS1} = ssl_record:uncompress(CompressAlg, - PlainFragment, CompressionS0), - ConnnectionStates = ConnnectionStates0#connection_states{ - current_read = ReadState1#connection_state{ - compression_state = CompressionS1}}, - {CipherText#ssl_tls{fragment = Plain}, ConnnectionStates}; - false -> - ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC) - end. %%-------------------------------------------------------------------- --spec encode_handshake(iolist(), dtls_version(), #connection_states{}) -> - {iolist(), #connection_states{}}. +-spec encode_alert_record(#alert{}, dtls_version(), ssl_record:connection_states()) -> + {iolist(), ssl_record:connection_states()}. %% -%% Description: Encodes a handshake message to send on the ssl-socket. +%% Description: Encodes an alert message to send on the ssl-socket. %%-------------------------------------------------------------------- -encode_handshake(Frag, Version, ConnectionStates) -> - encode_plain_text(?HANDSHAKE, Version, Frag, ConnectionStates). +encode_alert_record(#alert{level = Level, description = Description}, + Version, ConnectionStates) -> + #{epoch := Epoch} = ssl_record:current_connection_state(ConnectionStates, write), + encode_plain_text(?ALERT, Version, Epoch, <<?BYTE(Level), ?BYTE(Description)>>, + ConnectionStates). %%-------------------------------------------------------------------- --spec encode_change_cipher_spec(dtls_version(), #connection_states{}) -> - {iolist(), #connection_states{}}. +-spec encode_change_cipher_spec(dtls_version(), integer(), ssl_record:connection_states()) -> + {iolist(), ssl_record:connection_states()}. %% %% Description: Encodes a change_cipher_spec-message to send on the ssl socket. %%-------------------------------------------------------------------- -encode_change_cipher_spec(Version, ConnectionStates) -> - encode_plain_text(?CHANGE_CIPHER_SPEC, Version, <<1:8>>, ConnectionStates). +encode_change_cipher_spec(Version, Epoch, ConnectionStates) -> + encode_plain_text(?CHANGE_CIPHER_SPEC, Version, Epoch, ?byte(?CHANGE_CIPHER_SPEC_PROTO), ConnectionStates). + +%%-------------------------------------------------------------------- +-spec encode_data(binary(), dtls_version(), ssl_record:connection_states()) -> + {iolist(),ssl_record:connection_states()}. +%% +%% Description: Encodes data to send on the ssl-socket. +%%-------------------------------------------------------------------- +encode_data(Data, Version, ConnectionStates) -> + #{epoch := Epoch} = ssl_record:current_connection_state(ConnectionStates, write), + encode_plain_text(?APPLICATION_DATA, Version, Epoch, Data, ConnectionStates). + +encode_plain_text(Type, Version, Epoch, Data, ConnectionStates) -> + Write0 = get_connection_state_by_epoch(Epoch, ConnectionStates, write), + {CipherFragment, Write1} = encode_plain_text(Type, Version, Data, Write0), + {CipherText, Write} = encode_dtls_cipher_text(Type, Version, CipherFragment, Write1), + {CipherText, set_connection_state_by_epoch(Write, Epoch, ConnectionStates, write)}. + +%%==================================================================== +%% Decoding +%%==================================================================== + +decode_cipher_text(#ssl_tls{epoch = Epoch} = CipherText, ConnnectionStates0) -> + ReadState = get_connection_state_by_epoch(Epoch, ConnnectionStates0, read), + decode_cipher_text(CipherText, ReadState, ConnnectionStates0). + + +%%==================================================================== +%% Protocol version handling +%%==================================================================== %%-------------------------------------------------------------------- -spec protocol_version(dtls_atom_version() | dtls_version()) -> @@ -205,25 +275,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(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({M, _}, + Version = {M, _}) -> + Version; +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()]. @@ -240,21 +341,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(). @@ -265,106 +378,250 @@ supported_connection_protocol_versions([]) -> is_acceptable_version(Version, Versions) -> lists:member(Version, Versions). +-spec hello_version(dtls_version(), [dtls_version()]) -> dtls_version(). +hello_version(Version, Versions) -> + case dtls_v1:corresponding_tls_version(Version) of + TLSVersion when TLSVersion >= {3, 3} -> + Version; + _ -> + lowest_protocol_version(Versions) + end. + %%-------------------------------------------------------------------- --spec init_connection_state_seq(dtls_version(), #connection_states{}) -> - #connection_state{}. -%% -%% Description: Copy the read sequence number to the write sequence number -%% This is only valid for DTLS in the first client_hello +%%% Internal functions %%-------------------------------------------------------------------- -init_connection_state_seq({254, _}, - #connection_states{ - current_read = Read = #connection_state{epoch = 0}, - current_write = Write = #connection_state{epoch = 0}} = CS0) -> - CS0#connection_states{current_write = - Write#connection_state{ - sequence_number = Read#connection_state.sequence_number}}; -init_connection_state_seq(_, CS) -> - CS. +initial_connection_state(ConnectionEnd, BeastMitigation) -> + #{security_parameters => + ssl_record:initial_security_params(ConnectionEnd), + epoch => undefined, + sequence_number => 0, + replay_window => init_replay_window(?REPLAY_WINDOW_SIZE), + beast_mitigation => BeastMitigation, + compression_state => undefined, + cipher_state => undefined, + mac_secret => undefined, + secure_renegotiation => undefined, + client_verify_data => undefined, + server_verify_data => undefined + }. +assert_version(<<?BYTE(_), ?BYTE(MajVer), ?BYTE(MinVer), _/binary>>, Versions) -> + is_acceptable_version({MajVer, MinVer}, Versions). -%%-------------------------------------------------------- --spec current_connection_state_epoch(#connection_states{}, read | write) -> - integer(). -%% -%% Description: Returns the epoch the connection_state record -%% that is currently defined as the current conection state. +get_dtls_records_aux(<<?BYTE(?APPLICATION_DATA),?BYTE(MajVer),?BYTE(MinVer), + ?UINT16(Epoch), ?UINT48(SequenceNumber), + ?UINT16(Length), Data:Length/binary, Rest/binary>>, + Acc) -> + get_dtls_records_aux(Rest, [#ssl_tls{type = ?APPLICATION_DATA, + version = {MajVer, MinVer}, + epoch = Epoch, sequence_number = SequenceNumber, + fragment = Data} | Acc]); +get_dtls_records_aux(<<?BYTE(?HANDSHAKE),?BYTE(MajVer),?BYTE(MinVer), + ?UINT16(Epoch), ?UINT48(SequenceNumber), + ?UINT16(Length), + Data:Length/binary, Rest/binary>>, Acc) when MajVer >= 128 -> + get_dtls_records_aux(Rest, [#ssl_tls{type = ?HANDSHAKE, + version = {MajVer, MinVer}, + epoch = Epoch, sequence_number = SequenceNumber, + fragment = Data} | Acc]); +get_dtls_records_aux(<<?BYTE(?ALERT),?BYTE(MajVer),?BYTE(MinVer), + ?UINT16(Epoch), ?UINT48(SequenceNumber), + ?UINT16(Length), Data:Length/binary, + Rest/binary>>, Acc) -> + get_dtls_records_aux(Rest, [#ssl_tls{type = ?ALERT, + version = {MajVer, MinVer}, + epoch = Epoch, sequence_number = SequenceNumber, + fragment = Data} | Acc]); +get_dtls_records_aux(<<?BYTE(?CHANGE_CIPHER_SPEC),?BYTE(MajVer),?BYTE(MinVer), + ?UINT16(Epoch), ?UINT48(SequenceNumber), + ?UINT16(Length), Data:Length/binary, Rest/binary>>, + Acc) -> + get_dtls_records_aux(Rest, [#ssl_tls{type = ?CHANGE_CIPHER_SPEC, + version = {MajVer, MinVer}, + epoch = Epoch, sequence_number = SequenceNumber, + fragment = Data} | Acc]); +get_dtls_records_aux(<<?BYTE(_), ?BYTE(_MajVer), ?BYTE(_MinVer), + ?UINT16(Length), _/binary>>, + _Acc) when Length > ?MAX_CIPHER_TEXT_LENGTH -> + ?ALERT_REC(?FATAL, ?RECORD_OVERFLOW); + +get_dtls_records_aux(Data, Acc) -> + case size(Data) =< ?MAX_CIPHER_TEXT_LENGTH + ?INITIAL_BYTES of + true -> + {lists:reverse(Acc), Data}; + false -> + ?ALERT_REC(?FATAL, ?UNEXPECTED_MESSAGE) + end. %%-------------------------------------------------------------------- -current_connection_state_epoch(#connection_states{current_read = Current}, - read) -> - Current#connection_state.epoch; -current_connection_state_epoch(#connection_states{current_write = Current}, - write) -> - Current#connection_state.epoch. + +init_replay_window(Size) -> + #{size => Size, + top => Size, + bottom => 0, + mask => 0 bsl 64 + }. + +replay_detect(#ssl_tls{sequence_number = SequenceNumber}, #{replay_window := Window}) -> + is_replay(SequenceNumber, Window). + + +is_replay(SequenceNumber, #{bottom := Bottom}) when SequenceNumber < Bottom -> + true; +is_replay(SequenceNumber, #{size := Size, + top := Top, + bottom := Bottom, + mask := Mask}) when (SequenceNumber >= Bottom) andalso (SequenceNumber =< Top) -> + Index = (SequenceNumber rem Size), + (Index band Mask) == 1; + +is_replay(_, _) -> + false. + +update_replay_window(SequenceNumber, #{replay_window := #{size := Size, + top := Top, + bottom := Bottom, + mask := Mask0} = Window0} = ConnectionStates) -> + NoNewBits = SequenceNumber - Top, + Index = SequenceNumber rem Size, + Mask = (Mask0 bsl NoNewBits) bor Index, + Window = Window0#{top => SequenceNumber, + bottom => Bottom + NoNewBits, + mask => Mask}, + ConnectionStates#{replay_window := Window}. %%-------------------------------------------------------------------- --spec connection_state_by_epoch(#connection_states{}, integer(), read | write) -> - #connection_state{}. -%% -%% Description: Returns the instance of the connection_state record -%% that is defined by the Epoch. -%%-------------------------------------------------------------------- -connection_state_by_epoch(#connection_states{current_read = CS}, Epoch, read) - when CS#connection_state.epoch == Epoch -> - CS; -connection_state_by_epoch(#connection_states{pending_read = CS}, Epoch, read) - when CS#connection_state.epoch == Epoch -> - CS; -connection_state_by_epoch(#connection_states{current_write = CS}, Epoch, write) - when CS#connection_state.epoch == Epoch -> - CS; -connection_state_by_epoch(#connection_states{pending_write = CS}, Epoch, write) - when CS#connection_state.epoch == Epoch -> - CS. -%%-------------------------------------------------------------------- --spec set_connection_state_by_epoch(#connection_states{}, - #connection_state{}, read | write) - -> #connection_states{}. -%% -%% Description: Returns the instance of the connection_state record -%% that is defined by the Epoch. -%%-------------------------------------------------------------------- -set_connection_state_by_epoch(ConnectionStates0 = - #connection_states{current_read = CS}, - NewCS = #connection_state{epoch = Epoch}, read) - when CS#connection_state.epoch == Epoch -> - ConnectionStates0#connection_states{current_read = NewCS}; - -set_connection_state_by_epoch(ConnectionStates0 = - #connection_states{pending_read = CS}, - NewCS = #connection_state{epoch = Epoch}, read) - when CS#connection_state.epoch == Epoch -> - ConnectionStates0#connection_states{pending_read = NewCS}; - -set_connection_state_by_epoch(ConnectionStates0 = - #connection_states{current_write = CS}, - NewCS = #connection_state{epoch = Epoch}, write) - when CS#connection_state.epoch == Epoch -> - ConnectionStates0#connection_states{current_write = NewCS}; - -set_connection_state_by_epoch(ConnectionStates0 = - #connection_states{pending_write = CS}, - NewCS = #connection_state{epoch = Epoch}, write) - when CS#connection_state.epoch == Epoch -> - ConnectionStates0#connection_states{pending_write = NewCS}. +encode_dtls_cipher_text(Type, {MajVer, MinVer}, Fragment, + #{epoch := Epoch, sequence_number := Seq} = WriteState) -> + Length = erlang:iolist_size(Fragment), + {[<<?BYTE(Type), ?BYTE(MajVer), ?BYTE(MinVer), ?UINT16(Epoch), + ?UINT48(Seq), ?UINT16(Length)>>, Fragment], + WriteState#{sequence_number => Seq + 1}}. + +encode_plain_text(Type, Version, Data, #{compression_state := CompS0, + cipher_state := CipherS0, + epoch := Epoch, + sequence_number := Seq, + security_parameters := + #security_parameters{ + cipher_type = ?AEAD, + bulk_cipher_algorithm = BCAlg, + compression_algorithm = CompAlg} + } = WriteState0) -> + {Comp, CompS1} = ssl_record:compress(CompAlg, Data, CompS0), + AAD = start_additional_data(Type, Version, Epoch, Seq), + CipherS = ssl_record:nonce_seed(BCAlg, <<?UINT16(Epoch), ?UINT48(Seq)>>, CipherS0), + WriteState = WriteState0#{compression_state => CompS1, + cipher_state => CipherS}, + TLSVersion = dtls_v1:corresponding_tls_version(Version), + ssl_record:cipher_aead(TLSVersion, Comp, WriteState, AAD); +encode_plain_text(Type, Version, Fragment, #{compression_state := CompS0, + epoch := Epoch, + sequence_number := Seq, + cipher_state := CipherS0, + security_parameters := + #security_parameters{compression_algorithm = CompAlg, + bulk_cipher_algorithm = + BulkCipherAlgo} + }= WriteState0) -> + {Comp, CompS1} = ssl_record:compress(CompAlg, Fragment, CompS0), + WriteState1 = WriteState0#{compression_state => CompS1}, + MAC = calc_mac_hash(Type, Version, WriteState1, Epoch, Seq, Comp), + TLSVersion = dtls_v1:corresponding_tls_version(Version), + {CipherFragment, CipherS1} = + ssl_cipher:cipher(BulkCipherAlgo, CipherS0, MAC, Fragment, TLSVersion), + {CipherFragment, WriteState0#{cipher_state => CipherS1}}. %%-------------------------------------------------------------------- -%%% Internal functions +decode_cipher_text(#ssl_tls{type = Type, version = Version, + epoch = Epoch, + sequence_number = Seq, + fragment = CipherFragment} = CipherText, + #{compression_state := CompressionS0, + cipher_state := CipherS0, + security_parameters := + #security_parameters{ + cipher_type = ?AEAD, + bulk_cipher_algorithm = + BulkCipherAlgo, + compression_algorithm = CompAlg}} = ReadState0, + ConnnectionStates0) -> + AAD = start_additional_data(Type, Version, Epoch, Seq), + CipherS1 = ssl_record:nonce_seed(BulkCipherAlgo, <<?UINT16(Epoch), ?UINT48(Seq)>>, CipherS0), + TLSVersion = dtls_v1:corresponding_tls_version(Version), + case ssl_record:decipher_aead(BulkCipherAlgo, CipherS1, AAD, CipherFragment, TLSVersion) of + {PlainFragment, CipherState} -> + {Plain, CompressionS1} = ssl_record:uncompress(CompAlg, + PlainFragment, CompressionS0), + ReadState0 = ReadState0#{compression_state => CompressionS1, + cipher_state => CipherState}, + ReadState = update_replay_window(Seq, ReadState0), + ConnnectionStates = set_connection_state_by_epoch(ReadState, Epoch, ConnnectionStates0, read), + {CipherText#ssl_tls{fragment = Plain}, ConnnectionStates}; + #alert{} = Alert -> + Alert + end; +decode_cipher_text(#ssl_tls{type = Type, version = Version, + epoch = Epoch, + sequence_number = Seq, + fragment = CipherFragment} = CipherText, + #{compression_state := CompressionS0, + security_parameters := + #security_parameters{ + compression_algorithm = CompAlg}} = ReadState0, + ConnnectionStates0) -> + {PlainFragment, Mac, ReadState1} = ssl_record:decipher(dtls_v1:corresponding_tls_version(Version), + CipherFragment, ReadState0, true), + MacHash = calc_mac_hash(Type, Version, ReadState1, Epoch, Seq, PlainFragment), + case ssl_record:is_correct_mac(Mac, MacHash) of + true -> + {Plain, CompressionS1} = ssl_record:uncompress(CompAlg, + PlainFragment, CompressionS0), + + ReadState2 = ReadState1#{compression_state => CompressionS1}, + ReadState = update_replay_window(Seq, ReadState2), + ConnnectionStates = set_connection_state_by_epoch(ReadState, Epoch, ConnnectionStates0, read), + {CipherText#ssl_tls{fragment = Plain}, ConnnectionStates}; + false -> + ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC) + end. %%-------------------------------------------------------------------- -encode_tls_cipher_text(Type, {MajVer, MinVer}, Epoch, Seq, Fragment) -> - Length = erlang:iolist_size(Fragment), - [<<?BYTE(Type), ?BYTE(MajVer), ?BYTE(MinVer), ?UINT16(Epoch), - ?UINT48(Seq), ?UINT16(Length)>>, Fragment]. -calc_mac_hash(#connection_state{mac_secret = MacSecret, - security_parameters = #security_parameters{mac_algorithm = MacAlg}}, - Type, Version, Epoch, SeqNo, Fragment) -> +calc_mac_hash(Type, Version, #{mac_secret := MacSecret, + security_parameters := #security_parameters{mac_algorithm = MacAlg}}, + Epoch, SeqNo, Fragment) -> Length = erlang:iolist_size(Fragment), - NewSeq = (Epoch bsl 48) + SeqNo, - mac_hash(Version, MacAlg, MacSecret, NewSeq, Type, + mac_hash(Version, MacAlg, MacSecret, Epoch, SeqNo, Type, Length, Fragment). -mac_hash(Version, MacAlg, MacSecret, SeqNo, Type, Length, Fragment) -> - dtls_v1:mac_hash(Version, MacAlg, MacSecret, SeqNo, Type, - Length, Fragment). +mac_hash({Major, Minor}, MacAlg, MacSecret, Epoch, SeqNo, Type, Length, Fragment) -> + Value = [<<?UINT16(Epoch), ?UINT48(SeqNo), ?BYTE(Type), + ?BYTE(Major), ?BYTE(Minor), ?UINT16(Length)>>, + Fragment], + dtls_v1:hmac_hash(MacAlg, MacSecret, Value). + +start_additional_data(Type, {MajVer, MinVer}, Epoch, SeqNo) -> + <<?UINT16(Epoch), ?UINT48(SeqNo), ?BYTE(Type), ?BYTE(MajVer), ?BYTE(MinVer)>>. + +%%-------------------------------------------------------------------- + +lowest_list_protocol_version(Ver, []) -> + Ver; +lowest_list_protocol_version(Ver1, [Ver2 | Rest]) -> + lowest_list_protocol_version(lowest_protocol_version(Ver1, Ver2), Rest). + +highest_list_protocol_version(Ver, []) -> + Ver; +highest_list_protocol_version(Ver1, [Ver2 | Rest]) -> + highest_list_protocol_version(highest_protocol_version(Ver1, Ver2), Rest). + +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)). + diff --git a/lib/ssl/src/dtls_record.hrl b/lib/ssl/src/dtls_record.hrl index edb77fb2b1..373481c3f8 100644 --- a/lib/ssl/src/dtls_record.hrl +++ b/lib/ssl/src/dtls_record.hrl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2013-2014. All Rights Reserved. +%% Copyright Ericsson AB 2013-2016. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. +%% 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 %% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% 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% %% @@ -33,11 +34,10 @@ -record(ssl_tls, { type, version, - epoch, - sequence_number, - offset, - length, - fragment + %%length, + fragment, + epoch, + sequence_number }). -endif. % -ifdef(dtls_record). diff --git a/lib/ssl/src/dtls_socket.erl b/lib/ssl/src/dtls_socket.erl new file mode 100644 index 0000000000..2001afd02f --- /dev/null +++ b/lib/ssl/src/dtls_socket.erl @@ -0,0 +1,191 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2016-2018. 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(dtls_socket). + +-include("ssl_internal.hrl"). +-include("ssl_api.hrl"). + +-export([send/3, listen/2, accept/3, connect/4, socket/4, setopts/3, getopts/3, getstat/3, + peername/2, sockname/2, port/2, close/2]). +-export([emulated_options/0, emulated_options/1, internal_inet_values/0, default_inet_values/0, default_cb_info/0]). + +send(Transport, {{IP,Port},Socket}, Data) -> + Transport:send(Socket, IP, Port, Data). + +listen(Port, #config{transport_info = TransportInfo, + ssl = SslOpts, + emulated = EmOpts, + inet_user = Options} = Config) -> + + + case dtls_listener_sup:start_child([Port, TransportInfo, emulated_socket_options(EmOpts, #socket_options{}), + Options ++ internal_inet_values(), SslOpts]) of + {ok, Pid} -> + {ok, #sslsocket{pid = {dtls, Config#config{dtls_handler = {Pid, Port}}}}}; + Err = {error, _} -> + Err + end. + +accept(dtls, #config{transport_info = {Transport,_,_,_}, + connection_cb = ConnectionCb, + dtls_handler = {Listner, _}}, _Timeout) -> + case dtls_packet_demux:accept(Listner, self()) of + {ok, Pid, Socket} -> + {ok, socket([Pid], Transport, {Listner, Socket}, ConnectionCb)}; + {error, Reason} -> + {error, Reason} + end. + +connect(Address, Port, #config{transport_info = {Transport, _, _, _} = CbInfo, + connection_cb = ConnectionCb, + ssl = SslOpts, + emulated = EmOpts, + inet_ssl = SocketOpts}, Timeout) -> + case Transport:open(0, SocketOpts ++ internal_inet_values()) of + {ok, Socket} -> + ssl_connection:connect(ConnectionCb, Address, Port, {{Address, Port},Socket}, + {SslOpts, + emulated_socket_options(EmOpts, #socket_options{}), undefined}, + self(), CbInfo, Timeout); + {error, _} = Error-> + Error + end. + +close(gen_udp, {_Client, _Socket}) -> + ok; +close(Transport, {_Client, Socket}) -> + Transport:close(Socket). + +socket(Pids, gen_udp = Transport, {{_, _}, Socket}, ConnectionCb) -> + #sslsocket{pid = Pids, + %% "The name "fd" is keept for backwards compatibility + fd = {Transport, Socket, ConnectionCb}}; +socket(Pids, Transport, Socket, ConnectionCb) -> + #sslsocket{pid = Pids, + %% "The name "fd" is keept for backwards compatibility + fd = {Transport, Socket, ConnectionCb}}. +setopts(_, #sslsocket{pid = {dtls, #config{dtls_handler = {ListenPid, _}}}}, Options) -> + SplitOpts = tls_socket:split_options(Options), + dtls_packet_demux:set_sock_opts(ListenPid, SplitOpts); +%%% Following clauses will not be called for emulated options, they are handled in the connection process +setopts(gen_udp, Socket, Options) -> + inet:setopts(Socket, Options); +setopts(Transport, Socket, Options) -> + Transport:setopts(Socket, Options). + +getopts(_, #sslsocket{pid = {dtls, #config{dtls_handler = {ListenPid, _}}}}, Options) -> + SplitOpts = tls_socket:split_options(Options), + dtls_packet_demux:get_sock_opts(ListenPid, SplitOpts); +getopts(gen_udp, #sslsocket{pid = {Socket, #config{emulated = EmOpts}}}, Options) -> + {SockOptNames, EmulatedOptNames} = tls_socket:split_options(Options), + EmulatedOpts = get_emulated_opts(EmOpts, EmulatedOptNames), + SocketOpts = tls_socket:get_socket_opts(Socket, SockOptNames, inet), + {ok, EmulatedOpts ++ SocketOpts}; +getopts(_Transport, #sslsocket{pid = {Socket, #config{emulated = EmOpts}}}, Options) -> + {SockOptNames, EmulatedOptNames} = tls_socket:split_options(Options), + EmulatedOpts = get_emulated_opts(EmOpts, EmulatedOptNames), + SocketOpts = tls_socket:get_socket_opts(Socket, SockOptNames, inet), + {ok, EmulatedOpts ++ SocketOpts}; +%%% Following clauses will not be called for emulated options, they are handled in the connection process +getopts(gen_udp, {_,{{_, _},Socket}}, Options) -> + inet:getopts(Socket, Options); +getopts(gen_udp, {_,Socket}, Options) -> + inet:getopts(Socket, Options); +getopts(Transport, Socket, Options) -> + Transport:getopts(Socket, Options). +getstat(gen_udp, {_,Socket}, Options) -> + inet:getstat(Socket, Options); +getstat(Transport, Socket, Options) -> + Transport:getstat(Socket, Options). +peername(_, undefined) -> + {error, enotconn}; +peername(gen_udp, {_, {Client, _Socket}}) -> + {ok, Client}; +peername(Transport, Socket) -> + Transport:peername(Socket). +sockname(gen_udp, {_, {_,Socket}}) -> + inet:sockname(Socket); +sockname(gen_udp, Socket) -> + inet:sockname(Socket); +sockname(Transport, Socket) -> + Transport:sockname(Socket). + +port(gen_udp, {_,Socket}) -> + inet:port(Socket); +port(Transport, Socket) -> + Transport:port(Socket). + +emulated_options() -> + [mode, active, packet, packet_size]. + +emulated_options(Opts) -> + emulated_options(Opts, internal_inet_values(), default_inet_values()). + +internal_inet_values() -> + [{active, false}, {mode,binary}]. + +default_inet_values() -> + [{active, true}, {mode, list}, {packet, 0}, {packet_size, 0}]. + +default_cb_info() -> + {gen_udp, udp, udp_closed, udp_error}. + +get_emulated_opts(EmOpts, EmOptNames) -> + lists:map(fun(Name) -> {value, Value} = lists:keysearch(Name, 1, EmOpts), + Value end, + EmOptNames). + +emulated_socket_options(InetValues, #socket_options{ + mode = Mode, + packet = Packet, + packet_size = PacketSize, + active = Active}) -> + #socket_options{ + mode = proplists:get_value(mode, InetValues, Mode), + packet = proplists:get_value(packet, InetValues, Packet), + packet_size = proplists:get_value(packet_size, InetValues, PacketSize), + active = proplists:get_value(active, InetValues, Active) + }. + +emulated_options([{mode, Value} = Opt |Opts], Inet, Emulated) -> + validate_inet_option(mode, Value), + emulated_options(Opts, Inet, [Opt | proplists:delete(mode, Emulated)]); +emulated_options([{header, _} = Opt | _], _, _) -> + throw({error, {options, {not_supported, Opt}}}); +emulated_options([{active, Value} = Opt |Opts], Inet, Emulated) -> + validate_inet_option(active, Value), + emulated_options(Opts, Inet, [Opt | proplists:delete(active, Emulated)]); +emulated_options([{packet, _} = Opt | _], _, _) -> + throw({error, {options, {not_supported, Opt}}}); +emulated_options([{packet_size, _} = Opt | _], _, _) -> + throw({error, {options, {not_supported, Opt}}}); +emulated_options([Opt|Opts], Inet, Emulated) -> + emulated_options(Opts, [Opt|Inet], Emulated); +emulated_options([], Inet,Emulated) -> + {Inet, Emulated}. + +validate_inet_option(mode, Value) + when Value =/= list, Value =/= binary -> + throw({error, {options, {mode,Value}}}); +validate_inet_option(active, Value) + when Value =/= true, Value =/= false, Value =/= once -> + throw({error, {options, {active,Value}}}); +validate_inet_option(_, _) -> + ok. diff --git a/lib/ssl/src/dtls_v1.erl b/lib/ssl/src/dtls_v1.erl index 5a7ab32887..b365961a6a 100644 --- a/lib/ssl/src/dtls_v1.erl +++ b/lib/ssl/src/dtls_v1.erl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2013-2014. All Rights Reserved. +%% Copyright Ericsson AB 2013-2018. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. +%% 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 %% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% 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% %% @@ -20,16 +21,33 @@ -include("ssl_cipher.hrl"). --export([suites/1, mac_hash/7, ecc_curves/1, corresponding_tls_version/1]). +-export([suites/1, all_suites/1, anonymous_suites/1,hmac_hash/3, ecc_curves/1, + corresponding_tls_version/1, corresponding_dtls_version/1, + cookie_secret/0, cookie_timeout/0]). + +-define(COOKIE_BASE_TIMEOUT, 30000). --spec suites(Minor:: 253|255) -> [ssl_cipher:cipher_suite()]. +-spec suites(Minor:: 253|255) -> [ssl_cipher_format:cipher_suite()]. suites(Minor) -> - tls_v1:suites(corresponding_minor_tls_version(Minor)). + lists:filter(fun(Cipher) -> + is_acceptable_cipher(ssl_cipher_format:suite_definition(Cipher)) + end, + tls_v1:suites(corresponding_minor_tls_version(Minor))). +all_suites(Version) -> + lists:filter(fun(Cipher) -> + is_acceptable_cipher(ssl_cipher_format:suite_definition(Cipher)) + end, + ssl_cipher:all_suites(corresponding_tls_version(Version))). -mac_hash(Version, MacAlg, MacSecret, SeqNo, Type, Length, Fragment) -> - tls_v1:mac_hash(MacAlg, MacSecret, SeqNo, Type, corresponding_tls_version(Version), - Length, Fragment). +anonymous_suites(Version) -> + lists:filter(fun(Cipher) -> + is_acceptable_cipher(ssl_cipher_format:suite_definition(Cipher)) + end, + ssl_cipher:anonymous_suites(corresponding_tls_version(Version))). + +hmac_hash(MacAlg, MacSecret, Value) -> + tls_v1:hmac_hash(MacAlg, MacSecret, Value). ecc_curves({_Major, Minor}) -> tls_v1:ecc_curves(corresponding_minor_tls_version(Minor)). @@ -37,7 +55,24 @@ ecc_curves({_Major, Minor}) -> corresponding_tls_version({254, Minor}) -> {3, corresponding_minor_tls_version(Minor)}. +cookie_secret() -> + crypto:strong_rand_bytes(32). + +cookie_timeout() -> + %% Cookie will live for two timeouts periods + round(rand:uniform() * ?COOKIE_BASE_TIMEOUT/2). + corresponding_minor_tls_version(255) -> 2; corresponding_minor_tls_version(253) -> 3. + +corresponding_dtls_version({3, Minor}) -> + {254, corresponding_minor_dtls_version(Minor)}. + +corresponding_minor_dtls_version(2) -> + 255; +corresponding_minor_dtls_version(3) -> + 253. +is_acceptable_cipher(Suite) -> + not ssl_cipher:is_stream_ciphersuite(Suite). diff --git a/lib/ssl/src/inet6_tls_dist.erl b/lib/ssl/src/inet6_tls_dist.erl new file mode 100644 index 0000000000..96ce4d493a --- /dev/null +++ b/lib/ssl/src/inet6_tls_dist.erl @@ -0,0 +1,47 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2015-2017. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% 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(inet6_tls_dist). + +-export([childspecs/0]). +-export([listen/1, accept/1, accept_connection/5, + setup/5, close/1, select/1]). + +childspecs() -> + inet_tls_dist:childspecs(). + +select(Node) -> + inet_tls_dist:gen_select(inet6_tcp, Node). + +listen(Name) -> + inet_tls_dist:gen_listen(inet6_tcp, Name). + +accept(Listen) -> + inet_tls_dist:gen_accept(inet6_tcp, Listen). + +accept_connection(AcceptPid, Socket, MyNode, Allowed, SetupTime) -> + inet_tls_dist:gen_accept_connection(inet6_tcp, AcceptPid, Socket, MyNode, Allowed, SetupTime). + +setup(Node, Type, MyNode, LongOrShortNames,SetupTime) -> + inet_tls_dist:gen_setup(inet6_tcp, Node, Type, MyNode, LongOrShortNames,SetupTime). + +close(Socket) -> + inet_tls_dist:gen_close(inet6_tcp, Socket). diff --git a/lib/ssl/src/inet_tls_dist.erl b/lib/ssl/src/inet_tls_dist.erl index 7367b5c224..a4f8bb7562 100644 --- a/lib/ssl/src/inet_tls_dist.erl +++ b/lib/ssl/src/inet_tls_dist.erl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2011-2012. All Rights Reserved. +%% Copyright Ericsson AB 2011-2018. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. +%% 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 %% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% 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% %% @@ -20,119 +21,585 @@ %% -module(inet_tls_dist). --export([childspecs/0, listen/1, accept/1, accept_connection/5, +-export([childspecs/0]). +-export([listen/1, accept/1, accept_connection/5, setup/5, close/1, select/1, is_node_name/1]). +%% Generalized dist API +-export([gen_listen/2, gen_accept/2, gen_accept_connection/6, + gen_setup/6, gen_close/2, gen_select/2]). + +-export([nodelay/0]). + +-export([verify_client/3, cert_nodes/1]). + +-export([dbg/0]). % Debug + -include_lib("kernel/include/net_address.hrl"). -include_lib("kernel/include/dist.hrl"). -include_lib("kernel/include/dist_util.hrl"). +-include_lib("public_key/include/public_key.hrl"). + +-include("ssl_api.hrl"). + +%% ------------------------------------------------------------------------- childspecs() -> {ok, [{ssl_dist_sup,{ssl_dist_sup, start_link, []}, - permanent, 2000, worker, [ssl_dist_sup]}]}. + permanent, infinity, supervisor, [ssl_dist_sup]}]}. select(Node) -> - case split_node(atom_to_list(Node), $@, []) of - [_,_Host] -> - true; - _ -> - false + gen_select(inet_tcp, Node). + +gen_select(Driver, Node) -> + case dist_util:split_node(Node) of + {node,_,Host} -> + case Driver:getaddr(Host) of + {ok, _} -> true; + _ -> false + end; + _ -> + false end. -is_node_name(Node) when is_atom(Node) -> - select(Node); -is_node_name(_) -> - false. +%% ------------------------------------------------------------------------- + +is_node_name(Node) -> + dist_util:is_node_name(Node). + +%% ------------------------------------------------------------------------- + +hs_data_common(#sslsocket{pid = [_, DistCtrl|_]} = SslSocket) -> + #hs_data{ + f_send = + fun (_Ctrl, Packet) -> + f_send(SslSocket, Packet) + end, + f_recv = + fun (_, Length, Timeout) -> + f_recv(SslSocket, Length, Timeout) + end, + f_setopts_pre_nodeup = + fun (Ctrl) when Ctrl == DistCtrl -> + f_setopts_pre_nodeup(SslSocket) + end, + f_setopts_post_nodeup = + fun (Ctrl) when Ctrl == DistCtrl -> +%%% sys:trace(Ctrl, true), + f_setopts_post_nodeup(SslSocket) + end, + f_getll = + fun (Ctrl) when Ctrl == DistCtrl -> + f_getll(DistCtrl) + end, + f_address = + fun (Ctrl, Node) when Ctrl == DistCtrl -> + f_address(SslSocket, Node) + end, + mf_tick = + fun (Ctrl) when Ctrl == DistCtrl -> + mf_tick(DistCtrl) + end, + mf_getstat = + fun (Ctrl) when Ctrl == DistCtrl -> + mf_getstat(SslSocket) + end, + mf_setopts = + fun (Ctrl, Opts) when Ctrl == DistCtrl -> + mf_setopts(SslSocket, Opts) + end, + mf_getopts = + fun (Ctrl, Opts) when Ctrl == DistCtrl -> + mf_getopts(SslSocket, Opts) + end, + f_handshake_complete = + fun (Ctrl, Node, DHandle) when Ctrl == DistCtrl -> + f_handshake_complete(DistCtrl, Node, DHandle) + end}. + +f_send(SslSocket, Packet) -> + ssl:send(SslSocket, Packet). + +f_recv(SslSocket, Length, Timeout) -> + case ssl:recv(SslSocket, Length, Timeout) of + {ok, Bin} when is_binary(Bin) -> + {ok, binary_to_list(Bin)}; + Other -> + Other + end. + +f_setopts_pre_nodeup(_SslSocket) -> + ok. + +f_setopts_post_nodeup(_SslSocket) -> + ok. + +f_getll(DistCtrl) -> + {ok, DistCtrl}. + +f_address(SslSocket, Node) -> + case ssl:peername(SslSocket) of + {ok, Address} -> + case dist_util:split_node(Node) of + {node,_,Host} -> + #net_address{ + address=Address, host=Host, + protocol=tls, family=inet}; + _ -> + {error, no_node} + end + end. + +mf_tick(DistCtrl) -> + DistCtrl ! tick, + ok. + +mf_getstat(SslSocket) -> + case ssl:getstat( + SslSocket, [recv_cnt, send_cnt, send_pend]) of + {ok, Stat} -> + split_stat(Stat,0,0,0); + Error -> + Error + end. + +mf_setopts(SslSocket, Opts) -> + case setopts_filter(Opts) of + [] -> + ssl:setopts(SslSocket, Opts); + Opts1 -> + {error, {badopts,Opts1}} + end. + +mf_getopts(SslSocket, Opts) -> + ssl:getopts(SslSocket, Opts). + +f_handshake_complete(DistCtrl, Node, DHandle) -> + tls_sender:dist_handshake_complete(DistCtrl, Node, DHandle). + +setopts_filter(Opts) -> + [Opt || {K,_} = Opt <- Opts, + K =:= active orelse K =:= deliver orelse K =:= packet]. + +split_stat([{recv_cnt, R}|Stat], _, W, P) -> + split_stat(Stat, R, W, P); +split_stat([{send_cnt, W}|Stat], R, _, P) -> + split_stat(Stat, R, W, P); +split_stat([{send_pend, P}|Stat], R, W, _) -> + split_stat(Stat, R, W, P); +split_stat([], R, W, P) -> + {ok, R, W, P}. + +%% ------------------------------------------------------------------------- listen(Name) -> - ssl_tls_dist_proxy:listen(Name). + gen_listen(inet_tcp, Name). + +gen_listen(Driver, Name) -> + case inet_tcp_dist:gen_listen(Driver, Name) of + {ok, {Socket, Address, Creation}} -> + inet:setopts(Socket, [{packet, 4}]), + {ok, {Socket, Address#net_address{protocol=tls}, Creation}}; + Other -> + Other + end. + +%% ------------------------------------------------------------------------- accept(Listen) -> - ssl_tls_dist_proxy:accept(Listen). + gen_accept(inet_tcp, Listen). -accept_connection(AcceptPid, Socket, MyNode, Allowed, SetupTime) -> +gen_accept(Driver, Listen) -> Kernel = self(), - spawn_link(fun() -> do_accept(Kernel, AcceptPid, Socket, - MyNode, Allowed, SetupTime) end). + monitor_pid( + spawn_opt( + fun () -> + accept_loop(Driver, Listen, Kernel) + end, + [link, {priority, max}])). + +accept_loop(Driver, Listen, Kernel) -> + case Driver:accept(Listen) of + {ok, Socket} -> + case check_ip(Driver, Socket) of + true -> + accept_loop(Driver, Listen, Kernel, Socket); + {false,IP} -> + error_logger:error_msg( + "** Connection attempt from " + "disallowed IP ~w ** ~n", [IP]), + ?shutdown2(no_node, trace({disallowed, IP})) + end; + Error -> + exit(trace(Error)) + end. + +accept_loop(Driver, Listen, Kernel, Socket) -> + Opts = setup_verify_client(Socket, get_ssl_options(server)), + wait_for_code_server(), + case + ssl:handshake( + Socket, + trace([{active, false},{packet, 4}|Opts]), + net_kernel:connecttime()) + of + {ok, #sslsocket{pid = [_, DistCtrl| _]} = SslSocket} -> + trace( + Kernel ! + {accept, self(), DistCtrl, + Driver:family(), tls}), + receive + {Kernel, controller, Pid} -> + ok = ssl:controlling_process(SslSocket, Pid), + trace( + Pid ! {self(), controller}); + {Kernel, unsupported_protocol} -> + exit(trace(unsupported_protocol)) + end, + accept_loop(Driver, Listen, Kernel); + {error, {options, _}} = Error -> + %% Bad options: that's probably our fault. + %% Let's log that. + error_logger:error_msg( + "Cannot accept TLS distribution connection: ~s~n", + [ssl:format_error(Error)]), + gen_tcp:close(Socket), + exit(trace(Error)); + Other -> + gen_tcp:close(Socket), + exit(trace(Other)) + end. + -setup(Node, Type, MyNode, LongOrShortNames,SetupTime) -> +%% {verify_fun,{fun ?MODULE:verify_client/3,_}} is used +%% as a configuration marker that verify_client/3 shall be used. +%% +%% Replace the State in the first occurence of +%% {verify_fun,{fun ?MODULE:verify_client/3,State}} +%% and remove the rest. +%% The inserted state is not accesible from a configuration file +%% since it is dynamic and connection dependent. +%% +setup_verify_client(Socket, Opts) -> + setup_verify_client(Socket, Opts, true, []). +%% +setup_verify_client(_Socket, [], _, OptsR) -> + lists:reverse(OptsR); +setup_verify_client(Socket, [Opt|Opts], First, OptsR) -> + case Opt of + {verify_fun,{Fun,_}} -> + case Fun =:= fun ?MODULE:verify_client/3 of + true -> + if + First -> + case inet:peername(Socket) of + {ok,{PeerIP,_Port}} -> + {ok,Allowed} = net_kernel:allowed(), + AllowedHosts = allowed_hosts(Allowed), + setup_verify_client( + Socket, Opts, false, + [{verify_fun, + {Fun, {AllowedHosts,PeerIP}}} + |OptsR]); + {error,Reason} -> + exit(trace({no_peername,Reason})) + end; + true -> + setup_verify_client( + Socket, Opts, First, OptsR) + end; + false -> + setup_verify_client( + Socket, Opts, First, [Opt|OptsR]) + end; + _ -> + setup_verify_client(Socket, Opts, First, [Opt|OptsR]) + end. + +allowed_hosts(Allowed) -> + lists:usort(allowed_node_hosts(Allowed)). +%% +allowed_node_hosts([]) -> []; +allowed_node_hosts([Node|Allowed]) -> + case dist_util:split_node(Node) of + {node,_,Host} -> + [Host|allowed_node_hosts(Allowed)]; + {host,Host} -> + [Host|allowed_node_hosts(Allowed)]; + _ -> + allowed_node_hosts(Allowed) + end. + +%% Same as verify_peer but check cert host names for +%% peer IP address +verify_client(_, {bad_cert,_} = Reason, _) -> + {fail,Reason}; +verify_client(_, {extension,_}, S) -> + {unknown,S}; +verify_client(_, valid, S) -> + {valid,S}; +verify_client(_, valid_peer, {[],_} = S) -> + %% Allow all hosts + {valid,S}; +verify_client(PeerCert, valid_peer, {AllowedHosts,PeerIP} = S) -> + case + public_key:pkix_verify_hostname( + PeerCert, + [{ip,PeerIP}|[{dns_id,Host} || Host <- AllowedHosts]]) + of + true -> + {valid,S}; + false -> + {fail,cert_no_hostname_nor_ip_match} + end. + + +wait_for_code_server() -> + %% This is an ugly hack. Upgrading a socket to TLS requires the + %% crypto module to be loaded. Loading the crypto module triggers + %% its on_load function, which calls code:priv_dir/1 to find the + %% directory where its NIF library is. However, distribution is + %% started earlier than the code server, so the code server is not + %% necessarily started yet, and code:priv_dir/1 might fail because + %% of that, if we receive an incoming connection on the + %% distribution port early enough. + %% + %% If the on_load function of a module fails, the module is + %% unloaded, and the function call that triggered loading it fails + %% with 'undef', which is rather confusing. + %% + %% Thus, the accept process will terminate, and be + %% restarted by ssl_dist_sup. However, it won't have any memory + %% of being asked by net_kernel to listen for incoming + %% connections. Hence, the node will believe that it's open for + %% distribution, but it actually isn't. + %% + %% So let's avoid that by waiting for the code server to start. + case whereis(code_server) of + undefined -> + timer:sleep(10), + wait_for_code_server(); + Pid when is_pid(Pid) -> + ok + end. + +%% ------------------------------------------------------------------------- + +accept_connection(AcceptPid, DistCtrl, MyNode, Allowed, SetupTime) -> + gen_accept_connection( + inet_tcp, AcceptPid, DistCtrl, MyNode, Allowed, SetupTime). + +gen_accept_connection( + Driver, AcceptPid, DistCtrl, MyNode, Allowed, SetupTime) -> Kernel = self(), - spawn_opt(fun() -> do_setup(Kernel, Node, Type, MyNode, LongOrShortNames, SetupTime) end, [link, {priority, max}]). - -do_setup(Kernel, Node, Type, MyNode, LongOrShortNames, SetupTime) -> - [Name, Address] = splitnode(Node, LongOrShortNames), - case inet:getaddr(Address, inet) of - {ok, Ip} -> + monitor_pid( + spawn_opt( + fun() -> + do_accept( + Driver, AcceptPid, DistCtrl, + MyNode, Allowed, SetupTime, Kernel) + end, + [link, {priority, max}])). + +do_accept( + _Driver, AcceptPid, DistCtrl, MyNode, Allowed, SetupTime, Kernel) -> + {ok, SslSocket} = tls_sender:dist_tls_socket(DistCtrl), + receive + {AcceptPid, controller} -> Timer = dist_util:start_timer(SetupTime), - case erl_epmd:port_please(Name, Ip) of + NewAllowed = allowed_nodes(SslSocket, Allowed), + HSData0 = hs_data_common(SslSocket), + HSData = + HSData0#hs_data{ + kernel_pid = Kernel, + this_node = MyNode, + socket = DistCtrl, + timer = Timer, + this_flags = 0, + allowed = NewAllowed}, + link(DistCtrl), + dist_util:handshake_other_started(trace(HSData)) + end. + +allowed_nodes(_SslSocket, []) -> + %% Allow all + []; +allowed_nodes(SslSocket, Allowed) -> + case ssl:peercert(SslSocket) of + {ok,PeerCertDER} -> + case ssl:peername(SslSocket) of + {ok,{PeerIP,_Port}} -> + PeerCert = + public_key:pkix_decode_cert(PeerCertDER, otp), + case + allowed_nodes( + PeerCert, allowed_hosts(Allowed), PeerIP) + of + [] -> + error_logger:error_msg( + "** Connection attempt from " + "disallowed node(s) ~p ** ~n", [PeerIP]), + ?shutdown2( + PeerIP, trace({is_allowed, not_allowed})); + AllowedNodes -> + AllowedNodes + end; + Error1 -> + ?shutdown2(no_peer_ip, trace(Error1)) + end; + {error,no_peercert} -> + Allowed; + Error2 -> + ?shutdown2(no_peer_cert, trace(Error2)) + end. + +allowed_nodes(PeerCert, [], PeerIP) -> + case public_key:pkix_verify_hostname(PeerCert, [{ip,PeerIP}]) of + true -> + Host = inet:ntoa(PeerIP), + true = is_list(Host), + [Host]; + false -> + [] + end; +allowed_nodes(PeerCert, [Node|Allowed], PeerIP) -> + case dist_util:split_node(Node) of + {node,_,Host} -> + allowed_nodes(PeerCert, Allowed, PeerIP, Node, Host); + {host,Host} -> + allowed_nodes(PeerCert, Allowed, PeerIP, Node, Host); + _ -> + allowed_nodes(PeerCert, Allowed, PeerIP) + end. + +allowed_nodes(PeerCert, Allowed, PeerIP, Node, Host) -> + case public_key:pkix_verify_hostname(PeerCert, [{dns_id,Host}]) of + true -> + [Node|allowed_nodes(PeerCert, Allowed, PeerIP)]; + false -> + allowed_nodes(PeerCert, Allowed, PeerIP) + end. + + + +setup(Node, Type, MyNode, LongOrShortNames, SetupTime) -> + gen_setup(inet_tcp, Node, Type, MyNode, LongOrShortNames, SetupTime). + +gen_setup(Driver, Node, Type, MyNode, LongOrShortNames, SetupTime) -> + Kernel = self(), + monitor_pid( + spawn_opt( + fun() -> + do_setup( + Driver, Kernel, Node, Type, + MyNode, LongOrShortNames, SetupTime) + end, + [link, {priority, max}])). + +do_setup(Driver, Kernel, Node, Type, MyNode, LongOrShortNames, SetupTime) -> + {Name, Address} = split_node(Driver, Node, LongOrShortNames), + ErlEpmd = net_kernel:epmd_module(), + {ARMod, ARFun} = get_address_resolver(ErlEpmd, Driver), + Timer = trace(dist_util:start_timer(SetupTime)), + case ARMod:ARFun(Name,Address,Driver:family()) of + {ok, Ip, TcpPort, Version} -> + do_setup_connect(Driver, Kernel, Node, Address, Ip, TcpPort, Version, Type, MyNode, Timer); + {ok, Ip} -> + case ErlEpmd:port_please(Name, Ip) of {port, TcpPort, Version} -> - ?trace("port_please(~p) -> version ~p~n", - [Node,Version]), - dist_util:reset_timer(Timer), - case ssl_tls_dist_proxy:connect(Ip, TcpPort) of - {ok, Socket} -> - HSData = connect_hs_data(Kernel, Node, MyNode, Socket, - Timer, Version, Ip, TcpPort, Address, - Type), - dist_util:handshake_we_started(HSData); - _ -> - %% Other Node may have closed since - %% port_please ! - ?trace("other node (~p) " - "closed since port_please.~n", - [Node]), - ?shutdown(Node) - end; - _ -> - ?trace("port_please (~p) " - "failed.~n", [Node]), - ?shutdown(Node) + do_setup_connect(Driver, Kernel, Node, Address, Ip, TcpPort, Version, Type, MyNode, Timer); + Other -> + ?shutdown2( + Node, + trace( + {port_please_failed, ErlEpmd, Name, Ip, Other})) end; - _Other -> - ?trace("inet_getaddr(~p) " - "failed (~p).~n", [Node,Other]), - ?shutdown(Node) + Other -> + ?shutdown2( + Node, + trace({getaddr_failed, Driver, Address, Other})) + end. + +do_setup_connect(Driver, Kernel, Node, Address, Ip, TcpPort, Version, Type, MyNode, Timer) -> + Opts = trace(connect_options(get_ssl_options(client))), + dist_util:reset_timer(Timer), + case ssl:connect( + Address, TcpPort, + [binary, {active, false}, {packet, 4}, + Driver:family(), nodelay()] ++ Opts, + net_kernel:connecttime()) of + {ok, #sslsocket{pid = [_, DistCtrl| _]} = SslSocket} -> + _ = monitor_pid(DistCtrl), + ok = ssl:controlling_process(SslSocket, self()), + HSData0 = hs_data_common(SslSocket), + HSData = + HSData0#hs_data{ + kernel_pid = Kernel, + other_node = Node, + this_node = MyNode, + socket = DistCtrl, + timer = Timer, + this_flags = 0, + other_version = Version, + request_type = Type}, + link(DistCtrl), + dist_util:handshake_we_started(trace(HSData)); + Other -> + %% Other Node may have closed since + %% port_please ! + ?shutdown2( + Node, + trace( + {ssl_connect_failed, Ip, TcpPort, Other})) end. close(Socket) -> - gen_tcp:close(Socket), - ok. + gen_close(inet, Socket). -do_accept(Kernel, AcceptPid, Socket, MyNode, Allowed, SetupTime) -> - process_flag(priority, max), - receive - {AcceptPid, controller} -> - Timer = dist_util:start_timer(SetupTime), - case check_ip(Socket) of - true -> - HSData = accept_hs_data(Kernel, MyNode, Socket, Timer, Allowed), - dist_util:handshake_other_started(HSData); - {false,IP} -> - error_logger:error_msg("** Connection attempt from " - "disallowed IP ~w ** ~n", [IP]), - ?shutdown(no_node) - end +gen_close(Driver, Socket) -> + trace(Driver:close(Socket)). + + +%% ------------------------------------------------------------ +%% Determine if EPMD module supports address resolving. Default +%% is to use inet_tcp:getaddr/2. +%% ------------------------------------------------------------ +get_address_resolver(EpmdModule, Driver) -> + case erlang:function_exported(EpmdModule, address_please, 3) of + true -> {EpmdModule, address_please}; + _ -> {erl_epmd, address_please} end. + %% ------------------------------------------------------------ %% Do only accept new connection attempts from nodes at our %% own LAN, if the check_ip environment parameter is true. %% ------------------------------------------------------------ -check_ip(Socket) -> +check_ip(Driver, Socket) -> case application:get_env(check_ip) of {ok, true} -> case get_ifs(Socket) of {ok, IFs, IP} -> - check_ip(IFs, IP); - _ -> - ?shutdown(no_node) + check_ip(Driver, IFs, IP); + Other -> + ?shutdown2( + no_node, trace({check_ip_failed, Socket, Other})) end; _ -> true end. +check_ip(Driver, [{OwnIP, _, Netmask}|IFs], PeerIP) -> + case {Driver:mask(Netmask, PeerIP), Driver:mask(Netmask, OwnIP)} of + {M, M} -> true; + _ -> check_ip(IFs, PeerIP) + end; +check_ip(_Driver, [], PeerIP) -> + {false, PeerIP}. + get_ifs(Socket) -> case inet:peername(Socket) of {ok, {IP, _}} -> + %% XXX this is seriously broken for IPv6 case inet:getif(Socket) of {ok, IFs} -> {ok, IFs, IP}; Error -> Error @@ -141,136 +608,262 @@ get_ifs(Socket) -> Error end. -check_ip([{OwnIP, _, Netmask}|IFs], PeerIP) -> - case {mask(Netmask, PeerIP), mask(Netmask, OwnIP)} of - {M, M} -> true; - _ -> check_ip(IFs, PeerIP) + +%% Look in Extensions, in all subjectAltName:s +%% to find node names in this certificate. +%% Host names are picked up as a subjectAltName containing +%% a dNSName, and the first subjectAltName containing +%% a commonName is the node name. +%% +cert_nodes( + #'OTPCertificate'{ + tbsCertificate = #'OTPTBSCertificate'{extensions = Extensions}}) -> + parse_extensions(Extensions). + + +parse_extensions(Extensions) when is_list(Extensions) -> + parse_extensions(Extensions, [], []); +parse_extensions(asn1_NOVALUE) -> + undefined. % Allow all nodes +%% +parse_extensions([], [], []) -> + undefined; % Allow all nodes +parse_extensions([], Hosts, []) -> + lists:reverse(Hosts); +parse_extensions([], [], Names) -> + [Name ++ "@" || Name <- lists:reverse(Names)]; +parse_extensions([], Hosts, Names) -> + [Name ++ "@" ++ Host || + Host <- lists:reverse(Hosts), + Name <- lists:reverse(Names)]; +parse_extensions( + [#'Extension'{ + extnID = ?'id-ce-subjectAltName', + extnValue = AltNames} + |Extensions], + Hosts, Names) -> + case parse_subject_altname(AltNames) of + none -> + parse_extensions(Extensions, Hosts, Names); + {host,Host} -> + parse_extensions(Extensions, [Host|Hosts], Names); + {name,Name} -> + parse_extensions(Extensions, Hosts, [Name|Names]) end; -check_ip([], PeerIP) -> - {false, PeerIP}. +parse_extensions([_|Extensions], Hosts, Names) -> + parse_extensions(Extensions, Hosts, Names). -mask({M1,M2,M3,M4}, {IP1,IP2,IP3,IP4}) -> - {M1 band IP1, - M2 band IP2, - M3 band IP3, - M4 band IP4}; +parse_subject_altname([]) -> + none; +parse_subject_altname([{dNSName,Host}|_AltNames]) -> + {host,Host}; +parse_subject_altname( + [{directoryName,{rdnSequence,[Rdn|_]}}|AltNames]) -> + %% + %% XXX Why is rdnSequence a sequence? + %% Should we parse all members? + %% + case parse_rdn(Rdn) of + none -> + parse_subject_altname(AltNames); + Name -> + {name,Name} + end; +parse_subject_altname([_|AltNames]) -> + parse_subject_altname(AltNames). -mask({M1,M2,M3,M4, M5, M6, M7, M8}, {IP1,IP2,IP3,IP4, IP5, IP6, IP7, IP8}) -> - {M1 band IP1, - M2 band IP2, - M3 band IP3, - M4 band IP4, - M5 band IP5, - M6 band IP6, - M7 band IP7, - M8 band IP8}. + +parse_rdn([]) -> + none; +parse_rdn( + [#'AttributeTypeAndValue'{ + type = ?'id-at-commonName', + value = {utf8String,CommonName}}|_]) -> + unicode:characters_to_list(CommonName); +parse_rdn([_|Rdn]) -> + parse_rdn(Rdn). %% If Node is illegal terminate the connection setup!! -splitnode(Node, LongOrShortNames) -> - case split_node(atom_to_list(Node), $@, []) of - [Name|Tail] when Tail =/= [] -> - Host = lists:append(Tail), - check_node(Name, Node, Host, LongOrShortNames); - [_] -> - error_logger:error_msg("** Nodename ~p illegal, no '@' character **~n", - [Node]), - ?shutdown(Node); +split_node(Driver, Node, LongOrShortNames) -> + case dist_util:split_node(Node) of + {node, Name, Host} -> + check_node(Driver, Node, Name, Host, LongOrShortNames); + {host, _} -> + error_logger:error_msg( + "** Nodename ~p illegal, no '@' character **~n", + [Node]), + ?shutdown2(Node, trace({illegal_node_n@me, Node})); _ -> - error_logger:error_msg("** Nodename ~p illegal **~n", [Node]), - ?shutdown(Node) + error_logger:error_msg( + "** Nodename ~p illegal **~n", [Node]), + ?shutdown2(Node, trace({illegal_node_name, Node})) end. -check_node(Name, Node, Host, LongOrShortNames) -> - case split_node(Host, $., []) of - [_] when LongOrShortNames == longnames -> - error_logger:error_msg("** System running to use " - "fully qualified " - "hostnames **~n" - "** Hostname ~s is illegal **~n", - [Host]), - ?shutdown(Node); - [_, _ | _] when LongOrShortNames == shortnames -> - error_logger:error_msg("** System NOT running to use fully qualified " - "hostnames **~n" - "** Hostname ~s is illegal **~n", - [Host]), - ?shutdown(Node); +check_node(Driver, Node, Name, Host, LongOrShortNames) -> + case string:split(Host, ".", all) of + [_] when LongOrShortNames =:= longnames -> + case Driver:parse_address(Host) of + {ok, _} -> + {Name, Host}; + _ -> + error_logger:error_msg( + "** System running to use " + "fully qualified hostnames **~n" + "** Hostname ~s is illegal **~n", + [Host]), + ?shutdown2(Node, trace({not_longnames, Host})) + end; + [_,_|_] when LongOrShortNames =:= shortnames -> + error_logger:error_msg( + "** System NOT running to use " + "fully qualified hostnames **~n" + "** Hostname ~s is illegal **~n", + [Host]), + ?shutdown2(Node, trace({not_shortnames, Host})); _ -> - [Name, Host] + {Name, Host} end. -split_node([Chr|T], Chr, Ack) -> - [lists:reverse(Ack)|split_node(T, Chr, [])]; -split_node([H|T], Chr, Ack) -> - split_node(T, Chr, [H|Ack]); -split_node([], _, Ack) -> - [lists:reverse(Ack)]. - -connect_hs_data(Kernel, Node, MyNode, Socket, Timer, Version, Ip, TcpPort, Address, Type) -> - common_hs_data(Kernel, MyNode, Socket, Timer, - #hs_data{other_node = Node, - other_version = Version, - f_address = - fun(_,_) -> - #net_address{address = {Ip,TcpPort}, - host = Address, - protocol = proxy, - family = inet} - end, - request_type = Type - }). - -accept_hs_data(Kernel, MyNode, Socket, Timer, Allowed) -> - common_hs_data(Kernel, MyNode, Socket, Timer, #hs_data{ - allowed = Allowed, - f_address = fun get_remote_id/2 - }). - -common_hs_data(Kernel, MyNode, Socket, Timer, HsData) -> - HsData#hs_data{ - kernel_pid = Kernel, - this_node = MyNode, - socket = Socket, - timer = Timer, - this_flags = 0, - f_send = - fun(S,D) -> - gen_tcp:send(S,D) - end, - f_recv = - fun(S,N,T) -> - gen_tcp:recv(S,N,T) - end, - f_setopts_pre_nodeup = - fun(S) -> - inet:setopts(S, [{active, false}, {packet, 4}]) - end, - f_setopts_post_nodeup = - fun(S) -> - inet:setopts(S, [{deliver, port},{active, true}]) - end, - f_getll = - fun(S) -> - inet:getll(S) - end, - mf_tick = - fun(S) -> - gen_tcp:send(S, <<>>) - end, - mf_getstat = - fun(S) -> - {ok, Stats} = inet:getstat(S, [recv_cnt, send_cnt, send_pend]), - R = proplists:get_value(recv_cnt, Stats, 0), - W = proplists:get_value(send_cnt, Stats, 0), - P = proplists:get_value(send_pend, Stats, 0), - {ok, R,W,P} - end}. - -get_remote_id(Socket, _Node) -> - case ssl_tls_dist_proxy:get_tcp_address(Socket) of - {ok, Address} -> - Address; - {error, _Reason} -> - ?shutdown(no_node) +%% ------------------------------------------------------------------------- + +connect_options(Opts) -> + case application:get_env(kernel, inet_dist_connect_options) of + {ok,ConnectOpts} -> + lists:ukeysort(1, ConnectOpts ++ Opts); + _ -> + Opts + end. + +%% we may not always want the nodelay behaviour +%% for performance reasons +nodelay() -> + case application:get_env(kernel, dist_nodelay) of + undefined -> + {nodelay, true}; + {ok, true} -> + {nodelay, true}; + {ok, false} -> + {nodelay, false}; + _ -> + {nodelay, true} + end. + + +get_ssl_options(Type) -> + try ets:lookup(ssl_dist_opts, Type) of + [{Type, Opts}] -> + [{erl_dist, true} | Opts]; + _ -> + get_ssl_dist_arguments(Type) + catch + error:badarg -> + get_ssl_dist_arguments(Type) + end. + +get_ssl_dist_arguments(Type) -> + case init:get_argument(ssl_dist_opt) of + {ok, Args} -> + [{erl_dist, true} | ssl_options(Type, lists:append(Args))]; + _ -> + [{erl_dist, true}] + end. + + +ssl_options(_Type, []) -> + []; +ssl_options(client, ["client_" ++ Opt, Value | T] = Opts) -> + ssl_options(client, T, Opts, Opt, Value); +ssl_options(server, ["server_" ++ Opt, Value | T] = Opts) -> + ssl_options(server, T, Opts, Opt, Value); +ssl_options(Type, [_Opt, _Value | T]) -> + ssl_options(Type, T). +%% +ssl_options(Type, T, Opts, Opt, Value) -> + case ssl_option(Type, Opt) of + error -> + error(malformed_ssl_dist_opt, [Type, Opts]); + Fun -> + [{list_to_atom(Opt), Fun(Value)}|ssl_options(Type, T)] end. + +ssl_option(server, Opt) -> + case Opt of + "dhfile" -> fun listify/1; + "fail_if_no_peer_cert" -> fun atomize/1; + _ -> ssl_option(client, Opt) + end; +ssl_option(client, Opt) -> + case Opt of + "certfile" -> fun listify/1; + "cacertfile" -> fun listify/1; + "keyfile" -> fun listify/1; + "password" -> fun listify/1; + "verify" -> fun atomize/1; + "verify_fun" -> fun verify_fun/1; + "crl_check" -> fun atomize/1; + "crl_cache" -> fun termify/1; + "reuse_sessions" -> fun atomize/1; + "secure_renegotiate" -> fun atomize/1; + "depth" -> fun erlang:list_to_integer/1; + "hibernate_after" -> fun erlang:list_to_integer/1; + "ciphers" -> fun listify/1; + _ -> error + end. + +listify(List) when is_list(List) -> + List. + +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. + +%% ------------------------------------------------------------------------- + +%% Trace point +trace(Term) -> Term. + +%% Keep an eye on distribution Pid:s we know of +monitor_pid(Pid) -> + %%spawn( + %% fun () -> + %% MRef = erlang:monitor(process, Pid), + %% receive + %% {'DOWN', MRef, _, _, normal} -> + %% error_logger:error_report( + %% [dist_proc_died, + %% {reason, normal}, + %% {pid, Pid}]); + %% {'DOWN', MRef, _, _, Reason} -> + %% error_logger:info_report( + %% [dist_proc_died, + %% {reason, Reason}, + %% {pid, Pid}]) + %% end + %% end), + Pid. + +dbg() -> + dbg:stop(), + dbg:tracer(), + dbg:p(all, c), + dbg:tpl(?MODULE, cx), + dbg:tpl(erlang, dist_ctrl_get_data_notification, cx), + dbg:tpl(erlang, dist_ctrl_get_data, cx), + dbg:tpl(erlang, dist_ctrl_put_data, cx), + ok. diff --git a/lib/ssl/src/ssl.app.src b/lib/ssl/src/ssl.app.src index 36681e2897..936df12e70 100644 --- a/lib/ssl/src/ssl.app.src +++ b/lib/ssl/src/ssl.app.src @@ -6,18 +6,22 @@ tls_connection, tls_handshake, tls_record, + tls_socket, tls_v1, ssl_v3, - ssl_v2, + tls_connection_sup, + tls_sender, %% DTLS dtls_connection, dtls_handshake, dtls_record, + dtls_socket, dtls_v1, + dtls_connection_sup, + dtls_packet_demux, + dtls_listener_sup, %% API ssl, %% Main API - tls, %% TLS specific - dtls, %% DTLS specific ssl_session_cache_api, %% Both TLS/SSL and DTLS ssl_config, @@ -25,31 +29,37 @@ ssl_handshake, ssl_record, ssl_cipher, + ssl_cipher_format, ssl_srp_primes, ssl_alert, - ssl_socket, - ssl_listen_tracker_sup, + ssl_listen_tracker_sup, %% may be used by DTLS over SCTP %% Erlang Distribution over SSL/TLS inet_tls_dist, - ssl_tls_dist_proxy, + inet6_tls_dist, ssl_dist_sup, - %% SSL/TLS session handling + ssl_dist_connection_sup, + ssl_dist_admin_sup, + %% SSL/TLS session and cert handling ssl_session, ssl_session_cache, ssl_manager, + ssl_pem_cache, ssl_pkix_db, ssl_certificate, + %% CRL handling + ssl_crl, + ssl_crl_cache, + ssl_crl_cache_api, + ssl_crl_hash_dir, %% App structure ssl_app, ssl_sup, - tls_connection_sup, - dtls_connection_sup + ssl_admin_sup, + ssl_connection_sup ]}, {registered, [ssl_sup, ssl_manager]}, {applications, [crypto, public_key, kernel, stdlib]}, {env, []}, {mod, {ssl_app, []}}, - {runtime_dependencies, ["stdlib-2.0","public_key-0.22","kernel-3.0", - "erts-6.0","crypto-3.3"]}]}. - - + {runtime_dependencies, ["stdlib-3.5","public_key-1.5","kernel-6.0", + "erts-10.0","crypto-4.2", "inets-5.10.7"]}]}. diff --git a/lib/ssl/src/ssl.appup.src b/lib/ssl/src/ssl.appup.src index 4c4163d7fd..ae4d60b6ed 100644 --- a/lib/ssl/src/ssl.appup.src +++ b/lib/ssl/src/ssl.appup.src @@ -1,22 +1,21 @@ %% -*- erlang -*- {"%VSN%", - [ - {<<"6.0.1">>, [{load_module, ssl_cipher, soft_purge, soft_purge, []}]}, - {<<"6.0">>, [{load_module, ssl_cipher, soft_purge, soft_purge, []}, - {load_module, ssl_handshake, soft_purge, soft_purge, []}]}, - {<<"5\\.3\\.[1-7]($|\\..*)">>, [{restart_application, ssl}]}, - {<<"5\\.[0-2]($|\\..*)">>, [{restart_application, ssl}]}, +[ + {<<"9\\..*">>, [{restart_application, ssl}]}, + {<<"8\\..*">>, [{restart_application, ssl}]}, + {<<"7\\..*">>, [{restart_application, ssl}]}, + {<<"6\\..*">>, [{restart_application, ssl}]}, + {<<"5\\..*">>, [{restart_application, ssl}]}, {<<"4\\..*">>, [{restart_application, ssl}]}, {<<"3\\..*">>, [{restart_application, ssl}]} ], [ - {<<"6.0.1">>, [{load_module, ssl_cipher, soft_purge, soft_purge, []}]}, - {<<"6.0">>, [{load_module, ssl_cipher, soft_purge, soft_purge, []}, - {load_module, ssl_handshake, soft_purge, soft_purge, []}]}, - {<<"5\\.3\\.[1-7]($|\\..*)">>, [{restart_application, ssl}]}, - {<<"5\\.[0-2]($|\\..*)">>, [{restart_application, ssl}]}, + {<<"9\\..*">>, [{restart_application, ssl}]}, + {<<"8\\..*">>, [{restart_application, ssl}]}, + {<<"7\\..*">>, [{restart_application, ssl}]}, + {<<"6\\..*">>, [{restart_application, ssl}]}, + {<<"5\\..*">>, [{restart_application, ssl}]}, {<<"4\\..*">>, [{restart_application, ssl}]}, {<<"3\\..*">>, [{restart_application, ssl}]} ] }. - diff --git a/lib/ssl/src/ssl.erl b/lib/ssl/src/ssl.erl index 5f4ad7f013..03a1e40bfc 100644 --- a/lib/ssl/src/ssl.erl +++ b/lib/ssl/src/ssl.erl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1999-2015. All Rights Reserved. +%% Copyright Ericsson AB 1999-2018. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. +%% 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 %% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% 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% %% @@ -22,55 +23,61 @@ %%% Purpose : Main API module for SSL see also tls.erl and dtls.erl -module(ssl). --include("ssl_internal.hrl"). + -include_lib("public_key/include/public_key.hrl"). +-include("ssl_internal.hrl"). +-include("ssl_api.hrl"). +-include("ssl_internal.hrl"). +-include("ssl_record.hrl"). +-include("ssl_cipher.hrl"). +-include("ssl_handshake.hrl"). +-include("ssl_srp.hrl"). + %% Application handling -export([start/0, start/1, stop/0, clear_pem_cache/0]). %% Socket handling -export([connect/3, connect/2, connect/4, listen/2, transport_accept/1, transport_accept/2, - ssl_accept/1, ssl_accept/2, ssl_accept/3, + handshake/1, handshake/2, handshake/3, handshake_continue/2, + handshake_continue/3, handshake_cancel/1, + ssl_accept/1, ssl_accept/2, ssl_accept/3, controlling_process/2, peername/1, peercert/1, sockname/1, - close/1, 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, suite_definition/1, - connection_info/1, versions/0, session_info/1, format_error/1, - renegotiate/1, prf/5, negotiated_next_protocol/1]). +-export([cipher_suites/0, cipher_suites/1, cipher_suites/2, filter_cipher_suites/2, + prepend_cipher_suites/2, append_cipher_suites/2, + eccs/0, eccs/1, versions/0, + format_error/1, renegotiate/1, prf/5, negotiated_protocol/1, + connection_information/1, connection_information/2]). %% Misc --export([random_bytes/1]). +-export([handle_options/2, tls_version/1, new_ssl_options/3, suite_to_str/1]). --include("ssl_api.hrl"). --include("ssl_internal.hrl"). --include("ssl_record.hrl"). --include("ssl_cipher.hrl"). --include("ssl_handshake.hrl"). --include("ssl_srp.hrl"). - --include_lib("public_key/include/public_key.hrl"). +-deprecated({ssl_accept, 1, eventually}). +-deprecated({ssl_accept, 2, eventually}). +-deprecated({ssl_accept, 3, eventually}). %%-------------------------------------------------------------------- -spec start() -> ok | {error, reason()}. -spec start(permanent | transient | temporary) -> ok | {error, reason()}. %% -%% Description: Utility function that starts the ssl, -%% crypto and public_key applications. Default type -%% is temporary. see application(3) +%% Description: Utility function that starts the ssl and applications +%% that it depends on. +%% see application(3) %%-------------------------------------------------------------------- start() -> - application:start(crypto), - application:start(asn1), - application:start(public_key), - application:start(ssl). - + start(temporary). start(Type) -> - application:start(crypto, Type), - application:start(asn1), - application:start(public_key, Type), - application:start(ssl, Type). - + case application:ensure_all_started(ssl, Type) of + {ok, _} -> + ok; + Other -> + Other + end. %%-------------------------------------------------------------------- -spec stop() -> ok. %% @@ -94,36 +101,31 @@ stop() -> connect(Socket, SslOptions) when is_port(Socket) -> connect(Socket, SslOptions, infinity). -connect(Socket, SslOptions0, Timeout) when is_port(Socket) -> +connect(Socket, SslOptions0, Timeout) when is_port(Socket), + (is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity) -> {Transport,_,_,_} = proplists:get_value(cb_info, SslOptions0, {gen_tcp, tcp, tcp_closed, tcp_error}), - EmulatedOptions = ssl_socket:emulated_options(), - {ok, SocketValues} = ssl_socket:getopts(Transport, Socket, EmulatedOptions), - try handle_options(SslOptions0 ++ SocketValues) of - {ok, #config{transport_info = CbInfo, ssl = SslOptions, emulated = EmOpts, - connection_cb = ConnectionCb}} -> - - ok = ssl_socket:setopts(Transport, Socket, ssl_socket:internal_inet_values()), - case ssl_socket:peername(Transport, Socket) of - {ok, {Address, Port}} -> - ssl_connection:connect(ConnectionCb, Address, Port, Socket, - {SslOptions, emulated_socket_options(EmOpts, #socket_options{}), undefined}, - self(), CbInfo, Timeout); - {error, Error} -> - {error, Error} - end + EmulatedOptions = tls_socket:emulated_options(), + {ok, SocketValues} = tls_socket:getopts(Transport, Socket, EmulatedOptions), + try handle_options(SslOptions0 ++ SocketValues, client) of + {ok, Config} -> + tls_socket:upgrade(Socket, Config, Timeout) catch _:{error, Reason} -> {error, Reason} end; - connect(Host, Port, Options) -> connect(Host, Port, Options, infinity). -connect(Host, Port, Options, Timeout) -> - try handle_options(Options) of - {ok, Config} -> - do_connect(Host,Port,Config,Timeout) +connect(Host, Port, Options, Timeout) when (is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity) -> + try + {ok, Config} = handle_options(Options, client, Host), + case Config#config.connection_cb of + tls_connection -> + tls_socket:connect(Host,Port,Config,Timeout); + dtls_connection -> + dtls_socket:connect(Host,Port,Config,Timeout) + end catch throw:Error -> Error @@ -139,18 +141,8 @@ listen(_Port, []) -> {error, nooptions}; listen(Port, Options0) -> try - {ok, Config} = handle_options(Options0), - ConnectionCb = connection_cb(Options0), - #config{transport_info = {Transport, _, _, _}, inet_user = Options, connection_cb = ConnectionCb, - ssl = SslOpts, emulated = EmOpts} = Config, - case Transport:listen(Port, Options) of - {ok, ListenSocket} -> - ok = ssl_socket:setopts(Transport, ListenSocket, ssl_socket:internal_inet_values()), - {ok, Tracker} = ssl_socket:inherit_tracker(ListenSocket, EmOpts, SslOpts), - {ok, #sslsocket{pid = {ListenSocket, Config#config{emulated = Tracker}}}}; - Err = {error, _} -> - Err - end + {ok, Config} = handle_options(Options0, server), + do_listen(Port, Config, connection_cb(Options0)) catch Error = {error, _} -> Error @@ -167,27 +159,15 @@ transport_accept(ListenSocket) -> transport_accept(ListenSocket, infinity). transport_accept(#sslsocket{pid = {ListenSocket, - #config{transport_info = {Transport,_,_, _} =CbInfo, - connection_cb = ConnectionCb, - ssl = SslOpts, - emulated = Tracker}}}, Timeout) -> - case Transport:accept(ListenSocket, Timeout) of - {ok, Socket} -> - {ok, EmOpts} = ssl_socket:get_emulated_opts(Tracker), - {ok, Port} = ssl_socket:port(Transport, Socket), - ConnArgs = [server, "localhost", Port, Socket, - {SslOpts, emulated_socket_options(EmOpts, #socket_options{}), Tracker}, self(), CbInfo], - ConnectionSup = connection_sup(ConnectionCb), - case ConnectionSup:start_child(ConnArgs) of - {ok, Pid} -> - ssl_connection:socket_control(ConnectionCb, Socket, Pid, Transport, Tracker); - {error, Reason} -> - {error, Reason} - end; - {error, Reason} -> - {error, Reason} + #config{connection_cb = ConnectionCb} = Config}}, Timeout) + when (is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity) -> + case ConnectionCb of + tls_connection -> + tls_socket:accept(ListenSocket, Config, Timeout); + dtls_connection -> + dtls_socket:accept(ListenSocket, Config, Timeout) end. - + %%-------------------------------------------------------------------- -spec ssl_accept(#sslsocket{}) -> ok | {error, reason()}. -spec ssl_accept(#sslsocket{} | port(), timeout()| [ssl_option() @@ -195,64 +175,158 @@ transport_accept(#sslsocket{pid = {ListenSocket, ok | {ok, #sslsocket{}} | {error, reason()}. -spec ssl_accept(#sslsocket{} | port(), [ssl_option()] | [ssl_option()| transport_option()], timeout()) -> - {ok, #sslsocket{}} | {error, reason()}. + ok | {ok, #sslsocket{}} | {error, reason()}. %% %% Description: Performs accept on an ssl listen socket. e.i. performs %% ssl handshake. %%-------------------------------------------------------------------- ssl_accept(ListenSocket) -> - ssl_accept(ListenSocket, infinity). + ssl_accept(ListenSocket, [], infinity). +ssl_accept(Socket, Timeout) when (is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity) -> + ssl_accept(Socket, [], Timeout); +ssl_accept(ListenSocket, SslOptions) when is_port(ListenSocket) -> + ssl_accept(ListenSocket, SslOptions, infinity); +ssl_accept(Socket, Timeout) -> + ssl_accept(Socket, [], Timeout). +ssl_accept(Socket, SslOptions, Timeout) when is_port(Socket) -> + handshake(Socket, SslOptions, Timeout); +ssl_accept(Socket, SslOptions, Timeout) -> + case handshake(Socket, SslOptions, Timeout) of + {ok, _} -> + ok; + Error -> + Error + end. +%%-------------------------------------------------------------------- +-spec handshake(#sslsocket{}) -> {ok, #sslsocket{}} | {error, reason()}. +-spec handshake(#sslsocket{} | port(), timeout()| [ssl_option() + | transport_option()]) -> + {ok, #sslsocket{}} | {error, reason()}. -ssl_accept(#sslsocket{} = Socket, Timeout) -> +-spec handshake(#sslsocket{} | port(), [ssl_option()] | [ssl_option()| transport_option()], timeout()) -> + {ok, #sslsocket{}} | {error, reason()}. +%% +%% Description: Performs accept on an ssl listen socket. e.i. performs +%% ssl handshake. +%%-------------------------------------------------------------------- +handshake(ListenSocket) -> + handshake(ListenSocket, infinity). + +handshake(#sslsocket{} = Socket, Timeout) when (is_integer(Timeout) andalso Timeout >= 0) or + (Timeout == infinity) -> ssl_connection:handshake(Socket, Timeout); - -ssl_accept(ListenSocket, SslOptions) when is_port(ListenSocket) -> - ssl_accept(ListenSocket, SslOptions, infinity). - -ssl_accept(#sslsocket{} = Socket, [], Timeout) -> - ssl_accept(#sslsocket{} = Socket, Timeout); -ssl_accept(#sslsocket{fd = {_, _, _, Tracker}} = Socket, SslOpts0, Timeout) -> - try - {ok, EmOpts, InheritedSslOpts} = ssl_socket:get_all_opts(Tracker), - SslOpts = handle_options(SslOpts0, InheritedSslOpts), - ssl_connection:handshake(Socket, {SslOpts, emulated_socket_options(EmOpts, #socket_options{})}, Timeout) + +handshake(ListenSocket, SslOptions) when is_port(ListenSocket) -> + handshake(ListenSocket, SslOptions, infinity). + +handshake(#sslsocket{} = Socket, [], Timeout) when (is_integer(Timeout) andalso Timeout >= 0) or + (Timeout == infinity)-> + handshake(Socket, Timeout); +handshake(#sslsocket{fd = {_, _, _, Tracker}} = Socket, SslOpts, Timeout) when + (is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity)-> + try + {ok, EmOpts, _} = tls_socket:get_all_opts(Tracker), + ssl_connection:handshake(Socket, {SslOpts, + tls_socket:emulated_socket_options(EmOpts, #socket_options{})}, Timeout) + catch + Error = {error, _Reason} -> Error + end; +handshake(#sslsocket{pid = [Pid|_], fd = {_, _, _}} = Socket, SslOpts, Timeout) when + (is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity)-> + try + {ok, EmOpts, _} = dtls_packet_demux:get_all_opts(Pid), + ssl_connection:handshake(Socket, {SslOpts, + tls_socket:emulated_socket_options(EmOpts, #socket_options{})}, Timeout) catch Error = {error, _Reason} -> Error end; -ssl_accept(Socket, SslOptions, Timeout) when is_port(Socket) -> +handshake(Socket, SslOptions, Timeout) when is_port(Socket), + (is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity) -> {Transport,_,_,_} = proplists:get_value(cb_info, SslOptions, {gen_tcp, tcp, tcp_closed, tcp_error}), - EmulatedOptions = ssl_socket:emulated_options(), - {ok, SocketValues} = ssl_socket:getopts(Transport, Socket, EmulatedOptions), + EmulatedOptions = tls_socket:emulated_options(), + {ok, SocketValues} = tls_socket:getopts(Transport, Socket, EmulatedOptions), ConnetionCb = connection_cb(SslOptions), - try handle_options(SslOptions ++ SocketValues) of + try handle_options(SslOptions ++ SocketValues, server) of {ok, #config{transport_info = CbInfo, ssl = SslOpts, emulated = EmOpts}} -> - ok = ssl_socket:setopts(Transport, Socket, ssl_socket:internal_inet_values()), - {ok, Port} = ssl_socket:port(Transport, Socket), - ssl_connection:ssl_accept(ConnetionCb, Port, Socket, - {SslOpts, emulated_socket_options(EmOpts, #socket_options{}), undefined}, - self(), CbInfo, Timeout) + ok = tls_socket:setopts(Transport, Socket, tls_socket:internal_inet_values()), + {ok, Port} = tls_socket:port(Transport, Socket), + ssl_connection:handshake(ConnetionCb, Port, Socket, + {SslOpts, + tls_socket:emulated_socket_options(EmOpts, #socket_options{}), undefined}, + self(), CbInfo, Timeout) catch Error = {error, _Reason} -> Error end. + +%%-------------------------------------------------------------------- +-spec handshake_continue(#sslsocket{}, [ssl_option()]) -> + {ok, #sslsocket{}} | {error, reason()}. +%% +%% +%% Description: Continues the handshke possible with newly supplied options. +%%-------------------------------------------------------------------- +handshake_continue(Socket, SSLOptions) -> + handshake_continue(Socket, SSLOptions, infinity). +%%-------------------------------------------------------------------- +-spec handshake_continue(#sslsocket{}, [ssl_option()], timeout()) -> + {ok, #sslsocket{}} | {error, reason()}. +%% +%% +%% Description: Continues the handshke possible with newly supplied options. +%%-------------------------------------------------------------------- +handshake_continue(Socket, SSLOptions, Timeout) -> + ssl_connection:handshake_continue(Socket, SSLOptions, Timeout). +%%-------------------------------------------------------------------- +-spec handshake_cancel(#sslsocket{}) -> term(). +%% +%% Description: Cancels the handshakes sending a close alert. +%%-------------------------------------------------------------------- +handshake_cancel(Socket) -> + ssl_connection:handshake_cancel(Socket). + %%-------------------------------------------------------------------- -spec close(#sslsocket{}) -> term(). %% %% Description: Close an ssl connection %%-------------------------------------------------------------------- -close(#sslsocket{pid = Pid}) when is_pid(Pid) -> - ssl_connection:close(Pid); +close(#sslsocket{pid = [Pid|_]}) when is_pid(Pid) -> + ssl_connection:close(Pid, {close, ?DEFAULT_TIMEOUT}); +close(#sslsocket{pid = {dtls, #config{dtls_handler = {Pid, _}}}}) -> + dtls_packet_demux:close(Pid); close(#sslsocket{pid = {ListenSocket, #config{transport_info={Transport,_, _, _}}}}) -> Transport:close(ListenSocket). %%-------------------------------------------------------------------- +-spec close(#sslsocket{}, timeout() | {pid(), integer()}) -> term(). +%% +%% Description: Close an ssl connection +%%-------------------------------------------------------------------- +close(#sslsocket{pid = [TLSPid|_]}, + {Pid, Timeout} = DownGrade) when is_pid(TLSPid), + is_pid(Pid), + (is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity) -> + ssl_connection:close(TLSPid, {close, DownGrade}); +close(#sslsocket{pid = [TLSPid|_]}, Timeout) when is_pid(TLSPid), + (is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity) -> + ssl_connection:close(TLSPid, {close, Timeout}); +close(#sslsocket{pid = {ListenSocket, #config{transport_info={Transport,_, _, _}}}}, _) -> + Transport:close(ListenSocket). + +%%-------------------------------------------------------------------- -spec send(#sslsocket{}, iodata()) -> ok | {error, reason()}. %% %% Description: Sends data over the ssl connection %%-------------------------------------------------------------------- -send(#sslsocket{pid = Pid}, Data) when is_pid(Pid) -> +send(#sslsocket{pid = [Pid]}, Data) when is_pid(Pid) -> ssl_connection:send(Pid, Data); +send(#sslsocket{pid = [_, Pid]}, Data) when is_pid(Pid) -> + tls_sender:send_data(Pid, erlang:iolist_to_binary(Data)); +send(#sslsocket{pid = {_, #config{transport_info={_, udp, _, _}}}}, _) -> + {error,enotconn}; %% Emulate connection behaviour +send(#sslsocket{pid = {dtls,_}}, _) -> + {error,enotconn}; %% Emulate connection behaviour send(#sslsocket{pid = {ListenSocket, #config{transport_info={Transport, _, _, _}}}}, Data) -> Transport:send(ListenSocket, Data). %% {error,enotconn} @@ -264,8 +338,11 @@ send(#sslsocket{pid = {ListenSocket, #config{transport_info={Transport, _, _, _} %%-------------------------------------------------------------------- recv(Socket, Length) -> recv(Socket, Length, infinity). -recv(#sslsocket{pid = Pid}, Length, Timeout) when is_pid(Pid) -> +recv(#sslsocket{pid = [Pid|_]}, Length, Timeout) when is_pid(Pid), + (is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity)-> ssl_connection:recv(Pid, Length, Timeout); +recv(#sslsocket{pid = {dtls,_}}, _, _) -> + {error,enotconn}; recv(#sslsocket{pid = {Listen, #config{transport_info = {Transport, _, _, _}}}}, _,_) when is_port(Listen)-> Transport:recv(Listen, 0). %% {error,enotconn} @@ -276,91 +353,219 @@ recv(#sslsocket{pid = {Listen, %% Description: Changes process that receives the messages when active = true %% or once. %%-------------------------------------------------------------------- -controlling_process(#sslsocket{pid = Pid}, NewOwner) when is_pid(Pid), is_pid(NewOwner) -> +controlling_process(#sslsocket{pid = [Pid|_]}, NewOwner) when is_pid(Pid), is_pid(NewOwner) -> ssl_connection:new_user(Pid, NewOwner); +controlling_process(#sslsocket{pid = {dtls, _}}, + NewOwner) when is_pid(NewOwner) -> + ok; %% Meaningless but let it be allowed to conform with TLS controlling_process(#sslsocket{pid = {Listen, #config{transport_info = {Transport, _, _, _}}}}, NewOwner) when is_port(Listen), is_pid(NewOwner) -> + %% Meaningless but let it be allowed to conform with normal sockets Transport:controlling_process(Listen, NewOwner). + %%-------------------------------------------------------------------- --spec connection_info(#sslsocket{}) -> {ok, {tls_record:tls_atom_version(), ssl_cipher:erl_cipher_suite()}} | - {error, reason()}. +-spec connection_information(#sslsocket{}) -> {ok, list()} | {error, reason()}. %% -%% Description: Returns ssl protocol and cipher used for the connection +%% Description: Return SSL information for the connection %%-------------------------------------------------------------------- -connection_info(#sslsocket{pid = Pid}) when is_pid(Pid) -> - ssl_connection:info(Pid); -connection_info(#sslsocket{pid = {Listen, _}}) when is_port(Listen) -> - {error, enotconn}. +connection_information(#sslsocket{pid = [Pid|_]}) when is_pid(Pid) -> + case ssl_connection:connection_information(Pid, false) of + {ok, Info} -> + {ok, [Item || Item = {_Key, Value} <- Info, Value =/= undefined]}; + Error -> + Error + end; +connection_information(#sslsocket{pid = {Listen, _}}) when is_port(Listen) -> + {error, enotconn}; +connection_information(#sslsocket{pid = {dtls,_}}) -> + {error,enotconn}. + +%%-------------------------------------------------------------------- +-spec connection_information(#sslsocket{}, [atom()]) -> {ok, list()} | {error, reason()}. +%% +%% Description: Return SSL information for the connection +%%-------------------------------------------------------------------- +connection_information(#sslsocket{pid = [Pid|_]}, Items) when is_pid(Pid) -> + case ssl_connection:connection_information(Pid, include_security_info(Items)) of + {ok, Info} -> + {ok, [Item || Item = {Key, Value} <- Info, lists:member(Key, Items), + Value =/= undefined]}; + Error -> + Error + end. %%-------------------------------------------------------------------- -spec peername(#sslsocket{}) -> {ok, {inet:ip_address(), inet:port_number()}} | {error, reason()}. %% %% Description: same as inet:peername/1. %%-------------------------------------------------------------------- -peername(#sslsocket{pid = Pid, fd = {Transport, Socket, _, _}}) when is_pid(Pid)-> - ssl_socket:peername(Transport, Socket); +peername(#sslsocket{pid = [Pid|_], fd = {Transport, Socket, _}}) when is_pid(Pid)-> + dtls_socket:peername(Transport, Socket); +peername(#sslsocket{pid = [Pid|_], fd = {Transport, Socket, _, _}}) when is_pid(Pid)-> + tls_socket:peername(Transport, Socket); +peername(#sslsocket{pid = {dtls, #config{dtls_handler = {_Pid, _}}}}) -> + dtls_socket:peername(dtls, undefined); peername(#sslsocket{pid = {ListenSocket, #config{transport_info = {Transport,_,_,_}}}}) -> - ssl_socket:peername(Transport, ListenSocket). %% Will return {error, enotconn} + tls_socket:peername(Transport, ListenSocket); %% Will return {error, enotconn} +peername(#sslsocket{pid = {dtls,_}}) -> + {error,enotconn}. %%-------------------------------------------------------------------- -spec peercert(#sslsocket{}) ->{ok, DerCert::binary()} | {error, reason()}. %% %% Description: Returns the peercert. %%-------------------------------------------------------------------- -peercert(#sslsocket{pid = Pid}) when is_pid(Pid) -> +peercert(#sslsocket{pid = [Pid|_]}) when is_pid(Pid) -> case ssl_connection:peer_certificate(Pid) of {ok, undefined} -> {error, no_peercert}; Result -> Result end; +peercert(#sslsocket{pid = {dtls, _}}) -> + {error, enotconn}; peercert(#sslsocket{pid = {Listen, _}}) when is_port(Listen) -> {error, enotconn}. %%-------------------------------------------------------------------- --spec suite_definition(ssl_cipher:cipher_suite()) -> ssl_cipher:erl_cipher_suite(). +-spec negotiated_protocol(#sslsocket{}) -> {ok, binary()} | {error, reason()}. %% -%% Description: Return erlang cipher suite definition. +%% Description: Returns the protocol that has been negotiated. If no +%% protocol has been negotiated will return {error, protocol_not_negotiated} %%-------------------------------------------------------------------- -suite_definition(S) -> - {KeyExchange, Cipher, Hash, _} = ssl_cipher:suite_definition(S), - {KeyExchange, Cipher, Hash}. +negotiated_protocol(#sslsocket{pid = [Pid|_]}) when is_pid(Pid) -> + ssl_connection:negotiated_protocol(Pid). %%-------------------------------------------------------------------- --spec negotiated_next_protocol(#sslsocket{}) -> {ok, binary()} | {error, reason()}. -%% -%% Description: Returns the next protocol that has been negotiated. If no -%% protocol has been negotiated will return {error, next_protocol_not_negotiated} +-spec cipher_suites() -> [ssl_cipher_format:old_erl_cipher_suite()] | [string()]. %%-------------------------------------------------------------------- -negotiated_next_protocol(#sslsocket{pid = Pid}) -> - ssl_connection:negotiated_next_protocol(Pid). - +cipher_suites() -> + cipher_suites(erlang). %%-------------------------------------------------------------------- --spec cipher_suites(erlang | openssl | all) -> [ssl_cipher:erl_cipher_suite()] | - [string()]. +-spec cipher_suites(erlang | openssl | all) -> + [ssl_cipher_format:old_erl_cipher_suite() | string()]. %% Description: Returns all supported cipher suites. %%-------------------------------------------------------------------- cipher_suites(erlang) -> - Version = tls_record:highest_protocol_version([]), - ssl_cipher:filter_suites([suite_definition(S) - || S <- ssl_cipher:suites(Version)]); + [ssl_cipher_format: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_format:openssl_suite_name(Suite) || + Suite <- available_suites(default)]; + cipher_suites(all) -> - Version = tls_record:highest_protocol_version([]), - Supported = ssl_cipher:all_suites(Version) - ++ ssl_cipher:anonymous_suites() - ++ ssl_cipher:psk_suites(Version) - ++ ssl_cipher:srp_suites(), - ssl_cipher:filter_suites([suite_definition(S) || S <- Supported]). + [ssl_cipher_format:erl_suite_definition(Suite) || Suite <- available_suites(all)]. -cipher_suites() -> - cipher_suites(erlang). +%%-------------------------------------------------------------------- +-spec cipher_suites(default | all | anonymous, tls_record:tls_version() | dtls_record:dtls_version() | + tls_record:tls_atom_version() | dtls_record:dtls_atom_version()) -> + [ssl_cipher_format:erl_cipher_suite()]. +%% Description: Returns all default and all supported cipher suites for a +%% TLS/DTLS version +%%-------------------------------------------------------------------- +cipher_suites(Base, Version) when Version == 'tlsv1.2'; + Version == 'tlsv1.1'; + Version == tlsv1; + Version == sslv3 -> + cipher_suites(Base, tls_record:protocol_version(Version)); +cipher_suites(Base, Version) when Version == 'dtlsv1.2'; + Version == 'dtlsv1'-> + cipher_suites(Base, dtls_record:protocol_version(Version)); +cipher_suites(Base, Version) -> + [ssl_cipher_format:suite_definition(Suite) || Suite <- supported_suites(Base, Version)]. + +%%-------------------------------------------------------------------- +-spec filter_cipher_suites([ssl_cipher_format:erl_cipher_suite()], + [{key_exchange | cipher | mac | prf, fun()}] | []) -> + [ssl_cipher_format:erl_cipher_suite()]. +%% Description: Removes cipher suites if any of the filter functions returns false +%% for any part of the cipher suite. This function also calls default filter functions +%% to make sure the cipher suite are supported by crypto. +%%-------------------------------------------------------------------- +filter_cipher_suites(Suites, Filters0) -> + #{key_exchange_filters := KexF, + cipher_filters := CipherF, + mac_filters := MacF, + prf_filters := PrfF} + = ssl_cipher:crypto_support_filters(), + Filters = #{key_exchange_filters => add_filter(proplists:get_value(key_exchange, Filters0), KexF), + cipher_filters => add_filter(proplists:get_value(cipher, Filters0), CipherF), + mac_filters => add_filter(proplists:get_value(mac, Filters0), MacF), + prf_filters => add_filter(proplists:get_value(prf, Filters0), PrfF)}, + ssl_cipher:filter_suites(Suites, Filters). +%%-------------------------------------------------------------------- +-spec prepend_cipher_suites([ssl_cipher_format:erl_cipher_suite()] | + [{key_exchange | cipher | mac | prf, fun()}], + [ssl_cipher_format:erl_cipher_suite()]) -> + [ssl_cipher_format:erl_cipher_suite()]. +%% Description: Make <Preferred> suites become the most prefered +%% suites that is put them at the head of the cipher suite list +%% and remove them from <Suites> if present. <Preferred> may be a +%% list of cipher suits or a list of filters in which case the +%% filters are use on Suites to extract the the preferred +%% cipher list. +%% -------------------------------------------------------------------- +prepend_cipher_suites([First | _] = Preferred, Suites0) when is_map(First) -> + Suites = Preferred ++ (Suites0 -- Preferred), + Suites; +prepend_cipher_suites(Filters, Suites) -> + Preferred = filter_cipher_suites(Suites, Filters), + Preferred ++ (Suites -- Preferred). +%%-------------------------------------------------------------------- +-spec append_cipher_suites(Deferred :: [ssl_cipher_format:erl_cipher_suite()] | + [{key_exchange | cipher | mac | prf, fun()}], + [ssl_cipher_format:erl_cipher_suite()]) -> + [ssl_cipher_format:erl_cipher_suite()]. +%% Description: Make <Deferred> suites suites become the +%% least prefered suites that is put them at the end of the cipher suite list +%% and removed them from <Suites> if present. +%% +%%-------------------------------------------------------------------- +append_cipher_suites([First | _] = Deferred, Suites0) when is_map(First)-> + Suites = (Suites0 -- Deferred) ++ Deferred, + Suites; +append_cipher_suites(Filters, Suites) -> + Deferred = filter_cipher_suites(Suites, Filters), + (Suites -- Deferred) ++ Deferred. + +%%-------------------------------------------------------------------- +-spec eccs() -> tls_v1:curves(). +%% Description: returns all supported curves across all versions +%%-------------------------------------------------------------------- +eccs() -> + Curves = tls_v1:ecc_curves(all), % only tls_v1 has named curves right now + eccs_filter_supported(Curves). + +%%-------------------------------------------------------------------- +-spec eccs(tls_record:tls_version() | tls_record:tls_atom_version() | + dtls_record:dtls_version() | dtls_record:dtls_atom_version()) -> + tls_v1:curves(). +%% Description: returns the curves supported for a given version of +%% ssl/tls. +%%-------------------------------------------------------------------- +eccs({3,0}) -> + []; +eccs({3,_}) -> + Curves = tls_v1:ecc_curves(all), + eccs_filter_supported(Curves); +eccs({254,_} = Version) -> + eccs(dtls_v1:corresponding_tls_version(Version)); +eccs(Version) when Version == 'tlsv1.2'; + Version == 'tlsv1.1'; + Version == tlsv1; + Version == sslv3 -> + eccs(tls_record:protocol_version(Version)); +eccs(Version) when Version == 'dtlsv1.2'; + Version == 'dtlsv1'-> + eccs(dtls_v1:corresponding_tls_version(dtls_record:protocol_version(Version))). + +eccs_filter_supported(Curves) -> + CryptoCurves = crypto:ec_curves(), + lists:filter(fun(Curve) -> proplists:get_bool(Curve, CryptoCurves) end, + Curves). %%-------------------------------------------------------------------- -spec getopts(#sslsocket{}, [gen_tcp:option_name()]) -> @@ -368,11 +573,21 @@ cipher_suites() -> %% %% Description: Gets options %%-------------------------------------------------------------------- -getopts(#sslsocket{pid = Pid}, OptionTags) when is_pid(Pid), is_list(OptionTags) -> +getopts(#sslsocket{pid = [Pid|_]}, OptionTags) when is_pid(Pid), is_list(OptionTags) -> ssl_connection:get_opts(Pid, OptionTags); +getopts(#sslsocket{pid = {dtls, #config{transport_info = {Transport,_,_,_}}}} = ListenSocket, OptionTags) when is_list(OptionTags) -> + try dtls_socket:getopts(Transport, ListenSocket, OptionTags) of + {ok, _} = Result -> + Result; + {error, InetError} -> + {error, {options, {socket_options, OptionTags, InetError}}} + catch + _:Error -> + {error, {options, {socket_options, OptionTags, Error}}} + end; getopts(#sslsocket{pid = {_, #config{transport_info = {Transport,_,_,_}}}} = ListenSocket, OptionTags) when is_list(OptionTags) -> - try ssl_socket:getopts(Transport, ListenSocket, OptionTags) of + try tls_socket:getopts(Transport, ListenSocket, OptionTags) of {ok, _} = Result -> Result; {error, InetError} -> @@ -389,7 +604,26 @@ getopts(#sslsocket{}, OptionTags) -> %% %% Description: Sets options %%-------------------------------------------------------------------- -setopts(#sslsocket{pid = Pid}, Options0) when is_pid(Pid), is_list(Options0) -> +setopts(#sslsocket{pid = [Pid, Sender]}, Options0) when is_pid(Pid), is_list(Options0) -> + try proplists:expand([{binary, [{mode, binary}]}, + {list, [{mode, list}]}], Options0) of + Options -> + case proplists:get_value(packet, Options, undefined) of + undefined -> + ssl_connection:set_opts(Pid, Options); + PacketOpt -> + case tls_sender:setopts(Sender, [{packet, PacketOpt}]) of + ok -> + ssl_connection:set_opts(Pid, Options); + Error -> + Error + end + end + catch + _:_ -> + {error, {options, {not_a_proplist, Options0}}} + end; +setopts(#sslsocket{pid = [Pid|_]}, Options0) when is_pid(Pid), is_list(Options0) -> try proplists:expand([{binary, [{mode, binary}]}, {list, [{mode, list}]}], Options0) of Options -> @@ -398,9 +632,18 @@ setopts(#sslsocket{pid = Pid}, Options0) when is_pid(Pid), is_list(Options0) -> _:_ -> {error, {options, {not_a_proplist, Options0}}} end; - +setopts(#sslsocket{pid = {dtls, #config{transport_info = {Transport,_,_,_}}}} = ListenSocket, Options) when is_list(Options) -> + try dtls_socket:setopts(Transport, ListenSocket, Options) of + ok -> + ok; + {error, InetError} -> + {error, {options, {socket_options, Options, InetError}}} + catch + _:Error -> + {error, {options, {socket_options, Options, Error}}} + end; setopts(#sslsocket{pid = {_, #config{transport_info = {Transport,_,_,_}}}} = ListenSocket, Options) when is_list(Options) -> - try ssl_socket:setopts(Transport, ListenSocket, Options) of + try tls_socket:setopts(Transport, ListenSocket, Options) of ok -> ok; {error, InetError} -> @@ -413,6 +656,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) -> + tls_socket:getstat(Transport, Listen, Options); + +getstat(#sslsocket{pid = [Pid|_], fd = {Transport, Socket, _, _}}, Options) when is_pid(Pid), is_list(Options) -> + tls_socket:getstat(Transport, Socket, Options). + +%%--------------------------------------------------------------- -spec shutdown(#sslsocket{}, read | write | read_write) -> ok | {error, reason()}. %% %% Description: Same as gen_tcp:shutdown/2 @@ -420,7 +689,9 @@ setopts(#sslsocket{}, Options) -> shutdown(#sslsocket{pid = {Listen, #config{transport_info = {Transport,_, _, _}}}}, How) when is_port(Listen) -> Transport:shutdown(Listen, How); -shutdown(#sslsocket{pid = Pid}, How) -> +shutdown(#sslsocket{pid = {dtls,_}},_) -> + {error, enotconn}; +shutdown(#sslsocket{pid = [Pid|_]}, How) when is_pid(Pid) -> ssl_connection:shutdown(Pid, How). %%-------------------------------------------------------------------- @@ -429,34 +700,33 @@ shutdown(#sslsocket{pid = Pid}, How) -> %% Description: Same as inet:sockname/1 %%-------------------------------------------------------------------- sockname(#sslsocket{pid = {Listen, #config{transport_info = {Transport, _, _, _}}}}) when is_port(Listen) -> - ssl_socket:sockname(Transport, Listen); - -sockname(#sslsocket{pid = Pid, fd = {Transport, Socket, _, _}}) when is_pid(Pid) -> - ssl_socket:sockname(Transport, Socket). - -%%--------------------------------------------------------------- --spec session_info(#sslsocket{}) -> {ok, list()} | {error, reason()}. -%% -%% Description: Returns list of session info currently [{session_id, session_id(), -%% {cipher_suite, cipher_suite()}] -%%-------------------------------------------------------------------- -session_info(#sslsocket{pid = Pid}) when is_pid(Pid) -> - ssl_connection:session_info(Pid); -session_info(#sslsocket{pid = {Listen,_}}) when is_port(Listen) -> - {error, enotconn}. + tls_socket:sockname(Transport, Listen); +sockname(#sslsocket{pid = {dtls, #config{dtls_handler = {Pid, _}}}}) -> + dtls_packet_demux:sockname(Pid); +sockname(#sslsocket{pid = [Pid|_], fd = {Transport, Socket, _}}) when is_pid(Pid) -> + dtls_socket:sockname(Transport, Socket); +sockname(#sslsocket{pid = [Pid| _], fd = {Transport, Socket, _, _}}) when is_pid(Pid) -> + tls_socket:sockname(Transport, Socket). %%--------------------------------------------------------------- -spec versions() -> [{ssl_app, string()} | {supported, [tls_record:tls_atom_version()]} | - {available, [tls_record:tls_atom_version()]}]. + {supported_dtls, [dtls_record:dtls_atom_version()]} | + {available, [tls_record:tls_atom_version()]} | + {available_dtls, [dtls_record:dtls_atom_version()]}]. %% %% Description: Returns a list of relevant versions. %%-------------------------------------------------------------------- versions() -> - Vsns = tls_record:supported_protocol_versions(), - SupportedVsns = [tls_record:protocol_version(Vsn) || Vsn <- Vsns], - AvailableVsns = ?ALL_SUPPORTED_VERSIONS, - %% TODO Add DTLS versions when supported - [{ssl_app, ?VSN}, {supported, SupportedVsns}, {available, AvailableVsns}]. + TLSVsns = tls_record:supported_protocol_versions(), + DTLSVsns = dtls_record:supported_protocol_versions(), + SupportedTLSVsns = [tls_record:protocol_version(Vsn) || Vsn <- TLSVsns], + SupportedDTLSVsns = [dtls_record:protocol_version(Vsn) || Vsn <- DTLSVsns], + AvailableTLSVsns = ?ALL_AVAILABLE_VERSIONS, + AvailableDTLSVsns = ?ALL_AVAILABLE_DATAGRAM_VERSIONS, + [{ssl_app, ?VSN}, {supported, SupportedTLSVsns}, + {supported_dtls, SupportedDTLSVsns}, + {available, AvailableTLSVsns}, + {available_dtls, AvailableDTLSVsns}]. %%--------------------------------------------------------------- @@ -464,21 +734,33 @@ versions() -> %% %% Description: Initiates a renegotiation. %%-------------------------------------------------------------------- -renegotiate(#sslsocket{pid = Pid}) when is_pid(Pid) -> +renegotiate(#sslsocket{pid = [Pid, Sender |_]}) when is_pid(Pid), + is_pid(Sender) -> + case tls_sender:renegotiate(Sender) of + {ok, Write} -> + tls_connection:renegotiation(Pid, Write); + Error -> + Error + end; +renegotiate(#sslsocket{pid = [Pid |_]}) when is_pid(Pid) -> ssl_connection:renegotiation(Pid); +renegotiate(#sslsocket{pid = {dtls,_}}) -> + {error, enotconn}; renegotiate(#sslsocket{pid = {Listen,_}}) when is_port(Listen) -> {error, enotconn}. %%-------------------------------------------------------------------- -spec prf(#sslsocket{}, binary() | 'master_secret', binary(), - binary() | prf_random(), non_neg_integer()) -> + [binary() | prf_random()], non_neg_integer()) -> {ok, binary()} | {error, reason()}. %% %% Description: use a ssl sessions TLS PRF to generate key material %%-------------------------------------------------------------------- -prf(#sslsocket{pid = Pid}, +prf(#sslsocket{pid = [Pid|_]}, Secret, Label, Seed, WantedLength) when is_pid(Pid) -> ssl_connection:prf(Pid, Secret, Label, Seed, WantedLength); +prf(#sslsocket{pid = {dtls,_}}, _,_,_,_) -> + {error, enotconn}; prf(#sslsocket{pid = {Listen,_}}, _,_,_,_) when is_port(Listen) -> {error, enotconn}. @@ -488,7 +770,7 @@ prf(#sslsocket{pid = {Listen,_}}, _,_,_,_) when is_port(Listen) -> %% Description: Clear the PEM cache %%-------------------------------------------------------------------- clear_pem_cache() -> - ssl_manager:clear_pem_cache(). + ssl_pem_cache:clear(). %%--------------------------------------------------------------- -spec format_error({error, term()}) -> list(). @@ -524,52 +806,58 @@ format_error(Error) -> Other end. -%%-------------------------------------------------------------------- --spec random_bytes(integer()) -> binary(). +tls_version({3, _} = Version) -> + Version; +tls_version({254, _} = Version) -> + dtls_v1:corresponding_tls_version(Version). + +%%-------------------------------------------------------------------- +-spec suite_to_str(ssl_cipher_format:erl_cipher_suite()) -> string(). %% -%% Description: Generates cryptographically secure random sequence if possible -%% fallbacks on pseudo random function +%% Description: Return the string representation of a cipher suite. %%-------------------------------------------------------------------- -random_bytes(N) -> - try crypto:strong_rand_bytes(N) of - RandBytes -> - RandBytes - catch - error:low_entropy -> - crypto:rand_bytes(N) - end. +suite_to_str(Cipher) -> + ssl_cipher_format:suite_to_str(Cipher). + %%%-------------------------------------------------------------- %%% Internal functions %%%-------------------------------------------------------------------- -do_connect(Address, Port, - #config{transport_info = CbInfo, inet_user = UserOpts, ssl = SslOpts, - emulated = EmOpts, inet_ssl = SocketOpts, connection_cb = ConnetionCb}, - Timeout) -> - {Transport, _, _, _} = CbInfo, - try Transport:connect(Address, Port, SocketOpts, Timeout) of - {ok, Socket} -> - ssl_connection:connect(ConnetionCb, Address, Port, Socket, - {SslOpts, emulated_socket_options(EmOpts, #socket_options{}), undefined}, - self(), CbInfo, Timeout); - {error, Reason} -> - {error, Reason} - catch - exit:{function_clause, _} -> - {error, {options, {cb_info, CbInfo}}}; - exit:badarg -> - {error, {options, {socket_options, UserOpts}}}; - exit:{badarg, _} -> - {error, {options, {socket_options, UserOpts}}} - end. - +%% 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)). + +supported_suites(default, Version) -> + ssl_cipher:suites(Version); +supported_suites(all, Version) -> + ssl_cipher:all_suites(Version); +supported_suites(anonymous, Version) -> + ssl_cipher:anonymous_suites(Version). + +do_listen(Port, #config{transport_info = {Transport, _, _, _}} = Config, tls_connection) -> + tls_socket:listen(Transport, Port, Config); + +do_listen(Port, Config, dtls_connection) -> + dtls_socket:listen(Port, Config). + %% Handle extra ssl options given to ssl_accept +-spec handle_options([any()], #ssl_options{}) -> #ssl_options{} + ; ([any()], client | server) -> {ok, #config{}}. +handle_options(Opts, Role) -> + handle_options(Opts, Role, undefined). + + handle_options(Opts0, #ssl_options{protocol = Protocol, cacerts = CaCerts0, - cacertfile = CaCertFile0} = InheritedSslOpts) -> + cacertfile = CaCertFile0} = InheritedSslOpts, _) -> RecordCB = record_cb(Protocol), CaCerts = handle_option(cacerts, Opts0, CaCerts0), - {Verify, FailIfNoPeerCert, CaCertDefault, VerifyFun, PartialChainHanlder} = handle_verify_options(Opts0, CaCerts), + {Verify, FailIfNoPeerCert, CaCertDefault, VerifyFun, PartialChainHanlder, + VerifyClientOnce} = handle_verify_options(Opts0, CaCerts), CaCertFile = case proplists:get_value(cacertfile, Opts0, CaCertFile0) of undefined -> CaCertDefault; @@ -582,11 +870,12 @@ handle_options(Opts0, #ssl_options{protocol = Protocol, cacerts = CaCerts0, verify = Verify, verify_fun = VerifyFun, partial_chain = PartialChainHanlder, - fail_if_no_peer_cert = FailIfNoPeerCert}, + fail_if_no_peer_cert = FailIfNoPeerCert, + verify_client_once = VerifyClientOnce}, SslOpts1 = lists:foldl(fun(Key, PropList) -> proplists:delete(Key, PropList) end, Opts0, [cacerts, cacertfile, verify, verify_fun, partial_chain, - fail_if_no_peer_cert]), + fail_if_no_peer_cert, verify_client_once]), case handle_option(versions, SslOpts1, []) of [] -> new_ssl_options(SslOpts1, NewVerifyOpts, RecordCB); @@ -594,10 +883,10 @@ handle_options(Opts0, #ssl_options{protocol = Protocol, cacerts = CaCerts0, Versions = [RecordCB:protocol_version(Vsn) || Vsn <- Value], new_ssl_options(proplists:delete(versions, SslOpts1), NewVerifyOpts#ssl_options{versions = Versions}, record_cb(Protocol)) - end. + end; %% Handle all options in listen and connect -handle_options(Opts0) -> +handle_options(Opts0, Role, Host) -> Opts = proplists:expand([{binary, [{mode, binary}]}, {list, [{mode, list}]}], Opts0), assert_proplist(Opts), @@ -606,7 +895,7 @@ handle_options(Opts0) -> ReuseSessionFun = fun(_, _, _, _) -> true end, CaCerts = handle_option(cacerts, Opts, undefined), - {Verify, FailIfNoPeerCert, CaCertDefault, VerifyFun, PartialChainHanlder} = + {Verify, FailIfNoPeerCert, CaCertDefault, VerifyFun, PartialChainHanlder, VerifyClientOnce} = handle_verify_options(Opts, CaCerts), CertFile = handle_option(certfile, Opts, <<>>), @@ -619,13 +908,22 @@ handle_options(Opts0) -> [RecordCb:protocol_version(Vsn) || Vsn <- Vsns] end, + Protocol = handle_option(protocol, Opts, tls), + + case Versions of + [{3, 0}] -> + reject_alpn_next_prot_options(Opts); + _ -> + ok + end, + SSLOptions = #ssl_options{ versions = Versions, verify = validate_option(verify, Verify), verify_fun = VerifyFun, partial_chain = PartialChainHanlder, fail_if_no_peer_cert = FailIfNoPeerCert, - verify_client_once = handle_option(verify_client_once, Opts, false), + verify_client_once = VerifyClientOnce, depth = handle_option(depth, Opts, 1), cert = handle_option(cert, Opts, undefined), certfile = CertFile, @@ -641,55 +939,104 @@ handle_options(Opts0) -> srp_identity = handle_option(srp_identity, Opts, undefined), ciphers = handle_cipher_option(proplists:get_value(ciphers, Opts, []), RecordCb:highest_protocol_version(Versions)), + eccs = handle_eccs_option(proplists:get_value(eccs, Opts, eccs()), + RecordCb:highest_protocol_version(Versions)), + signature_algs = handle_hashsigns_option(proplists:get_value(signature_algs, Opts, + default_option_role(server, + tls_v1:default_signature_algs(Versions), Role)), + tls_version(RecordCb:highest_protocol_version(Versions))), %% Server side option reuse_session = handle_option(reuse_session, Opts, ReuseSessionFun), reuse_sessions = handle_option(reuse_sessions, Opts, true), - secure_renegotiate = handle_option(secure_renegotiate, Opts, false), + secure_renegotiate = handle_option(secure_renegotiate, Opts, true), + client_renegotiation = handle_option(client_renegotiation, Opts, + 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), + alpn_preferred_protocols = + handle_option(alpn_preferred_protocols, Opts, undefined), next_protocols_advertised = handle_option(next_protocols_advertised, Opts, undefined), next_protocol_selector = make_next_protocol_selector( handle_option(client_preferred_next_protocols, Opts, undefined)), log_alert = handle_option(log_alert, Opts, true), - server_name_indication = handle_option(server_name_indication, Opts, undefined), - honor_cipher_order = handle_option(honor_cipher_order, Opts, false), - protocol = proplists:get_value(protocol, Opts, tls), + server_name_indication = handle_option(server_name_indication, Opts, + default_option_role(client, + server_name_indication_default(Host), Role)), + sni_hosts = handle_option(sni_hosts, Opts, []), + sni_fun = handle_option(sni_fun, Opts, undefined), + honor_cipher_order = handle_option(honor_cipher_order, Opts, + default_option_role(server, false, Role), + server, Role), + honor_ecc_order = handle_option(honor_ecc_order, Opts, + default_option_role(server, false, Role), + server, Role), + protocol = Protocol, padding_check = proplists:get_value(padding_check, Opts, true), - fallback = proplists:get_value(fallback, Opts, false) + 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, []}}), + max_handshake_size = handle_option(max_handshake_size, Opts, ?DEFAULT_MAX_HANDSHAKE_SIZE), + handshake = handle_option(handshake, Opts, full), + customize_hostname_check = handle_option(customize_hostname_check, Opts, []) }, - CbInfo = proplists:get_value(cb_info, Opts, {gen_tcp, tcp, tcp_closed, tcp_error}), + CbInfo = proplists:get_value(cb_info, Opts, default_cb_info(Protocol)), SslOptions = [protocol, versions, verify, verify_fun, partial_chain, fail_if_no_peer_cert, verify_client_once, depth, cert, certfile, key, keyfile, password, cacerts, cacertfile, dh, dhfile, user_lookup_fun, psk_identity, srp_identity, ciphers, - reuse_session, reuse_sessions, ssl_imp, + reuse_session, reuse_sessions, ssl_imp, client_renegotiation, cb_info, renegotiate_at, secure_renegotiate, hibernate_after, - erl_dist, next_protocols_advertised, + erl_dist, alpn_advertised_protocols, sni_hosts, sni_fun, + alpn_preferred_protocols, next_protocols_advertised, client_preferred_next_protocols, log_alert, - server_name_indication, honor_cipher_order, padding_check, - fallback], - + server_name_indication, honor_cipher_order, padding_check, crl_check, crl_cache, + fallback, signature_algs, eccs, honor_ecc_order, beast_mitigation, + max_handshake_size, handshake, customize_hostname_check], SockOpts = lists:foldl(fun(Key, PropList) -> proplists:delete(Key, PropList) end, Opts, SslOptions), - {Sock, Emulated} = emulated_options(SockOpts), + {Sock, Emulated} = emulated_options(Protocol, SockOpts), ConnetionCb = connection_cb(Opts), {ok, #config{ssl = SSLOptions, emulated = Emulated, inet_ssl = Sock, - inet_user = SockOpts, transport_info = CbInfo, connection_cb = ConnetionCb + inet_user = Sock, transport_info = CbInfo, connection_cb = ConnetionCb }}. +handle_option(OptionName, Opts, Default, Role, Role) -> + handle_option(OptionName, Opts, Default); +handle_option(_, _, undefined = Value, _, _) -> + Value. + +handle_option(sni_fun, Opts, Default) -> + OptFun = validate_option(sni_fun, + proplists:get_value(sni_fun, Opts, Default)), + OptHosts = proplists:get_value(sni_hosts, Opts, undefined), + case {OptFun, OptHosts} of + {Default, _} -> + Default; + {_, undefined} -> + OptFun; + _ -> + throw({error, {conflict_options, [sni_fun, sni_hosts]}}) + end; handle_option(OptionName, Opts, Default) -> validate_option(OptionName, proplists:get_value(OptionName, Opts, Default)). - validate_option(versions, Versions) -> validate_versions(Versions, Versions); validate_option(verify, Value) @@ -741,9 +1088,11 @@ validate_option(key, {KeyType, Value}) when is_binary(Value), KeyType == dsa; %% Backwards compatibility KeyType == 'RSAPrivateKey'; KeyType == 'DSAPrivateKey'; + KeyType == 'ECPrivateKey'; KeyType == 'PrivateKeyInfo' -> {KeyType, Value}; - +validate_option(key, #{algorithm := _} = Value) -> + Value; validate_option(keyfile, undefined) -> <<>>; validate_option(keyfile, Value) when is_binary(Value) -> @@ -796,67 +1145,131 @@ validate_option(reuse_sessions, Value) when is_boolean(Value) -> validate_option(secure_renegotiate, Value) when is_boolean(Value) -> Value; +validate_option(client_renegotiation, Value) when is_boolean(Value) -> + 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(client_preferred_next_protocols = Opt, {Precedence, PreferredProtocols} = Value) +validate_option(Opt, Value) when Opt =:= alpn_advertised_protocols orelse Opt =:= alpn_preferred_protocols, + is_list(Value) -> + validate_binary_list(Opt, Value), + Value; +validate_option(Opt, Value) + when Opt =:= alpn_advertised_protocols orelse Opt =:= alpn_preferred_protocols, + Value =:= undefined -> + undefined; +validate_option(client_preferred_next_protocols, {Precedence, PreferredProtocols}) when is_list(PreferredProtocols) -> - case tls_record:highest_protocol_version([]) of - {3,0} -> - throw({error, {options, {not_supported_in_sslv3, {Opt, Value}}}}); - _ -> - validate_binary_list(client_preferred_next_protocols, PreferredProtocols), - validate_npn_ordering(Precedence), - {Precedence, PreferredProtocols, ?NO_PROTOCOL} - end; -validate_option(client_preferred_next_protocols = Opt, {Precedence, PreferredProtocols, Default} = Value) - when is_list(PreferredProtocols), is_binary(Default), - byte_size(Default) > 0, byte_size(Default) < 256 -> - case tls_record:highest_protocol_version([]) of - {3,0} -> - throw({error, {options, {not_supported_in_sslv3, {Opt, Value}}}}); - _ -> - validate_binary_list(client_preferred_next_protocols, PreferredProtocols), - validate_npn_ordering(Precedence), - Value - end; - + validate_binary_list(client_preferred_next_protocols, PreferredProtocols), + validate_npn_ordering(Precedence), + {Precedence, PreferredProtocols, ?NO_PROTOCOL}; +validate_option(client_preferred_next_protocols, {Precedence, PreferredProtocols, Default} = Value) + when is_list(PreferredProtocols), is_binary(Default), + byte_size(Default) > 0, byte_size(Default) < 256 -> + validate_binary_list(client_preferred_next_protocols, PreferredProtocols), + validate_npn_ordering(Precedence), + Value; validate_option(client_preferred_next_protocols, undefined) -> undefined; validate_option(log_alert, Value) when is_boolean(Value) -> Value; -validate_option(next_protocols_advertised = Opt, Value) when is_list(Value) -> - case tls_record:highest_protocol_version([]) of - {3,0} -> - throw({error, {options, {not_supported_in_sslv3, {Opt, Value}}}}); - _ -> - validate_binary_list(next_protocols_advertised, Value), - Value - end; - +validate_option(next_protocols_advertised, Value) when is_list(Value) -> + validate_binary_list(next_protocols_advertised, Value), + Value; validate_option(next_protocols_advertised, undefined) -> undefined; validate_option(server_name_indication, Value) when is_list(Value) -> + %% RFC 6066, Section 3: Currently, the only server names supported are + %% DNS hostnames + %% case inet_parse:domain(Value) of + %% false -> + %% throw({error, {options, {{Opt, Value}}}}); + %% true -> + %% Value + %% end; + %% + %% But the definition seems very diffuse, so let all strings through + %% and leave it up to public_key to decide... Value; +validate_option(server_name_indication, undefined) -> + undefined; validate_option(server_name_indication, disable) -> disable; -validate_option(server_name_indication, undefined) -> + +validate_option(sni_hosts, []) -> + []; +validate_option(sni_hosts, [{Hostname, SSLOptions} | Tail]) when is_list(Hostname) -> + RecursiveSNIOptions = proplists:get_value(sni_hosts, SSLOptions, undefined), + case RecursiveSNIOptions of + undefined -> + [{Hostname, validate_options(SSLOptions)} | validate_option(sni_hosts, Tail)]; + _ -> + throw({error, {options, {sni_hosts, RecursiveSNIOptions}}}) + end; +validate_option(sni_fun, undefined) -> undefined; +validate_option(sni_fun, Fun) when is_function(Fun) -> + Fun; validate_option(honor_cipher_order, Value) when is_boolean(Value) -> Value; +validate_option(honor_ecc_order, Value) when is_boolean(Value) -> + Value; validate_option(padding_check, Value) when is_boolean(Value) -> Value; validate_option(fallback, Value) when is_boolean(Value) -> Value; +validate_option(crl_check, Value) when is_boolean(Value) -> + Value; +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(max_handshake_size, Value) when is_integer(Value) andalso Value =< ?MAX_UNIT24 -> + Value; +validate_option(protocol, Value = tls) -> + Value; +validate_option(protocol, Value = dtls) -> + Value; +validate_option(handshake, hello = Value) -> + Value; +validate_option(handshake, full = Value) -> + Value; +validate_option(customize_hostname_check, Value) when is_list(Value) -> + Value; validate_option(Opt, Value) -> throw({error, {options, {Opt, Value}}}). +handle_hashsigns_option(Value, Version) when is_list(Value) + andalso Version >= {3, 3} -> + case tls_v1:signature_algs(Version, Value) of + [] -> + throw({error, {options, no_supported_algorithms, {signature_algs, Value}}}); + _ -> + Value + end; +handle_hashsigns_option(_, Version) when Version >= {3, 3} -> + handle_hashsigns_option(tls_v1:default_signature_algs(Version), Version); +handle_hashsigns_option(_, _Version) -> + undefined. + +validate_options([]) -> + []; +validate_options([{Opt, Value} | Tail]) -> + [{Opt, validate_option(Opt, Value)} | validate_options(Tail)]. + validate_npn_ordering(client) -> ok; validate_npn_ordering(server) -> @@ -873,34 +1286,36 @@ validate_binary_list(Opt, List) -> (Bin) -> throw({error, {options, {Opt, {invalid_protocol, Bin}}}}) end, List). - validate_versions([], Versions) -> Versions; validate_versions([Version | Rest], Versions) when Version == 'tlsv1.2'; Version == 'tlsv1.1'; Version == tlsv1; Version == sslv3 -> - validate_versions(Rest, Versions); + tls_validate_versions(Rest, Versions); +validate_versions([Version | Rest], Versions) when Version == 'dtlsv1'; + Version == 'dtlsv1.2'-> + dtls_validate_versions(Rest, Versions); validate_versions([Ver| _], Versions) -> throw({error, {options, {Ver, {versions, Versions}}}}). -validate_inet_option(mode, Value) - when Value =/= list, Value =/= binary -> - throw({error, {options, {mode,Value}}}); -validate_inet_option(packet, Value) - when not (is_atom(Value) orelse is_integer(Value)) -> - throw({error, {options, {packet,Value}}}); -validate_inet_option(packet_size, Value) - when not is_integer(Value) -> - throw({error, {options, {packet_size,Value}}}); -validate_inet_option(header, Value) - when not is_integer(Value) -> - throw({error, {options, {header,Value}}}); -validate_inet_option(active, Value) - when Value =/= true, Value =/= false, Value =/= once -> - throw({error, {options, {active,Value}}}); -validate_inet_option(_, _) -> - ok. +tls_validate_versions([], Versions) -> + Versions; +tls_validate_versions([Version | Rest], Versions) when Version == 'tlsv1.2'; + Version == 'tlsv1.1'; + Version == tlsv1; + Version == sslv3 -> + tls_validate_versions(Rest, Versions); +tls_validate_versions([Ver| _], Versions) -> + throw({error, {options, {Ver, {versions, Versions}}}}). + +dtls_validate_versions([], Versions) -> + Versions; +dtls_validate_versions([Version | Rest], Versions) when Version == 'dtlsv1'; + Version == 'dtlsv1.2'-> + dtls_validate_versions(Rest, Versions); +dtls_validate_versions([Ver| _], Versions) -> + throw({error, {options, {Ver, {versions, Versions}}}}). %% The option cacerts overrides cacertsfile ca_cert_default(_,_, [_|_]) -> @@ -913,28 +1328,13 @@ ca_cert_default(verify_peer, {Fun,_}, _) when is_function(Fun) -> %% some trusted certs. ca_cert_default(verify_peer, undefined, _) -> "". -emulated_options(Opts) -> - emulated_options(Opts, ssl_socket:internal_inet_values(), ssl_socket:default_inet_values()). - -emulated_options([{mode, Value} = Opt |Opts], Inet, Emulated) -> - validate_inet_option(mode, Value), - emulated_options(Opts, Inet, [Opt | proplists:delete(mode, Emulated)]); -emulated_options([{header, Value} = Opt | Opts], Inet, Emulated) -> - validate_inet_option(header, Value), - emulated_options(Opts, Inet, [Opt | proplists:delete(header, Emulated)]); -emulated_options([{active, Value} = Opt |Opts], Inet, Emulated) -> - validate_inet_option(active, Value), - emulated_options(Opts, Inet, [Opt | proplists:delete(active, Emulated)]); -emulated_options([{packet, Value} = Opt |Opts], Inet, Emulated) -> - validate_inet_option(packet, Value), - emulated_options(Opts, Inet, [Opt | proplists:delete(packet, Emulated)]); -emulated_options([{packet_size, Value} = Opt | Opts], Inet, Emulated) -> - validate_inet_option(packet_size, Value), - emulated_options(Opts, Inet, [Opt | proplists:delete(packet_size, Emulated)]); -emulated_options([Opt|Opts], Inet, Emulated) -> - emulated_options(Opts, [Opt|Inet], Emulated); -emulated_options([], Inet,Emulated) -> - {Inet, Emulated}. +emulated_options(Protocol, Opts) -> + case Protocol of + tls -> + tls_socket:emulated_options(Opts); + dtls -> + dtls_socket:emulated_options(Opts) + end. handle_cipher_option(Value, Version) when is_list(Value) -> try binary_cipher_suites(Version, Value) of @@ -950,36 +1350,66 @@ handle_cipher_option(Value, Version) when is_list(Value) -> binary_cipher_suites(Version, []) -> %% Defaults to all supported suites that does %% not require explicit configuration - ssl_cipher:filter_suites(ssl_cipher:suites(Version)); -binary_cipher_suites(Version, [{_,_,_,_}| _] = Ciphers0) -> %% Backwards compatibility - Ciphers = [{KeyExchange, Cipher, Hash} || {KeyExchange, Cipher, Hash, _} <- Ciphers0], + default_binary_suites(Version); +binary_cipher_suites(Version, [Map|_] = Ciphers0) when is_map(Map) -> + Ciphers = [ssl_cipher_format:suite(C) || C <- Ciphers0], binary_cipher_suites(Version, Ciphers); -binary_cipher_suites(Version, [{_,_,_}| _] = Ciphers0) -> - Ciphers = [ssl_cipher:suite(C) || C <- Ciphers0], +binary_cipher_suites(Version, [Tuple|_] = Ciphers0) when is_tuple(Tuple) -> + Ciphers = [ssl_cipher_format:suite(tuple_to_map(C)) || C <- Ciphers0], binary_cipher_suites(Version, Ciphers); - binary_cipher_suites(Version, [Cipher0 | _] = Ciphers0) when is_binary(Cipher0) -> - All = ssl_cipher:suites(Version) - ++ ssl_cipher:anonymous_suites() - ++ ssl_cipher:psk_suites(Version) - ++ ssl_cipher:srp_suites(), + All = ssl_cipher:all_suites(Version) ++ + ssl_cipher:anonymous_suites(Version), case [Cipher || Cipher <- Ciphers0, lists:member(Cipher, All)] of [] -> %% Defaults to all supported suites that does %% not require explicit configuration - ssl_cipher:filter_suites(ssl_cipher:suites(Version)); + default_binary_suites(Version); Ciphers -> Ciphers end; binary_cipher_suites(Version, [Head | _] = Ciphers0) when is_list(Head) -> %% Format: ["RC4-SHA","RC4-MD5"] - Ciphers = [ssl_cipher:openssl_suite(C) || C <- Ciphers0], + Ciphers = [ssl_cipher_format:openssl_suite(C) || C <- Ciphers0], binary_cipher_suites(Version, Ciphers); binary_cipher_suites(Version, Ciphers0) -> %% Format: "RC4-SHA:RC4-MD5" - Ciphers = [ssl_cipher:openssl_suite(C) || C <- string:tokens(Ciphers0, ":")], + Ciphers = [ssl_cipher_format:openssl_suite(C) || C <- string:lexemes(Ciphers0, ":")], binary_cipher_suites(Version, Ciphers). +default_binary_suites(Version) -> + ssl_cipher:filter_suites(ssl_cipher:suites(Version)). + +tuple_to_map({Kex, Cipher, Mac}) -> + #{key_exchange => Kex, + cipher => Cipher, + mac => Mac, + prf => default_prf}; +tuple_to_map({Kex, Cipher, Mac, Prf}) -> + #{key_exchange => Kex, + cipher => Cipher, + mac => tuple_to_map_mac(Cipher, Mac), + prf => Prf}. + +%% Backwards compatible +tuple_to_map_mac(aes_128_gcm, _) -> + aead; +tuple_to_map_mac(aes_256_gcm, _) -> + aead; +tuple_to_map_mac(chacha20_poly1305, _) -> + aead; +tuple_to_map_mac(_, MAC) -> + MAC. + +handle_eccs_option(Value, Version) when is_list(Value) -> + {_Major, Minor} = tls_version(Version), + try tls_v1:ecc_curves(Minor, Value) of + Curves -> #elliptic_curves{elliptic_curve_list = Curves} + catch + exit:_ -> throw({error, {options, {eccs, Value}}}); + error:_ -> throw({error, {options, {eccs, Value}}}) + end. + unexpected_format(Error) -> lists:flatten(io_lib:format("Unexpected error: ~p", [Error])). @@ -1053,11 +1483,6 @@ record_cb(dtls) -> record_cb(Opts) -> record_cb(proplists:get_value(protocol, Opts, tls)). -connection_sup(tls_connection) -> - tls_connection_sup; -connection_sup(dtls_connection) -> - dtls_connection_sup. - binary_filename(FileName) -> Enc = file:native_name_encoding(), unicode:characters_to_binary(FileName, unicode, Enc). @@ -1067,6 +1492,8 @@ assert_proplist([]) -> assert_proplist([{Key,_} | Rest]) when is_atom(Key) -> assert_proplist(Rest); %% Handle exceptions +assert_proplist([{raw,_,_,_} | Rest]) -> + assert_proplist(Rest); assert_proplist([inet | Rest]) -> assert_proplist(Rest); assert_proplist([inet6 | Rest]) -> @@ -1074,24 +1501,11 @@ assert_proplist([inet6 | Rest]) -> assert_proplist([Value | _]) -> throw({option_not_a_key_value_tuple, Value}). -emulated_socket_options(InetValues, #socket_options{ - mode = Mode, - header = Header, - active = Active, - packet = Packet, - packet_size = Size}) -> - #socket_options{ - mode = proplists:get_value(mode, InetValues, Mode), - header = proplists:get_value(header, InetValues, Header), - active = proplists:get_value(active, InetValues, Active), - packet = proplists:get_value(packet, InetValues, Packet), - packet_size = proplists:get_value(packet_size, InetValues, Size) - }. - new_ssl_options([], #ssl_options{} = Opts, _) -> Opts; new_ssl_options([{verify_client_once, Value} | Rest], #ssl_options{} = Opts, RecordCB) -> - new_ssl_options(Rest, Opts#ssl_options{verify_client_once = validate_option(verify_client_once, Value)}, RecordCB); + new_ssl_options(Rest, Opts#ssl_options{verify_client_once = + validate_option(verify_client_once, Value)}, RecordCB); new_ssl_options([{depth, Value} | Rest], #ssl_options{} = Opts, RecordCB) -> new_ssl_options(Rest, Opts#ssl_options{depth = validate_option(depth, Value)}, RecordCB); new_ssl_options([{cert, Value} | Rest], #ssl_options{} = Opts, RecordCB) -> @@ -1128,8 +1542,14 @@ new_ssl_options([{renegotiate_at, Value} | Rest], #ssl_options{} = Opts, RecordC new_ssl_options(Rest, Opts#ssl_options{ renegotiate_at = validate_option(renegotiate_at, Value)}, RecordCB); new_ssl_options([{secure_renegotiate, Value} | Rest], #ssl_options{} = Opts, RecordCB) -> new_ssl_options(Rest, Opts#ssl_options{secure_renegotiate = validate_option(secure_renegotiate, Value)}, RecordCB); +new_ssl_options([{client_renegotiation, Value} | Rest], #ssl_options{} = Opts, RecordCB) -> + new_ssl_options(Rest, Opts#ssl_options{client_renegotiation = validate_option(client_renegotiation, Value)}, RecordCB); new_ssl_options([{hibernate_after, Value} | Rest], #ssl_options{} = Opts, RecordCB) -> new_ssl_options(Rest, Opts#ssl_options{hibernate_after = validate_option(hibernate_after, Value)}, RecordCB); +new_ssl_options([{alpn_advertised_protocols, Value} | Rest], #ssl_options{} = Opts, RecordCB) -> + new_ssl_options(Rest, Opts#ssl_options{alpn_advertised_protocols = validate_option(alpn_advertised_protocols, Value)}, RecordCB); +new_ssl_options([{alpn_preferred_protocols, Value} | Rest], #ssl_options{} = Opts, RecordCB) -> + new_ssl_options(Rest, Opts#ssl_options{alpn_preferred_protocols = validate_option(alpn_preferred_protocols, Value)}, RecordCB); new_ssl_options([{next_protocols_advertised, Value} | Rest], #ssl_options{} = Opts, RecordCB) -> new_ssl_options(Rest, Opts#ssl_options{next_protocols_advertised = validate_option(next_protocols_advertised, Value)}, RecordCB); new_ssl_options([{client_preferred_next_protocols, Value} | Rest], #ssl_options{} = Opts, RecordCB) -> @@ -1141,6 +1561,24 @@ new_ssl_options([{server_name_indication, Value} | Rest], #ssl_options{} = Opts, new_ssl_options(Rest, Opts#ssl_options{server_name_indication = validate_option(server_name_indication, Value)}, RecordCB); new_ssl_options([{honor_cipher_order, Value} | Rest], #ssl_options{} = Opts, RecordCB) -> new_ssl_options(Rest, Opts#ssl_options{honor_cipher_order = validate_option(honor_cipher_order, Value)}, RecordCB); +new_ssl_options([{honor_ecc_order, Value} | Rest], #ssl_options{} = Opts, RecordCB) -> + new_ssl_options(Rest, Opts#ssl_options{honor_ecc_order = validate_option(honor_ecc_order, Value)}, RecordCB); +new_ssl_options([{eccs, Value} | Rest], #ssl_options{} = Opts, RecordCB) -> + new_ssl_options(Rest, + Opts#ssl_options{eccs = + handle_eccs_option(Value, RecordCB:highest_protocol_version()) + }, + RecordCB); +new_ssl_options([{signature_algs, Value} | Rest], #ssl_options{} = Opts, RecordCB) -> + new_ssl_options(Rest, + Opts#ssl_options{signature_algs = + handle_hashsigns_option(Value, + tls_version(RecordCB:highest_protocol_version()))}, + RecordCB); +new_ssl_options([{protocol, dtls = Value} | Rest], #ssl_options{} = Opts, dtls_record = RecordCB) -> + new_ssl_options(Rest, Opts#ssl_options{protocol = Value}, RecordCB); +new_ssl_options([{protocol, tls = Value} | Rest], #ssl_options{} = Opts, tls_record = RecordCB) -> + new_ssl_options(Rest, Opts#ssl_options{protocol = Value}, RecordCB); new_ssl_options([{Key, Value} | _Rest], #ssl_options{}, _) -> throw({error, {options, {Key, Value}}}). @@ -1149,6 +1587,12 @@ handle_verify_options(Opts, CaCerts) -> DefaultVerifyNoneFun = {fun(_,{bad_cert, _}, UserState) -> {valid, UserState}; + (_,{extension, #'Extension'{critical = true}}, UserState) -> + %% This extension is marked as critical, so + %% certificate verification should fail if we don't + %% understand the extension. However, this is + %% `verify_none', so let's accept it anyway. + {valid, UserState}; (_,{extension, _}, UserState) -> {unknown, UserState}; (_, valid, UserState) -> @@ -1164,28 +1608,79 @@ handle_verify_options(Opts, CaCerts) -> PartialChainHanlder = handle_option(partial_chain, Opts, fun(_) -> unknown_ca end), + VerifyClientOnce = handle_option(verify_client_once, Opts, false), + %% Handle 0, 1, 2 for backwards compatibility case proplists:get_value(verify, Opts, verify_none) of 0 -> {verify_none, false, ca_cert_default(verify_none, VerifyNoneFun, CaCerts), - VerifyNoneFun, PartialChainHanlder}; + VerifyNoneFun, PartialChainHanlder, VerifyClientOnce}; 1 -> {verify_peer, false, ca_cert_default(verify_peer, UserVerifyFun, CaCerts), - UserVerifyFun, PartialChainHanlder}; + UserVerifyFun, PartialChainHanlder, VerifyClientOnce}; 2 -> {verify_peer, true, ca_cert_default(verify_peer, UserVerifyFun, CaCerts), - UserVerifyFun, PartialChainHanlder}; + UserVerifyFun, PartialChainHanlder, VerifyClientOnce}; verify_none -> {verify_none, false, ca_cert_default(verify_none, VerifyNoneFun, CaCerts), - VerifyNoneFun, PartialChainHanlder}; + VerifyNoneFun, PartialChainHanlder, VerifyClientOnce}; verify_peer -> {verify_peer, UserFailIfNoPeerCert, ca_cert_default(verify_peer, UserVerifyFun, CaCerts), - UserVerifyFun, PartialChainHanlder}; + UserVerifyFun, PartialChainHanlder, VerifyClientOnce}; Value -> throw({error, {options, {verify, Value}}}) end. + +default_option_role(Role, Value, Role) -> + Value; +default_option_role(_,_,_) -> + undefined. + +default_cb_info(tls) -> + {gen_tcp, tcp, tcp_closed, tcp_error}; +default_cb_info(dtls) -> + {gen_udp, udp, udp_closed, udp_error}. + +include_security_info([]) -> + false; +include_security_info([Item | Items]) -> + case lists:member(Item, [client_random, server_random, master_secret]) of + true -> + true; + false -> + include_security_info(Items) + end. + +server_name_indication_default(Host) when is_list(Host) -> + Host; +server_name_indication_default(_) -> + undefined. + + +reject_alpn_next_prot_options(Opts) -> + AlpnNextOpts = [alpn_advertised_protocols, + alpn_preferred_protocols, + next_protocols_advertised, + next_protocol_selector, + client_preferred_next_protocols], + reject_alpn_next_prot_options(AlpnNextOpts, Opts). + +reject_alpn_next_prot_options([], _) -> + ok; +reject_alpn_next_prot_options([Opt| AlpnNextOpts], Opts) -> + case lists:keyfind(Opt, 1, Opts) of + {Opt, Value} -> + throw({error, {options, {not_supported_in_sslv3, {Opt, Value}}}}); + false -> + reject_alpn_next_prot_options(AlpnNextOpts, Opts) + end. + +add_filter(undefined, Filters) -> + Filters; +add_filter(Filter, Filters) -> + [Filter | Filters]. diff --git a/lib/ssl/src/ssl_admin_sup.erl b/lib/ssl/src/ssl_admin_sup.erl new file mode 100644 index 0000000000..9c96435753 --- /dev/null +++ b/lib/ssl/src/ssl_admin_sup.erl @@ -0,0 +1,95 @@ +%% +%% %CopyrightBegin% +%% +%% 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. +%% 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_admin_sup). + +-behaviour(supervisor). + +%% API +-export([start_link/0, manager_opts/0]). + +%% Supervisor callback +-export([init/1]). + +%%%========================================================================= +%%% API +%%%========================================================================= + +-spec start_link() -> {ok, pid()} | ignore | {error, term()}. + +start_link() -> + supervisor:start_link({local, ?MODULE}, ?MODULE, []). + +%%%========================================================================= +%%% Supervisor callback +%%%========================================================================= + +init([]) -> + PEMCache = pem_cache_child_spec(), + SessionCertManager = session_and_cert_manager_child_spec(), + {ok, {{rest_for_one, 10, 3600}, [PEMCache, SessionCertManager]}}. + +manager_opts() -> + CbOpts = case application:get_env(ssl, session_cb) of + {ok, Cb} when is_atom(Cb) -> + InitArgs = session_cb_init_args(), + [{session_cb, Cb}, {session_cb_init_args, InitArgs}]; + _ -> + [] + end, + case application:get_env(ssl, session_lifetime) of + {ok, Time} when is_integer(Time) -> + [{session_lifetime, Time}| CbOpts]; + _ -> + CbOpts + end. + +%%-------------------------------------------------------------------- +%%% Internal functions +%%-------------------------------------------------------------------- + +pem_cache_child_spec() -> + Name = ssl_pem_cache, + StartFunc = {ssl_pem_cache, start_link, [[]]}, + Restart = permanent, + Shutdown = 4000, + Modules = [ssl_pem_cache], + Type = worker, + {Name, StartFunc, Restart, Shutdown, Type, Modules}. + +session_and_cert_manager_child_spec() -> + Opts = manager_opts(), + Name = ssl_manager, + StartFunc = {ssl_manager, start_link, [Opts]}, + Restart = permanent, + Shutdown = 4000, + Modules = [ssl_manager], + Type = worker, + {Name, StartFunc, Restart, Shutdown, Type, Modules}. + +session_cb_init_args() -> + case application:get_env(ssl, session_cb_init_args) of + {ok, Args} when is_list(Args) -> + Args; + _ -> + [] + end. diff --git a/lib/ssl/src/ssl_alert.erl b/lib/ssl/src/ssl_alert.erl index 9e372f739a..34e9797f1f 100644 --- a/lib/ssl/src/ssl_alert.erl +++ b/lib/ssl/src/ssl_alert.erl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2015. All Rights Reserved. +%% Copyright Ericsson AB 2007-2018. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. +%% 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 %% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% 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% %% @@ -31,22 +32,13 @@ -include("ssl_record.hrl"). -include("ssl_internal.hrl"). --export([encode/3, decode/1, alert_txt/1, reason_code/2]). +-export([decode/1, own_alert_txt/1, alert_txt/1, reason_code/2]). %%==================================================================== %% Internal application API %%==================================================================== %%-------------------------------------------------------------------- --spec encode(#alert{}, ssl_record:ssl_version(), #connection_states{}) -> - {iolist(), #connection_states{}}. -%% -%% Description: Encodes an alert -%%-------------------------------------------------------------------- -encode(#alert{} = Alert, Version, ConnectionStates) -> - ssl_record:encode_alert_record(Alert, Version, ConnectionStates). - -%%-------------------------------------------------------------------- -spec decode(binary()) -> [#alert{}] | #alert{}. %% %% Description: Decode alert(s), will return a singel own alert if peer @@ -56,7 +48,9 @@ decode(Bin) -> decode(Bin, [], 0). %%-------------------------------------------------------------------- --spec reason_code(#alert{}, client | server) -> closed | {essl, string()}. +-spec reason_code(#alert{}, client | server) -> + closed | {tls_alert, unicode:chardata()}. +%-spec reason_code(#alert{}, client | server) -> closed | {essl, string()}. %% %% Description: Returns the error reason that will be returned to the %% user. @@ -65,17 +59,37 @@ decode(Bin) -> reason_code(#alert{description = ?CLOSE_NOTIFY}, _) -> closed; reason_code(#alert{description = Description}, _) -> - {tls_alert, description_txt(Description)}. + {tls_alert, string:casefold(description_txt(Description))}. %%-------------------------------------------------------------------- --spec alert_txt(#alert{}) -> string(). +-spec own_alert_txt(#alert{}) -> string(). %% -%% Description: Returns the error string for given alert. +%% Description: Returns the error string for given alert generated +%% by the erlang implementation. %%-------------------------------------------------------------------- +own_alert_txt(#alert{level = Level, description = Description, where = {Mod,Line}, reason = undefined, role = Role}) -> + "at " ++ Mod ++ ":" ++ integer_to_list(Line) ++ " generated " ++ string:uppercase(atom_to_list(Role)) ++ " ALERT: " ++ + level_txt(Level) ++ description_txt(Description); +own_alert_txt(#alert{reason = Reason} = Alert) -> + BaseTxt = own_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. -alert_txt(#alert{level = Level, description = Description, where = {Mod,Line}}) -> - Mod ++ ":" ++ integer_to_list(Line) ++ ":" ++ - level_txt(Level) ++" "++ description_txt(Description). +%%-------------------------------------------------------------------- +-spec alert_txt(#alert{}) -> string(). +%% +%% Description: Returns the error string for given alert received from +%% the peer. +%%-------------------------------------------------------------------- +alert_txt(#alert{level = Level, description = Description, reason = undefined, role = Role}) -> + "received " ++ string:uppercase(atom_to_list(Role)) ++ " ALERT: " ++ + 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 @@ -84,7 +98,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); @@ -92,76 +106,78 @@ 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, []). level_txt(?WARNING) -> - "Warning:"; + "Warning - "; level_txt(?FATAL) -> - "Fatal error:". + "Fatal - ". description_txt(?CLOSE_NOTIFY) -> - "close notify"; + "Close Notify"; description_txt(?UNEXPECTED_MESSAGE) -> - "unexpected message"; + "Unexpected Message"; description_txt(?BAD_RECORD_MAC) -> - "bad record mac"; -description_txt(?DECRYPTION_FAILED) -> - "decryption failed"; + "Bad Record MAC"; +description_txt(?DECRYPTION_FAILED_RESERVED) -> + "Decryption Failed Reserved"; description_txt(?RECORD_OVERFLOW) -> - "record overflow"; + "Record Overflow"; description_txt(?DECOMPRESSION_FAILURE) -> - "decompression failure"; + "Decompression Failure"; description_txt(?HANDSHAKE_FAILURE) -> - "handshake failure"; + "Handshake Failure"; description_txt(?NO_CERTIFICATE_RESERVED) -> - "No certificate reserved"; + "No Certificate Reserved"; description_txt(?BAD_CERTIFICATE) -> - "bad certificate"; + "Bad Certificate"; description_txt(?UNSUPPORTED_CERTIFICATE) -> - "unsupported certificate"; + "Unsupported Certificate"; description_txt(?CERTIFICATE_REVOKED) -> - "certificate revoked"; + "Certificate Revoked"; description_txt(?CERTIFICATE_EXPIRED) -> - "certificate expired"; + "Certificate Expired"; description_txt(?CERTIFICATE_UNKNOWN) -> - "certificate unknown"; + "Certificate Unknown"; description_txt(?ILLEGAL_PARAMETER) -> - "illegal parameter"; + "Illegal Parameter"; description_txt(?UNKNOWN_CA) -> - "unknown ca"; + "Unknown CA"; description_txt(?ACCESS_DENIED) -> - "access denied"; + "Access Denied"; description_txt(?DECODE_ERROR) -> - "decode error"; + "Decode Error"; description_txt(?DECRYPT_ERROR) -> - "decrypt error"; + "Decrypt Error"; description_txt(?EXPORT_RESTRICTION) -> - "export restriction"; + "Export Restriction"; description_txt(?PROTOCOL_VERSION) -> - "protocol version"; + "Protocol Version"; description_txt(?INSUFFICIENT_SECURITY) -> - "insufficient security"; + "Insufficient Security"; description_txt(?INTERNAL_ERROR) -> - "internal error"; + "Internal Error"; description_txt(?USER_CANCELED) -> - "user canceled"; + "User Canceled"; description_txt(?NO_RENEGOTIATION) -> - "no renegotiation"; + "No Renegotiation"; description_txt(?UNSUPPORTED_EXTENSION) -> - "unsupported extension"; + "Unsupported Extension"; description_txt(?CERTIFICATE_UNOBTAINABLE) -> - "certificate unobtainable"; + "Certificate Unobtainable"; description_txt(?UNRECOGNISED_NAME) -> - "unrecognised name"; + "Unrecognised Name"; description_txt(?BAD_CERTIFICATE_STATUS_RESPONSE) -> - "bad certificate status response"; + "Bad Certificate Status Response"; description_txt(?BAD_CERTIFICATE_HASH_VALUE) -> - "bad certificate hash value"; + "Bad Certificate Hash Value"; description_txt(?UNKNOWN_PSK_IDENTITY) -> - "unknown psk identity"; + "Unknown Psk Identity"; description_txt(?INAPPROPRIATE_FALLBACK) -> - "inappropriate fallback"; + "Inappropriate Fallback"; +description_txt(?NO_APPLICATION_PROTOCOL) -> + "No application protocol"; description_txt(Enum) -> lists:flatten(io_lib:format("unsupported/unknown alert: ~p", [Enum])). diff --git a/lib/ssl/src/ssl_alert.hrl b/lib/ssl/src/ssl_alert.hrl index a3619e4a35..b23123905e 100644 --- a/lib/ssl/src/ssl_alert.hrl +++ b/lib/ssl/src/ssl_alert.hrl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2015. All Rights Reserved. +%% Copyright Ericsson AB 2007-2018. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% 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% %% @@ -39,7 +40,7 @@ %% close_notify(0), %% unexpected_message(10), %% bad_record_mac(20), -%% decryption_failed(21), +%% decryption_failed_reserved(21), %% record_overflow(22), %% decompression_failure(30), %% handshake_failure(40), @@ -69,13 +70,15 @@ %% bad_certificate_hash_value(114), %% RFC 4366 %% unknown_psk_identity(115), +%% RFC 7301 +%% no_application_protocol(120), %% (255) %% } AlertDescription; -define(CLOSE_NOTIFY, 0). -define(UNEXPECTED_MESSAGE, 10). -define(BAD_RECORD_MAC, 20). --define(DECRYPTION_FAILED, 21). +-define(DECRYPTION_FAILED_RESERVED, 21). -define(RECORD_OVERFLOW, 22). -define(DECOMPRESSION_FAILURE, 30). -define(HANDSHAKE_FAILURE, 40). @@ -103,8 +106,10 @@ -define(BAD_CERTIFICATE_STATUS_RESPONSE, 113). -define(BAD_CERTIFICATE_HASH_VALUE, 114). -define(UNKNOWN_PSK_IDENTITY, 115). +-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). @@ -112,6 +117,8 @@ -record(alert, { level, description, - where = {?FILE, ?LINE} + where = {?FILE, ?LINE}, + role, + reason }). -endif. % -ifdef(ssl_alert). diff --git a/lib/ssl/src/ssl_api.hrl b/lib/ssl/src/ssl_api.hrl index 22185ff60a..7b7b1cbcd9 100644 --- a/lib/ssl/src/ssl_api.hrl +++ b/lib/ssl/src/ssl_api.hrl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2013-2014. All Rights Reserved. +%% Copyright Ericsson AB 2013-2018. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. +%% 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 %% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% 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% %% @@ -41,7 +42,8 @@ {verify, verify_type()} | {verify_fun, {fun(), InitialUserState::term()}} | {fail_if_no_peer_cert, boolean()} | {depth, integer()} | - {cert, Der::binary()} | {certfile, path()} | {key, Der::binary()} | + {cert, Der::binary()} | {certfile, path()} | + {key, {private_key_type(), Der::binary()}} | {keyfile, path()} | {password, string()} | {cacerts, [Der::binary()]} | {cacertfile, path()} | {dh, Der::binary()} | {dhfile, path()} | {user_lookup_fun, {fun(), InitialUserState::term()}} | @@ -49,12 +51,14 @@ {srp_identity, {string(), string()}} | {ciphers, ciphers()} | {ssl_imp, ssl_imp()} | {reuse_sessions, boolean()} | {reuse_session, fun()} | {hibernate_after, integer()|undefined} | + {alpn_advertised_protocols, [binary()]} | + {alpn_preferred_protocols, [binary()]} | {next_protocols_advertised, list(binary())} | {client_preferred_next_protocols, binary(), client | server, list(binary())}. -type verify_type() :: verify_none | verify_peer. -type path() :: string(). --type ciphers() :: [ssl_cipher:erl_cipher_suite()] | +-type ciphers() :: [ssl_cipher_format:erl_cipher_suite()] | string(). % (according to old API) -type ssl_imp() :: new | old. @@ -62,4 +66,11 @@ ClosedTag::atom(), ErrTag::atom()}}. -type prf_random() :: client_random | server_random. +-type private_key_type() :: rsa | %% Backwards compatibility + dsa | %% Backwards compatibility + 'RSAPrivateKey' | + 'DSAPrivateKey' | + 'ECPrivateKey' | + 'PrivateKeyInfo'. + -endif. % -ifdef(ssl_api). diff --git a/lib/ssl/src/ssl_app.erl b/lib/ssl/src/ssl_app.erl index 0c475a6d01..62e8765d4a 100644 --- a/lib/ssl/src/ssl_app.erl +++ b/lib/ssl/src/ssl_app.erl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1998-2011. All Rights Reserved. +%% Copyright Ericsson AB 1998-2016. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% 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% %% diff --git a/lib/ssl/src/ssl_certificate.erl b/lib/ssl/src/ssl_certificate.erl index 30d224fee2..549e557beb 100644 --- a/lib/ssl/src/ssl_certificate.erl +++ b/lib/ssl/src/ssl_certificate.erl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2014 All Rights Reserved. +%% Copyright Ericsson AB 2007-2018 All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. +%% 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 %% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% 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% %% @@ -32,13 +33,16 @@ -export([trusted_cert_and_path/4, certificate_chain/3, + certificate_chain/4, file_to_certificats/2, - validate_extension/3, + file_to_crls/2, + validate/3, is_valid_extkey_usage/2, is_valid_key_usage/2, select_extension/2, extensions_list/1, - public_key_type/1 + public_key_type/1, + foldl_db/3 ]). %%==================================================================== @@ -54,15 +58,15 @@ %% errors. Returns {RootCert, Path, VerifyErrors} %%-------------------------------------------------------------------- trusted_cert_and_path(CertChain, CertDbHandle, CertDbRef, PartialChainHandler) -> - Path = [Cert | _] = lists:reverse(CertChain), - OtpCert = public_key:pkix_decode_cert(Cert, otp), + Path = [BinCert | _] = lists:reverse(CertChain), + OtpCert = public_key:pkix_decode_cert(BinCert, otp), SignedAndIssuerID = case public_key:pkix_is_self_signed(OtpCert) of true -> {ok, IssuerId} = public_key:pkix_issuer_id(OtpCert, self), {self, IssuerId}; false -> - other_issuer(OtpCert, CertDbHandle) + other_issuer(OtpCert, BinCert, CertDbHandle, CertDbRef) end, case SignedAndIssuerID of @@ -77,22 +81,39 @@ trusted_cert_and_path(CertChain, CertDbHandle, CertDbRef, PartialChainHandler) - %% Trusted must be selfsigned or it is an incomplete chain handle_path(Trusted, Path, PartialChainHandler); _ -> - %% Root CA could not be verified + %% Root CA could not be verified, but partial + %% chain handler may trusted a cert that we got handle_incomplete_chain(Path, PartialChainHandler) end end. %%-------------------------------------------------------------------- --spec certificate_chain(undefined | binary(), db_handle(), certdb_ref()) -> - {error, no_cert} | {ok, [der_cert()]}. +-spec certificate_chain(undefined | binary() | #'OTPCertificate'{} , db_handle(), certdb_ref()) -> + {error, no_cert} | {ok, #'OTPCertificate'{} | undefined, [der_cert()]}. %% %% Description: Return the certificate chain to send to peer. %%-------------------------------------------------------------------- certificate_chain(undefined, _, _) -> {error, no_cert}; -certificate_chain(OwnCert, CertDbHandle, CertsDbRef) -> +certificate_chain(OwnCert, CertDbHandle, CertsDbRef) when is_binary(OwnCert) -> ErlCert = public_key:pkix_decode_cert(OwnCert, otp), - certificate_chain(ErlCert, OwnCert, CertDbHandle, CertsDbRef, [OwnCert]). + certificate_chain(ErlCert, OwnCert, CertDbHandle, CertsDbRef, [OwnCert], []); +certificate_chain(OwnCert, CertDbHandle, CertsDbRef) -> + DerCert = public_key:pkix_encode('OTPCertificate', OwnCert, otp), + certificate_chain(OwnCert, DerCert, CertDbHandle, CertsDbRef, [DerCert], []). + +%%-------------------------------------------------------------------- +-spec certificate_chain(undefined | binary() | #'OTPCertificate'{} , db_handle(), certdb_ref(), [der_cert()]) -> + {error, no_cert} | {ok, #'OTPCertificate'{} | undefined, [der_cert()]}. +%% +%% Description: Create certificate chain with certs from +%%-------------------------------------------------------------------- +certificate_chain(Cert, CertDbHandle, CertsDbRef, Candidates) when is_binary(Cert) -> + ErlCert = public_key:pkix_decode_cert(Cert, otp), + certificate_chain(ErlCert, Cert, CertDbHandle, CertsDbRef, [Cert], Candidates); +certificate_chain(Cert, CertDbHandle, CertsDbRef, Candidates) -> + DerCert = public_key:pkix_encode('OTPCertificate', Cert, otp), + certificate_chain(Cert, DerCert, CertDbHandle, CertsDbRef, [DerCert], Candidates). %%-------------------------------------------------------------------- -spec file_to_certificats(binary(), term()) -> [der_cert()]. %% @@ -101,30 +122,42 @@ certificate_chain(OwnCert, CertDbHandle, CertsDbRef) -> file_to_certificats(File, DbHandle) -> {ok, List} = ssl_manager:cache_pem_file(File, DbHandle), [Bin || {'Certificate', Bin, not_encrypted} <- List]. + %%-------------------------------------------------------------------- --spec validate_extension(term(), {extension, #'Extension'{}} | {bad_cert, atom()} | valid, - term()) -> {valid, term()} | - {fail, tuple()} | - {unknown, term()}. +-spec file_to_crls(binary(), term()) -> [der_cert()]. +%% +%% Description: Return list of DER encoded certificates. +%%-------------------------------------------------------------------- +file_to_crls(File, DbHandle) -> + {ok, List} = ssl_manager:cache_pem_file(File, DbHandle), + [Bin || {'CertificateList', Bin, not_encrypted} <- List]. + +%%-------------------------------------------------------------------- +-spec validate(term(), {extension, #'Extension'{}} | {bad_cert, atom()} | valid, + term()) -> {valid, term()} | + {fail, tuple()} | + {unknown, term()}. %% %% Description: Validates ssl/tls specific extensions %%-------------------------------------------------------------------- -validate_extension(_,{extension, #'Extension'{extnID = ?'id-ce-extKeyUsage', - extnValue = KeyUse}}, Role) -> +validate(_,{extension, #'Extension'{extnID = ?'id-ce-extKeyUsage', + extnValue = KeyUse}}, UserState = {Role, _,_, _, _, _}) -> case is_valid_extkey_usage(KeyUse, Role) of true -> - {valid, Role}; + {valid, UserState}; false -> {fail, {bad_cert, invalid_ext_key_usage}} end; -validate_extension(_, {bad_cert, _} = Reason, _) -> +validate(_, {extension, _}, UserState) -> + {unknown, UserState}; +validate(_, {bad_cert, _} = Reason, _) -> {fail, Reason}; -validate_extension(_, {extension, _}, Role) -> - {unknown, Role}; -validate_extension(_, valid, Role) -> - {valid, Role}; -validate_extension(_, valid_peer, Role) -> - {valid, Role}. +validate(_, valid, UserState) -> + {valid, UserState}; +validate(Cert, valid_peer, UserState = {client, _,_, {Hostname, Customize}, _, _}) when Hostname =/= disable -> + verify_hostname(Hostname, Customize, Cert, UserState); +validate(_, valid_peer, UserState) -> + {valid, UserState}. %%-------------------------------------------------------------------- -spec is_valid_key_usage(list(), term()) -> boolean(). @@ -170,9 +203,20 @@ public_key_type(?'id-ecPublicKey') -> ec. %%-------------------------------------------------------------------- +-spec foldl_db(fun(), db_handle() | {extracted, list()}, list()) -> + {ok, term()} | issuer_not_found. +%% +%% Description: +%%-------------------------------------------------------------------- +foldl_db(IsIssuerFun, CertDbHandle, []) -> + ssl_pkix_db:foldl(IsIssuerFun, issuer_not_found, CertDbHandle); +foldl_db(IsIssuerFun, _, [_|_] = ListDb) -> + lists:foldl(IsIssuerFun, issuer_not_found, ListDb). + +%%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- -certificate_chain(OtpCert, _Cert, CertDbHandle, CertsDbRef, Chain) -> +certificate_chain(OtpCert, BinCert, CertDbHandle, CertsDbRef, Chain, ListDb) -> IssuerAndSelfSigned = case public_key:pkix_is_self_signed(OtpCert) of true -> @@ -183,46 +227,47 @@ certificate_chain(OtpCert, _Cert, CertDbHandle, CertsDbRef, Chain) -> case IssuerAndSelfSigned of {_, true = SelfSigned} -> - certificate_chain(CertDbHandle, CertsDbRef, Chain, ignore, ignore, SelfSigned); + do_certificate_chain(CertDbHandle, CertsDbRef, Chain, ignore, ignore, SelfSigned, ListDb); {{error, issuer_not_found}, SelfSigned} -> - case find_issuer(OtpCert, CertDbHandle) of + case find_issuer(OtpCert, BinCert, CertDbHandle, CertsDbRef, ListDb) of {ok, {SerialNr, Issuer}} -> - certificate_chain(CertDbHandle, CertsDbRef, Chain, - SerialNr, Issuer, SelfSigned); + do_certificate_chain(CertDbHandle, CertsDbRef, Chain, + SerialNr, Issuer, SelfSigned, ListDb); _ -> %% Guess the the issuer must be the root %% certificate. The verification of the %% cert chain will fail if guess is %% incorrect. - {ok, lists:reverse(Chain)} + {ok, undefined, lists:reverse(Chain)} end; {{ok, {SerialNr, Issuer}}, SelfSigned} -> - certificate_chain(CertDbHandle, CertsDbRef, Chain, SerialNr, Issuer, SelfSigned) + do_certificate_chain(CertDbHandle, CertsDbRef, Chain, SerialNr, Issuer, SelfSigned, ListDb) end. -certificate_chain(_,_, Chain, _SerialNr, _Issuer, true) -> - {ok, lists:reverse(Chain)}; +do_certificate_chain(_, _, [RootCert | _] = Chain, _, _, true, _) -> + {ok, RootCert, lists:reverse(Chain)}; -certificate_chain(CertDbHandle, CertsDbRef, Chain, SerialNr, Issuer, _SelfSigned) -> +do_certificate_chain(CertDbHandle, CertsDbRef, Chain, SerialNr, Issuer, _, ListDb) -> case ssl_manager:lookup_trusted_cert(CertDbHandle, CertsDbRef, SerialNr, Issuer) of {ok, {IssuerCert, ErlCert}} -> ErlCert = public_key:pkix_decode_cert(IssuerCert, otp), certificate_chain(ErlCert, IssuerCert, - CertDbHandle, CertsDbRef, [IssuerCert | Chain]); + CertDbHandle, CertsDbRef, [IssuerCert | Chain], ListDb); _ -> %% The trusted cert may be obmitted from the chain as the %% counter part needs to have it anyway to be able to %% verify it. - {ok, lists:reverse(Chain)} + {ok, undefined, lists:reverse(Chain)} end. -find_issuer(OtpCert, CertDbHandle) -> + +find_issuer(OtpCert, BinCert, CertDbHandle, CertsDbRef, ListDb) -> IsIssuerFun = fun({_Key, {_Der, #'OTPCertificate'{} = ErlCertCandidate}}, Acc) -> case public_key:pkix_is_issuer(OtpCert, ErlCertCandidate) of true -> - case verify_cert_signer(OtpCert, ErlCertCandidate#'OTPCertificate'.tbsCertificate) of + case verify_cert_signer(BinCert, ErlCertCandidate#'OTPCertificate'.tbsCertificate) of true -> throw(public_key:pkix_issuer_id(ErlCertCandidate, self)); false -> @@ -235,14 +280,29 @@ find_issuer(OtpCert, CertDbHandle) -> Acc end, - try ssl_pkix_db:foldl(IsIssuerFun, issuer_not_found, CertDbHandle) of - issuer_not_found -> - {error, issuer_not_found} - catch - {ok, _IssuerId} = Return -> - Return + Result = case is_reference(CertsDbRef) of + true -> + do_find_issuer(IsIssuerFun, CertDbHandle, ListDb); + false -> + {extracted, CertsData} = CertsDbRef, + DB = [Entry || {decoded, Entry} <- CertsData], + do_find_issuer(IsIssuerFun, CertDbHandle, DB) + end, + case Result of + issuer_not_found -> + {error, issuer_not_found}; + Result -> + Result end. +do_find_issuer(IssuerFun, CertDbHandle, CertDb) -> + try + foldl_db(IssuerFun, CertDbHandle, CertDb) + catch + throw:{ok, _} = Return -> + Return + end. + is_valid_extkey_usage(KeyUse, client) -> %% Client wants to verify server is_valid_key_usage(KeyUse,?'id-kp-serverAuth'); @@ -250,9 +310,9 @@ is_valid_extkey_usage(KeyUse, server) -> %% Server wants to verify client is_valid_key_usage(KeyUse, ?'id-kp-clientAuth'). -verify_cert_signer(OtpCert, SignerTBSCert) -> +verify_cert_signer(BinCert, SignerTBSCert) -> PublicKey = public_key(SignerTBSCert#'OTPTBSCertificate'.subjectPublicKeyInfo), - public_key:pkix_verify(public_key:pkix_encode('OTPCertificate', OtpCert, otp), PublicKey). + public_key:pkix_verify(BinCert, PublicKey). public_key(#'OTPSubjectPublicKeyInfo'{algorithm = #'PublicKeyAlgorithm'{algorithm = ?'id-ecPublicKey', parameters = Params}, @@ -266,12 +326,12 @@ public_key(#'OTPSubjectPublicKeyInfo'{algorithm = #'PublicKeyAlgorithm'{algorith subjectPublicKey = Key}) -> {Key, Params}. -other_issuer(OtpCert, CertDbHandle) -> +other_issuer(OtpCert, BinCert, CertDbHandle, CertDbRef) -> case public_key:pkix_issuer_id(OtpCert, other) of {ok, IssuerId} -> {other, IssuerId}; {error, issuer_not_found} -> - case find_issuer(OtpCert, CertDbHandle) of + case find_issuer(OtpCert, BinCert, CertDbHandle, CertDbRef, []) of {ok, IssuerId} -> {other, IssuerId}; Other -> @@ -303,3 +363,32 @@ new_trusteded_chain(DerCert, [_ | Rest]) -> new_trusteded_chain(DerCert, Rest); new_trusteded_chain(_, []) -> unknown_ca. + +verify_hostname({fallback, Hostname}, Customize, Cert, UserState) when is_list(Hostname) -> + case public_key:pkix_verify_hostname(Cert, [{dns_id, Hostname}], Customize) of + true -> + {valid, UserState}; + false -> + case public_key:pkix_verify_hostname(Cert, [{ip, Hostname}], Customize) of + true -> + {valid, UserState}; + false -> + {fail, {bad_cert, hostname_check_failed}} + end + end; + +verify_hostname({fallback, Hostname}, Customize, Cert, UserState) -> + case public_key:pkix_verify_hostname(Cert, [{ip, Hostname}], Customize) of + true -> + {valid, UserState}; + false -> + {fail, {bad_cert, hostname_check_failed}} + end; + +verify_hostname(Hostname, Customize, Cert, UserState) -> + case public_key:pkix_verify_hostname(Cert, [{dns_id, Hostname}], Customize) of + true -> + {valid, UserState}; + false -> + {fail, {bad_cert, hostname_check_failed}} + end. diff --git a/lib/ssl/src/ssl_cipher.erl b/lib/ssl/src/ssl_cipher.erl index c2af0f946a..66a00c60f1 100644 --- a/lib/ssl/src/ssl_cipher.erl +++ b/lib/ssl/src/ssl_cipher.erl @@ -1,18 +1,19 @@ -%% +% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2015. All Rights Reserved. +%% Copyright Ericsson AB 2007-2018. 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 %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. +%% http://www.apache.org/licenses/LICENSE-2.0 %% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% 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% %% @@ -32,31 +33,25 @@ -include("ssl_alert.hrl"). -include_lib("public_key/include/public_key.hrl"). --export([security_parameters/2, security_parameters/3, suite_definition/1, - decipher/6, cipher/5, suite/1, suites/1, all_suites/1, - ec_keyed_suites/0, anonymous_suites/0, psk_suites/1, srp_suites/0, - 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]). - --export_type([cipher_suite/0, - erl_cipher_suite/0, openssl_cipher_suite/0, - key_algo/0]). - --type cipher() :: null |rc4_128 | idea_cbc | des40_cbc | des_cbc | '3des_ede_cbc' - | aes_128_cbc | aes_256_cbc. --type hash() :: null | sha | md5 | sha224 | sha256 | sha384 | sha512. --type key_algo() :: null | rsa | dhe_rsa | dhe_dss | ecdhe_ecdsa| ecdh_ecdsa | ecdh_rsa| srp_rsa| srp_dss | psk | dhe_psk | rsa_psk | dh_anon | ecdh_anon | srp_anon. --type erl_cipher_suite() :: {key_algo(), cipher(), hash()}. --type int_cipher_suite() :: {key_algo(), cipher(), hash(), hash() | default_prf}. --type cipher_suite() :: binary(). --type cipher_enum() :: integer(). --type openssl_cipher_suite() :: string(). - +-export([security_parameters/2, security_parameters/3, + cipher_init/3, nonce_seed/2, decipher/6, cipher/5, aead_encrypt/5, aead_decrypt/6, + suites/1, all_suites/1, crypto_support_filters/0, + chacha_suites/1, anonymous_suites/1, psk_suites/1, psk_suites_anon/1, + srp_suites/0, srp_suites_anon/0, + rc4_suites/1, des_suites/1, rsa_suites/1, + filter/3, filter_suites/1, filter_suites/2, + hash_algorithm/1, sign_algorithm/1, is_acceptable_hash/2, is_fallback/1, + random_bytes/1, calc_mac_hash/4, + is_stream_ciphersuite/1]). -compile(inline). +-type cipher_enum() :: integer(). + +-export_type([cipher_enum/0]). + %%-------------------------------------------------------------------- --spec security_parameters(cipher_suite(), #security_parameters{}) -> +-spec security_parameters(ssl_cipher_format:cipher_suite(), #security_parameters{}) -> #security_parameters{}. %% Only security_parameters/2 should call security_parameters/3 with undefined as %% first argument. @@ -66,14 +61,16 @@ security_parameters(?TLS_NULL_WITH_NULL_NULL = CipherSuite, SecParams) -> security_parameters(undefined, CipherSuite, SecParams). %%-------------------------------------------------------------------- --spec security_parameters(ssl_record:ssl_version() | undefined, cipher_suite(), #security_parameters{}) -> +-spec security_parameters(ssl_record:ssl_version() | undefined, + ssl_cipher_format:cipher_suite(), #security_parameters{}) -> #security_parameters{}. %% %% Description: Returns a security parameters record where the %% cipher values has been updated according to <CipherSuite> %%------------------------------------------------------------------- security_parameters(Version, CipherSuite, SecParams) -> - { _, Cipher, Hash, PrfHashAlg} = suite_definition(CipherSuite), + #{cipher := Cipher, mac := Hash, + prf := PrfHashAlg} = ssl_cipher_format:suite_definition(CipherSuite), SecParams#security_parameters{ cipher_suite = CipherSuite, bulk_cipher_algorithm = bulk_cipher_algorithm(Cipher), @@ -82,25 +79,42 @@ security_parameters(Version, CipherSuite, SecParams) -> expanded_key_material_length = expanded_key_material(Cipher), key_material_length = key_material(Cipher), iv_size = iv_size(Cipher), - mac_algorithm = hash_algorithm(Hash), + mac_algorithm = mac_algorithm(Hash), prf_algorithm = prf_algorithm(PrfHashAlg, Version), hash_size = hash_size(Hash)}. %%-------------------------------------------------------------------- +-spec cipher_init(cipher_enum(), binary(), binary()) -> #cipher_state{}. +%% +%% Description: Initializes the #cipher_state according to BCA +%%------------------------------------------------------------------- +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>> = random_bytes(8), + #cipher_state{iv = IV, key = Key, nonce = Nonce, tag_len = 16}; +cipher_init(?CHACHA20_POLY1305, IV, Key) -> + #cipher_state{iv = IV, key = Key, tag_len = 16}; +cipher_init(_BCA, IV, Key) -> + #cipher_state{iv = IV, key = Key}. + +nonce_seed(Seed, CipherState) -> + CipherState#cipher_state{nonce = Seed}. + +%%-------------------------------------------------------------------- -spec cipher(cipher_enum(), #cipher_state{}, binary(), iodata(), ssl_record:ssl_version()) -> {binary(), #cipher_state{}}. %% %% Description: Encrypts the data and the MAC using chipher described %% by cipher_enum() and updating the cipher state +%% Used for "MAC then Cipher" suites where first an HMAC of the +%% data is calculated and the data plus the HMAC is ecncrypted. %%------------------------------------------------------------------- cipher(?NULL, CipherState, <<>>, Fragment, _Version) -> GenStreamCipherList = [Fragment, <<>>], {GenStreamCipherList, CipherState}; -cipher(?RC4, CipherState, Mac, Fragment, _Version) -> - State0 = case CipherState#cipher_state.state of - undefined -> crypto:stream_init(rc4, CipherState#cipher_state.key); - S -> S - end, +cipher(?RC4, CipherState = #cipher_state{state = State0}, Mac, Fragment, _Version) -> GenStreamCipherList = [Fragment, Mac], {State1, T} = crypto:stream_encrypt(State0, GenStreamCipherList), {T, CipherState#cipher_state{state = State1}}; @@ -112,13 +126,24 @@ cipher(?'3DES', CipherState, Mac, Fragment, Version) -> block_cipher(fun(<<K1:8/binary, K2:8/binary, K3:8/binary>>, IV, T) -> crypto:block_encrypt(des3_cbc, [K1, K2, K3], IV, T) end, block_size(des_cbc), CipherState, Mac, Fragment, Version); -cipher(?AES, CipherState, Mac, Fragment, Version) -> +cipher(?AES_CBC, CipherState, Mac, Fragment, Version) -> block_cipher(fun(Key, IV, T) when byte_size(Key) =:= 16 -> crypto:block_encrypt(aes_cbc128, Key, IV, T); (Key, IV, T) when byte_size(Key) =:= 32 -> crypto:block_encrypt(aes_cbc256, Key, IV, T) end, block_size(aes_128_cbc), CipherState, Mac, Fragment, Version). +aead_encrypt(Type, Key, Nonce, Fragment, AdditionalData) -> + crypto:block_encrypt(aead_type(Type), Key, Nonce, {AdditionalData, Fragment}). + +aead_decrypt(Type, Key, Nonce, CipherText, CipherTag, AdditionalData) -> + crypto:block_decrypt(aead_type(Type), Key, Nonce, {AdditionalData, CipherText, CipherTag}). + +aead_type(?AES_GCM) -> + aes_gcm; +aead_type(?CHACHA20_POLY1305) -> + chacha20_poly1305. + build_cipher_block(BlockSz, Mac, Fragment) -> TotSz = byte_size(Mac) + erlang:iolist_size(Fragment) + 1, {PaddingLength, Padding} = get_padding(TotSz, BlockSz), @@ -148,14 +173,12 @@ block_cipher(Fun, BlockSz, #cipher_state{key=Key, iv=IV} = CS0, %% %% Description: Decrypts the data and the MAC using cipher described %% by cipher_enum() and updating the cipher state. +%% Used for "MAC then Cipher" suites where first the data is decrypted +%% and the an HMAC of the decrypted data is checked %%------------------------------------------------------------------- decipher(?NULL, _HashSz, CipherState, Fragment, _, _) -> {Fragment, <<>>, CipherState}; -decipher(?RC4, HashSz, CipherState, Fragment, _, _) -> - State0 = case CipherState#cipher_state.state of - undefined -> crypto:stream_init(rc4, CipherState#cipher_state.key); - S -> S - end, +decipher(?RC4, HashSz, CipherState = #cipher_state{state = State0}, Fragment, _, _) -> try crypto:stream_decrypt(State0, Fragment) of {State, Text} -> GSC = generic_stream_cipher_from_bin(Text, HashSz), @@ -168,7 +191,7 @@ decipher(?RC4, HashSz, CipherState, 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) -> @@ -179,7 +202,7 @@ decipher(?'3DES', HashSz, CipherState, Fragment, Version, PaddingCheck) -> block_decipher(fun(<<K1:8/binary, K2:8/binary, K3:8/binary>>, IV, T) -> crypto:block_decrypt(des3_cbc, [K1, K2, K3], IV, T) end, CipherState, HashSz, Fragment, Version, PaddingCheck); -decipher(?AES, HashSz, CipherState, Fragment, Version, PaddingCheck) -> +decipher(?AES_CBC, HashSz, CipherState, Fragment, Version, PaddingCheck) -> block_decipher(fun(Key, IV, T) when byte_size(Key) =:= 16 -> crypto:block_decrypt(aes_cbc128, Key, IV, T); (Key, IV, T) when byte_size(Key) =:= 32 -> @@ -213,893 +236,379 @@ 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. + %%-------------------------------------------------------------------- --spec suites(ssl_record:ssl_version()) -> [cipher_suite()]. +-spec suites(ssl_record:ssl_version()) -> [ssl_cipher_format:cipher_suite()]. %% %% Description: Returns a list of supported cipher suites. %%-------------------------------------------------------------------- suites({3, 0}) -> ssl_v3:suites(); -suites({3, N}) -> - tls_v1:suites(N). +suites({3, Minor}) -> + tls_v1:suites(Minor); +suites({_, Minor}) -> + dtls_v1:suites(Minor). -all_suites(Version) -> +all_suites({3, _} = Version) -> suites(Version) - ++ ssl_cipher:anonymous_suites() - ++ ssl_cipher:psk_suites(Version) - ++ ssl_cipher:srp_suites(). + ++ chacha_suites(Version) + ++ psk_suites(Version) + ++ srp_suites() + ++ rc4_suites(Version) + ++ des_suites(Version) + ++ rsa_suites(Version); + +all_suites(Version) -> + dtls_v1:all_suites(Version). +%%-------------------------------------------------------------------- +-spec chacha_suites(ssl_record:ssl_version() | integer()) -> + [ssl_cipher_format:cipher_suite()]. +%% +%% Description: Returns list of the chacha cipher suites, only supported +%% if explicitly set by user for now due to interop problems, proably need +%% to be fixed in crypto. +%%-------------------------------------------------------------------- +chacha_suites({3, _}) -> + [?TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, + ?TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, + ?TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256]; +chacha_suites(_) -> + []. + %%-------------------------------------------------------------------- --spec anonymous_suites() -> [cipher_suite()]. +-spec anonymous_suites(ssl_record:ssl_version() | integer()) -> + [ssl_cipher_format:cipher_suite()]. %% %% Description: Returns a list of the anonymous cipher suites, only supported %% if explicitly set by user. Intended only for testing. %%-------------------------------------------------------------------- -anonymous_suites() -> - [?TLS_DH_anon_WITH_RC4_128_MD5, - ?TLS_DH_anon_WITH_DES_CBC_SHA, - ?TLS_DH_anon_WITH_3DES_EDE_CBC_SHA, - ?TLS_DH_anon_WITH_AES_128_CBC_SHA, - ?TLS_DH_anon_WITH_AES_256_CBC_SHA, +anonymous_suites({3, N}) -> + srp_suites_anon() ++ anonymous_suites(N); +anonymous_suites({254, _} = Version) -> + dtls_v1:anonymous_suites(Version); +anonymous_suites(N) + when N >= 3 -> + psk_suites_anon(N) ++ + [?TLS_DH_anon_WITH_AES_128_GCM_SHA256, + ?TLS_DH_anon_WITH_AES_256_GCM_SHA384, ?TLS_DH_anon_WITH_AES_128_CBC_SHA256, ?TLS_DH_anon_WITH_AES_256_CBC_SHA256, - ?TLS_ECDH_anon_WITH_RC4_128_SHA, - ?TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA, ?TLS_ECDH_anon_WITH_AES_128_CBC_SHA, - ?TLS_ECDH_anon_WITH_AES_256_CBC_SHA]. + ?TLS_ECDH_anon_WITH_AES_256_CBC_SHA, + ?TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA, + ?TLS_DH_anon_WITH_RC4_128_MD5]; +anonymous_suites(2 = N) -> + psk_suites_anon(N) ++ + [?TLS_ECDH_anon_WITH_AES_128_CBC_SHA, + ?TLS_ECDH_anon_WITH_AES_256_CBC_SHA, + ?TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA, + ?TLS_DH_anon_WITH_DES_CBC_SHA, + ?TLS_DH_anon_WITH_RC4_128_MD5]; +anonymous_suites(N) when N == 0; + N == 1 -> + psk_suites_anon(N) ++ + [?TLS_DH_anon_WITH_RC4_128_MD5, + ?TLS_DH_anon_WITH_3DES_EDE_CBC_SHA, + ?TLS_DH_anon_WITH_DES_CBC_SHA + ]. %%-------------------------------------------------------------------- --spec psk_suites(ssl_record:ssl_version() | integer()) -> [cipher_suite()]. +-spec psk_suites(ssl_record:ssl_version() | integer()) -> [ssl_cipher_format:cipher_suite()]. %% %% Description: Returns a list of the PSK cipher suites, only supported %% if explicitly set by user. %%-------------------------------------------------------------------- psk_suites({3, N}) -> psk_suites(N); - psk_suites(N) when N >= 3 -> - psk_suites(0) ++ - [?TLS_DHE_PSK_WITH_AES_256_CBC_SHA384, - ?TLS_RSA_PSK_WITH_AES_256_CBC_SHA384, - ?TLS_PSK_WITH_AES_256_CBC_SHA384, - ?TLS_DHE_PSK_WITH_AES_128_CBC_SHA256, - ?TLS_RSA_PSK_WITH_AES_128_CBC_SHA256, - ?TLS_PSK_WITH_AES_128_CBC_SHA256]; - + [ + ?TLS_RSA_PSK_WITH_AES_256_GCM_SHA384, + ?TLS_RSA_PSK_WITH_AES_256_CBC_SHA384, + ?TLS_RSA_PSK_WITH_AES_128_GCM_SHA256, + ?TLS_RSA_PSK_WITH_AES_128_CBC_SHA256 + ] ++ psk_suites(0); psk_suites(_) -> + [?TLS_RSA_PSK_WITH_AES_256_CBC_SHA, + ?TLS_RSA_PSK_WITH_AES_128_CBC_SHA, + ?TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA, + ?TLS_RSA_PSK_WITH_RC4_128_SHA]. + +%%-------------------------------------------------------------------- +-spec psk_suites_anon(ssl_record:ssl_version() | integer()) -> [ssl_cipher_format:cipher_suite()]. +%% +%% Description: Returns a list of the anonymous PSK cipher suites, only supported +%% if explicitly set by user. +%%-------------------------------------------------------------------- +psk_suites_anon({3, N}) -> + psk_suites_anon(N); +psk_suites_anon(N) + when N >= 3 -> + [ + ?TLS_DHE_PSK_WITH_AES_256_GCM_SHA384, + ?TLS_PSK_WITH_AES_256_GCM_SHA384, + ?TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384, + ?TLS_DHE_PSK_WITH_AES_256_CBC_SHA384, + ?TLS_PSK_WITH_AES_256_CBC_SHA384, + ?TLS_ECDHE_PSK_WITH_AES_128_GCM_SHA256, + ?TLS_DHE_PSK_WITH_AES_128_GCM_SHA256, + ?TLS_PSK_WITH_AES_128_GCM_SHA256, + ?TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256, + ?TLS_DHE_PSK_WITH_AES_128_CBC_SHA256, + ?TLS_PSK_WITH_AES_128_CBC_SHA256 + ] ++ psk_suites_anon(0); +psk_suites_anon(_) -> [?TLS_DHE_PSK_WITH_AES_256_CBC_SHA, - ?TLS_RSA_PSK_WITH_AES_256_CBC_SHA, ?TLS_PSK_WITH_AES_256_CBC_SHA, + ?TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA, ?TLS_DHE_PSK_WITH_AES_128_CBC_SHA, - ?TLS_RSA_PSK_WITH_AES_128_CBC_SHA, ?TLS_PSK_WITH_AES_128_CBC_SHA, + ?TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA, ?TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA, - ?TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA, ?TLS_PSK_WITH_3DES_EDE_CBC_SHA, + ?TLS_ECDHE_PSK_WITH_RC4_128_SHA, ?TLS_DHE_PSK_WITH_RC4_128_SHA, - ?TLS_RSA_PSK_WITH_RC4_128_SHA, ?TLS_PSK_WITH_RC4_128_SHA]. - %%-------------------------------------------------------------------- --spec srp_suites() -> [cipher_suite()]. +-spec srp_suites() -> [ssl_cipher_format:cipher_suite()]. %% %% Description: Returns a list of the SRP cipher suites, only supported %% if explicitly set by user. %%-------------------------------------------------------------------- srp_suites() -> - [?TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA, - ?TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA, + [?TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA, ?TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA, - ?TLS_SRP_SHA_WITH_AES_128_CBC_SHA, ?TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA, ?TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA, - ?TLS_SRP_SHA_WITH_AES_256_CBC_SHA, ?TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA, ?TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA]. %%-------------------------------------------------------------------- --spec suite_definition(cipher_suite()) -> int_cipher_suite(). +-spec srp_suites_anon() -> [ssl_cipher_format:cipher_suite()]. %% -%% Description: Return erlang cipher suite definition. -%% Note: Currently not supported suites are commented away. -%% They should be supported or removed in the future. -%%------------------------------------------------------------------- -%% TLS v1.1 suites -suite_definition(?TLS_NULL_WITH_NULL_NULL) -> - {null, null, null, null}; -%% RFC 5746 - Not a real cipher suite used to signal empty "renegotiation_info" extension -%% to avoid handshake failure from old servers that do not ignore -%% hello extension data as they should. -suite_definition(?TLS_EMPTY_RENEGOTIATION_INFO_SCSV) -> - {null, null, null, null}; -%% suite_definition(?TLS_RSA_WITH_NULL_MD5) -> -%% {rsa, null, md5, default_prf}; -%% suite_definition(?TLS_RSA_WITH_NULL_SHA) -> -%% {rsa, null, sha, default_prf}; -suite_definition(?TLS_RSA_WITH_RC4_128_MD5) -> - {rsa, rc4_128, md5, default_prf}; -suite_definition(?TLS_RSA_WITH_RC4_128_SHA) -> - {rsa, rc4_128, sha, default_prf}; -suite_definition(?TLS_RSA_WITH_DES_CBC_SHA) -> - {rsa, des_cbc, sha, default_prf}; -suite_definition(?TLS_RSA_WITH_3DES_EDE_CBC_SHA) -> - {rsa, '3des_ede_cbc', sha, default_prf}; -suite_definition(?TLS_DHE_DSS_WITH_DES_CBC_SHA) -> - {dhe_dss, des_cbc, sha, default_prf}; -suite_definition(?TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA) -> - {dhe_dss, '3des_ede_cbc', sha, default_prf}; -suite_definition(?TLS_DHE_RSA_WITH_DES_CBC_SHA) -> - {dhe_rsa, des_cbc, sha, default_prf}; -suite_definition(?TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA) -> - {dhe_rsa, '3des_ede_cbc', sha, default_prf}; - -%%% TSL V1.1 AES suites -suite_definition(?TLS_RSA_WITH_AES_128_CBC_SHA) -> - {rsa, aes_128_cbc, sha, default_prf}; -suite_definition(?TLS_DHE_DSS_WITH_AES_128_CBC_SHA) -> - {dhe_dss, aes_128_cbc, sha, default_prf}; -suite_definition(?TLS_DHE_RSA_WITH_AES_128_CBC_SHA) -> - {dhe_rsa, aes_128_cbc, sha, default_prf}; -suite_definition(?TLS_RSA_WITH_AES_256_CBC_SHA) -> - {rsa, aes_256_cbc, sha, default_prf}; -suite_definition(?TLS_DHE_DSS_WITH_AES_256_CBC_SHA) -> - {dhe_dss, aes_256_cbc, sha, default_prf}; -suite_definition(?TLS_DHE_RSA_WITH_AES_256_CBC_SHA) -> - {dhe_rsa, aes_256_cbc, sha, default_prf}; - -%% TLS v1.2 suites - -%% suite_definition(?TLS_RSA_WITH_NULL_SHA) -> -%% {rsa, null, sha, default_prf}; -suite_definition(?TLS_RSA_WITH_AES_128_CBC_SHA256) -> - {rsa, aes_128_cbc, sha256, default_prf}; -suite_definition(?TLS_RSA_WITH_AES_256_CBC_SHA256) -> - {rsa, aes_256_cbc, sha256, default_prf}; -suite_definition(?TLS_DHE_DSS_WITH_AES_128_CBC_SHA256) -> - {dhe_dss, aes_128_cbc, sha256, default_prf}; -suite_definition(?TLS_DHE_RSA_WITH_AES_128_CBC_SHA256) -> - {dhe_rsa, aes_128_cbc, sha256, default_prf}; -suite_definition(?TLS_DHE_DSS_WITH_AES_256_CBC_SHA256) -> - {dhe_dss, aes_256_cbc, sha256, default_prf}; -suite_definition(?TLS_DHE_RSA_WITH_AES_256_CBC_SHA256) -> - {dhe_rsa, aes_256_cbc, sha256, default_prf}; - -%% not defined YET: -%% TLS_DH_DSS_WITH_AES_128_CBC_SHA256 DH_DSS AES_128_CBC SHA256 -%% TLS_DH_RSA_WITH_AES_128_CBC_SHA256 DH_RSA AES_128_CBC SHA256 -%% TLS_DH_DSS_WITH_AES_256_CBC_SHA256 DH_DSS AES_256_CBC SHA256 -%% TLS_DH_RSA_WITH_AES_256_CBC_SHA256 DH_RSA AES_256_CBC SHA256 - -%%% DH-ANON deprecated by TLS spec and not available -%%% by default, but good for testing purposes. -suite_definition(?TLS_DH_anon_WITH_RC4_128_MD5) -> - {dh_anon, rc4_128, md5, default_prf}; -suite_definition(?TLS_DH_anon_WITH_DES_CBC_SHA) -> - {dh_anon, des_cbc, sha, default_prf}; -suite_definition(?TLS_DH_anon_WITH_3DES_EDE_CBC_SHA) -> - {dh_anon, '3des_ede_cbc', sha, default_prf}; -suite_definition(?TLS_DH_anon_WITH_AES_128_CBC_SHA) -> - {dh_anon, aes_128_cbc, sha, default_prf}; -suite_definition(?TLS_DH_anon_WITH_AES_256_CBC_SHA) -> - {dh_anon, aes_256_cbc, sha, default_prf}; -suite_definition(?TLS_DH_anon_WITH_AES_128_CBC_SHA256) -> - {dh_anon, aes_128_cbc, sha256, default_prf}; -suite_definition(?TLS_DH_anon_WITH_AES_256_CBC_SHA256) -> - {dh_anon, aes_256_cbc, sha256, default_prf}; - -%%% PSK Cipher Suites RFC 4279 - -suite_definition(?TLS_PSK_WITH_RC4_128_SHA) -> - {psk, rc4_128, sha, default_prf}; -suite_definition(?TLS_PSK_WITH_3DES_EDE_CBC_SHA) -> - {psk, '3des_ede_cbc', sha, default_prf}; -suite_definition(?TLS_PSK_WITH_AES_128_CBC_SHA) -> - {psk, aes_128_cbc, sha, default_prf}; -suite_definition(?TLS_PSK_WITH_AES_256_CBC_SHA) -> - {psk, aes_256_cbc, sha, default_prf}; -suite_definition(?TLS_DHE_PSK_WITH_RC4_128_SHA) -> - {dhe_psk, rc4_128, sha, default_prf}; -suite_definition(?TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA) -> - {dhe_psk, '3des_ede_cbc', sha, default_prf}; -suite_definition(?TLS_DHE_PSK_WITH_AES_128_CBC_SHA) -> - {dhe_psk, aes_128_cbc, sha, default_prf}; -suite_definition(?TLS_DHE_PSK_WITH_AES_256_CBC_SHA) -> - {dhe_psk, aes_256_cbc, sha, default_prf}; -suite_definition(?TLS_RSA_PSK_WITH_RC4_128_SHA) -> - {rsa_psk, rc4_128, sha, default_prf}; -suite_definition(?TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA) -> - {rsa_psk, '3des_ede_cbc', sha, default_prf}; -suite_definition(?TLS_RSA_PSK_WITH_AES_128_CBC_SHA) -> - {rsa_psk, aes_128_cbc, sha, default_prf}; -suite_definition(?TLS_RSA_PSK_WITH_AES_256_CBC_SHA) -> - {rsa_psk, aes_256_cbc, sha, default_prf}; - -%%% TLS 1.2 PSK Cipher Suites RFC 5487 - -suite_definition(?TLS_PSK_WITH_AES_128_CBC_SHA256) -> - {psk, aes_128_cbc, sha256, default_prf}; -suite_definition(?TLS_PSK_WITH_AES_256_CBC_SHA384) -> - {psk, aes_256_cbc, sha384, default_prf}; -suite_definition(?TLS_DHE_PSK_WITH_AES_128_CBC_SHA256) -> - {dhe_psk, aes_128_cbc, sha256, default_prf}; -suite_definition(?TLS_DHE_PSK_WITH_AES_256_CBC_SHA384) -> - {dhe_psk, aes_256_cbc, sha384, default_prf}; -suite_definition(?TLS_RSA_PSK_WITH_AES_128_CBC_SHA256) -> - {rsa_psk, aes_128_cbc, sha256, default_prf}; -suite_definition(?TLS_RSA_PSK_WITH_AES_256_CBC_SHA384) -> - {rsa_psk, aes_256_cbc, sha384, default_prf}; - -suite_definition(?TLS_PSK_WITH_NULL_SHA256) -> - {psk, null, sha256, default_prf}; -suite_definition(?TLS_PSK_WITH_NULL_SHA384) -> - {psk, null, sha384, default_prf}; -suite_definition(?TLS_DHE_PSK_WITH_NULL_SHA256) -> - {dhe_psk, null, sha256, default_prf}; -suite_definition(?TLS_DHE_PSK_WITH_NULL_SHA384) -> - {dhe_psk, null, sha384, default_prf}; -suite_definition(?TLS_RSA_PSK_WITH_NULL_SHA256) -> - {rsa_psk, null, sha256, default_prf}; -suite_definition(?TLS_RSA_PSK_WITH_NULL_SHA384) -> - {rsa_psk, null, sha384, default_prf}; - -%%% SRP Cipher Suites RFC 5054 - -suite_definition(?TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA) -> - {srp_anon, '3des_ede_cbc', sha, default_prf}; -suite_definition(?TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA) -> - {srp_rsa, '3des_ede_cbc', sha, default_prf}; -suite_definition(?TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA) -> - {srp_dss, '3des_ede_cbc', sha, default_prf}; -suite_definition(?TLS_SRP_SHA_WITH_AES_128_CBC_SHA) -> - {srp_anon, aes_128_cbc, sha, default_prf}; -suite_definition(?TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA) -> - {srp_rsa, aes_128_cbc, sha, default_prf}; -suite_definition(?TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA) -> - {srp_dss, aes_128_cbc, sha, default_prf}; -suite_definition(?TLS_SRP_SHA_WITH_AES_256_CBC_SHA) -> - {srp_anon, aes_256_cbc, sha, default_prf}; -suite_definition(?TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA) -> - {srp_rsa, aes_256_cbc, sha, default_prf}; -suite_definition(?TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA) -> - {srp_dss, aes_256_cbc, sha, default_prf}; - -%% RFC 4492 EC TLS suites -suite_definition(?TLS_ECDH_ECDSA_WITH_NULL_SHA) -> - {ecdh_ecdsa, null, sha, default_prf}; -suite_definition(?TLS_ECDH_ECDSA_WITH_RC4_128_SHA) -> - {ecdh_ecdsa, rc4_128, sha, default_prf}; -suite_definition(?TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA) -> - {ecdh_ecdsa, '3des_ede_cbc', sha, default_prf}; -suite_definition(?TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA) -> - {ecdh_ecdsa, aes_128_cbc, sha, default_prf}; -suite_definition(?TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA) -> - {ecdh_ecdsa, aes_256_cbc, sha, default_prf}; - -suite_definition(?TLS_ECDHE_ECDSA_WITH_NULL_SHA) -> - {ecdhe_ecdsa, null, sha, default_prf}; -suite_definition(?TLS_ECDHE_ECDSA_WITH_RC4_128_SHA) -> - {ecdhe_ecdsa, rc4_128, sha, default_prf}; -suite_definition(?TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA) -> - {ecdhe_ecdsa, '3des_ede_cbc', sha, default_prf}; -suite_definition(?TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA) -> - {ecdhe_ecdsa, aes_128_cbc, sha, default_prf}; -suite_definition(?TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA) -> - {ecdhe_ecdsa, aes_256_cbc, sha, default_prf}; - -suite_definition(?TLS_ECDH_RSA_WITH_NULL_SHA) -> - {ecdh_rsa, null, sha, default_prf}; -suite_definition(?TLS_ECDH_RSA_WITH_RC4_128_SHA) -> - {ecdh_rsa, rc4_128, sha, default_prf}; -suite_definition(?TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA) -> - {ecdh_rsa, '3des_ede_cbc', sha, default_prf}; -suite_definition(?TLS_ECDH_RSA_WITH_AES_128_CBC_SHA) -> - {ecdh_rsa, aes_128_cbc, sha, default_prf}; -suite_definition(?TLS_ECDH_RSA_WITH_AES_256_CBC_SHA) -> - {ecdh_rsa, aes_256_cbc, sha, default_prf}; - -suite_definition(?TLS_ECDHE_RSA_WITH_NULL_SHA) -> - {ecdhe_rsa, null, sha, default_prf}; -suite_definition(?TLS_ECDHE_RSA_WITH_RC4_128_SHA) -> - {ecdhe_rsa, rc4_128, sha, default_prf}; -suite_definition(?TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA) -> - {ecdhe_rsa, '3des_ede_cbc', sha, default_prf}; -suite_definition(?TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA) -> - {ecdhe_rsa, aes_128_cbc, sha, default_prf}; -suite_definition(?TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA) -> - {ecdhe_rsa, aes_256_cbc, sha, default_prf}; - -suite_definition(?TLS_ECDH_anon_WITH_NULL_SHA) -> - {ecdh_anon, null, sha, default_prf}; -suite_definition(?TLS_ECDH_anon_WITH_RC4_128_SHA) -> - {ecdh_anon, rc4_128, sha, default_prf}; -suite_definition(?TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA) -> - {ecdh_anon, '3des_ede_cbc', sha, default_prf}; -suite_definition(?TLS_ECDH_anon_WITH_AES_128_CBC_SHA) -> - {ecdh_anon, aes_128_cbc, sha, default_prf}; -suite_definition(?TLS_ECDH_anon_WITH_AES_256_CBC_SHA) -> - {ecdh_anon, aes_256_cbc, sha, default_prf}; - -%% RFC 5289 EC TLS suites -suite_definition(?TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256) -> - {ecdhe_ecdsa, aes_128_cbc, sha256, sha256}; -suite_definition(?TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384) -> - {ecdhe_ecdsa, aes_256_cbc, sha384, sha384}; -suite_definition(?TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256) -> - {ecdh_ecdsa, aes_128_cbc, sha256, sha256}; -suite_definition(?TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384) -> - {ecdh_ecdsa, aes_256_cbc, sha384, sha384}; -suite_definition(?TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256) -> - {ecdhe_rsa, aes_128_cbc, sha256, sha256}; -suite_definition(?TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384) -> - {ecdhe_rsa, aes_256_cbc, sha384, sha384}; -suite_definition(?TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256) -> - {ecdh_rsa, aes_128_cbc, sha256, sha256}; -suite_definition(?TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384) -> - {ecdh_rsa, aes_256_cbc, sha384, sha384}. +%% Description: Returns a list of the SRP anonymous cipher suites, only supported +%% if explicitly set by user. +%%-------------------------------------------------------------------- +srp_suites_anon() -> + [?TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA, + ?TLS_SRP_SHA_WITH_AES_128_CBC_SHA, + ?TLS_SRP_SHA_WITH_AES_256_CBC_SHA]. %%-------------------------------------------------------------------- --spec suite(erl_cipher_suite()) -> cipher_suite(). +-spec rc4_suites(Version::ssl_record:ssl_version() | integer()) -> + [ssl_cipher_format:cipher_suite()]. %% -%% Description: Return TLS cipher suite definition. +%% Description: Returns a list of the RSA|(ECDH/RSA)| (ECDH/ECDSA) +%% with RC4 cipher suites, only supported if explicitly set by user. +%% Are not considered secure any more. Other RC4 suites already +%% belonged to the user configured only category. %%-------------------------------------------------------------------- - -%% TLS v1.1 suites -%%suite({rsa, null, md5}) -> -%% ?TLS_RSA_WITH_NULL_MD5; -%%suite({rsa, null, sha}) -> -%% ?TLS_RSA_WITH_NULL_SHA; -suite({rsa, rc4_128, md5}) -> - ?TLS_RSA_WITH_RC4_128_MD5; -suite({rsa, rc4_128, sha}) -> - ?TLS_RSA_WITH_RC4_128_SHA; -suite({rsa, des_cbc, sha}) -> - ?TLS_RSA_WITH_DES_CBC_SHA; -suite({rsa, '3des_ede_cbc', sha}) -> - ?TLS_RSA_WITH_3DES_EDE_CBC_SHA; -suite({dhe_dss, des_cbc, sha}) -> - ?TLS_DHE_DSS_WITH_DES_CBC_SHA; -suite({dhe_dss, '3des_ede_cbc', sha}) -> - ?TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA; -suite({dhe_rsa, des_cbc, sha}) -> - ?TLS_DHE_RSA_WITH_DES_CBC_SHA; -suite({dhe_rsa, '3des_ede_cbc', sha}) -> - ?TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA; -suite({dh_anon, rc4_128, md5}) -> - ?TLS_DH_anon_WITH_RC4_128_MD5; -suite({dh_anon, des_cbc, sha}) -> - ?TLS_DH_anon_WITH_DES_CBC_SHA; -suite({dh_anon, '3des_ede_cbc', sha}) -> - ?TLS_DH_anon_WITH_3DES_EDE_CBC_SHA; - -%%% TSL V1.1 AES suites -suite({rsa, aes_128_cbc, sha}) -> - ?TLS_RSA_WITH_AES_128_CBC_SHA; -suite({dhe_dss, aes_128_cbc, sha}) -> - ?TLS_DHE_DSS_WITH_AES_128_CBC_SHA; -suite({dhe_rsa, aes_128_cbc, sha}) -> - ?TLS_DHE_RSA_WITH_AES_128_CBC_SHA; -suite({dh_anon, aes_128_cbc, sha}) -> - ?TLS_DH_anon_WITH_AES_128_CBC_SHA; -suite({rsa, aes_256_cbc, sha}) -> - ?TLS_RSA_WITH_AES_256_CBC_SHA; -suite({dhe_dss, aes_256_cbc, sha}) -> - ?TLS_DHE_DSS_WITH_AES_256_CBC_SHA; -suite({dhe_rsa, aes_256_cbc, sha}) -> - ?TLS_DHE_RSA_WITH_AES_256_CBC_SHA; -suite({dh_anon, aes_256_cbc, sha}) -> - ?TLS_DH_anon_WITH_AES_256_CBC_SHA; - -%% TLS v1.2 suites - -%% suite_definition(?TLS_RSA_WITH_NULL_SHA) -> -%% {rsa, null, sha, sha256}; -suite({rsa, aes_128_cbc, sha256}) -> - ?TLS_RSA_WITH_AES_128_CBC_SHA256; -suite({rsa, aes_256_cbc, sha256}) -> - ?TLS_RSA_WITH_AES_256_CBC_SHA256; -suite({dhe_dss, aes_128_cbc, sha256}) -> - ?TLS_DHE_DSS_WITH_AES_128_CBC_SHA256; -suite({dhe_rsa, aes_128_cbc, sha256}) -> - ?TLS_DHE_RSA_WITH_AES_128_CBC_SHA256; -suite({dhe_dss, aes_256_cbc, sha256}) -> - ?TLS_DHE_DSS_WITH_AES_256_CBC_SHA256; -suite({dhe_rsa, aes_256_cbc, sha256}) -> - ?TLS_DHE_RSA_WITH_AES_256_CBC_SHA256; -suite({dh_anon, aes_128_cbc, sha256}) -> - ?TLS_DH_anon_WITH_AES_128_CBC_SHA256; -suite({dh_anon, aes_256_cbc, sha256}) -> - ?TLS_DH_anon_WITH_AES_256_CBC_SHA256; - -%%% PSK Cipher Suites RFC 4279 - -suite({psk, rc4_128,sha}) -> - ?TLS_PSK_WITH_RC4_128_SHA; -suite({psk, '3des_ede_cbc',sha}) -> - ?TLS_PSK_WITH_3DES_EDE_CBC_SHA; -suite({psk, aes_128_cbc,sha}) -> - ?TLS_PSK_WITH_AES_128_CBC_SHA; -suite({psk, aes_256_cbc,sha}) -> - ?TLS_PSK_WITH_AES_256_CBC_SHA; -suite({dhe_psk, rc4_128,sha}) -> - ?TLS_DHE_PSK_WITH_RC4_128_SHA; -suite({dhe_psk, '3des_ede_cbc',sha}) -> - ?TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA; -suite({dhe_psk, aes_128_cbc,sha}) -> - ?TLS_DHE_PSK_WITH_AES_128_CBC_SHA; -suite({dhe_psk, aes_256_cbc,sha}) -> - ?TLS_DHE_PSK_WITH_AES_256_CBC_SHA; -suite({rsa_psk, rc4_128,sha}) -> - ?TLS_RSA_PSK_WITH_RC4_128_SHA; -suite({rsa_psk, '3des_ede_cbc',sha}) -> - ?TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA; -suite({rsa_psk, aes_128_cbc,sha}) -> - ?TLS_RSA_PSK_WITH_AES_128_CBC_SHA; -suite({rsa_psk, aes_256_cbc,sha}) -> - ?TLS_RSA_PSK_WITH_AES_256_CBC_SHA; - -%%% TLS 1.2 PSK Cipher Suites RFC 5487 - -suite({psk, aes_128_cbc, sha256}) -> - ?TLS_PSK_WITH_AES_128_CBC_SHA256; -suite({psk, aes_256_cbc, sha384}) -> - ?TLS_PSK_WITH_AES_256_CBC_SHA384; -suite({dhe_psk, aes_128_cbc, sha256}) -> - ?TLS_DHE_PSK_WITH_AES_128_CBC_SHA256; -suite({dhe_psk, aes_256_cbc, sha384}) -> - ?TLS_DHE_PSK_WITH_AES_256_CBC_SHA384; -suite({rsa_psk, aes_128_cbc, sha256}) -> - ?TLS_RSA_PSK_WITH_AES_128_CBC_SHA256; -suite({rsa_psk, aes_256_cbc, sha384}) -> - ?TLS_RSA_PSK_WITH_AES_256_CBC_SHA384; - -suite({psk, null, sha256}) -> - ?TLS_PSK_WITH_NULL_SHA256; -suite({psk, null, sha384}) -> - ?TLS_PSK_WITH_NULL_SHA384; -suite({dhe_psk, null, sha256}) -> - ?TLS_DHE_PSK_WITH_NULL_SHA256; -suite({dhe_psk, null, sha384}) -> - ?TLS_DHE_PSK_WITH_NULL_SHA384; -suite({rsa_psk, null, sha256}) -> - ?TLS_RSA_PSK_WITH_NULL_SHA256; -suite({rsa_psk, null, sha384}) -> - ?TLS_RSA_PSK_WITH_NULL_SHA384; - -%%% SRP Cipher Suites RFC 5054 - -suite({srp_anon, '3des_ede_cbc', sha}) -> - ?TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA; -suite({srp_rsa, '3des_ede_cbc', sha}) -> - ?TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA; -suite({srp_dss, '3des_ede_cbc', sha}) -> - ?TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA; -suite({srp_anon, aes_128_cbc, sha}) -> - ?TLS_SRP_SHA_WITH_AES_128_CBC_SHA; -suite({srp_rsa, aes_128_cbc, sha}) -> - ?TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA; -suite({srp_dss, aes_128_cbc, sha}) -> - ?TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA; -suite({srp_anon, aes_256_cbc, sha}) -> - ?TLS_SRP_SHA_WITH_AES_256_CBC_SHA; -suite({srp_rsa, aes_256_cbc, sha}) -> - ?TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA; -suite({srp_dss, aes_256_cbc, sha}) -> - ?TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA; - -%%% RFC 4492 EC TLS suites -suite({ecdh_ecdsa, null, sha}) -> - ?TLS_ECDH_ECDSA_WITH_NULL_SHA; -suite({ecdh_ecdsa, rc4_128, sha}) -> - ?TLS_ECDH_ECDSA_WITH_RC4_128_SHA; -suite({ecdh_ecdsa, '3des_ede_cbc', sha}) -> - ?TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA; -suite({ecdh_ecdsa, aes_128_cbc, sha}) -> - ?TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA; -suite({ecdh_ecdsa, aes_256_cbc, sha}) -> - ?TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA; - -suite({ecdhe_ecdsa, null, sha}) -> - ?TLS_ECDHE_ECDSA_WITH_NULL_SHA; -suite({ecdhe_ecdsa, rc4_128, sha}) -> - ?TLS_ECDHE_ECDSA_WITH_RC4_128_SHA; -suite({ecdhe_ecdsa, '3des_ede_cbc', sha}) -> - ?TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA; -suite({ecdhe_ecdsa, aes_128_cbc, sha}) -> - ?TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA; -suite({ecdhe_ecdsa, aes_256_cbc, sha}) -> - ?TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA; - -suite({ecdh_rsa, null, sha}) -> - ?TLS_ECDH_RSA_WITH_NULL_SHA; -suite({ecdh_rsa, rc4_128, sha}) -> - ?TLS_ECDH_RSA_WITH_RC4_128_SHA; -suite({ecdh_rsa, '3des_ede_cbc', sha}) -> - ?TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA; -suite({ecdh_rsa, aes_128_cbc, sha}) -> - ?TLS_ECDH_RSA_WITH_AES_128_CBC_SHA; -suite({ecdh_rsa, aes_256_cbc, sha}) -> - ?TLS_ECDH_RSA_WITH_AES_256_CBC_SHA; - -suite({ecdhe_rsa, null, sha}) -> - ?TLS_ECDHE_RSA_WITH_NULL_SHA; -suite({ecdhe_rsa, rc4_128, sha}) -> - ?TLS_ECDHE_RSA_WITH_RC4_128_SHA; -suite({ecdhe_rsa, '3des_ede_cbc', sha}) -> - ?TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA; -suite({ecdhe_rsa, aes_128_cbc, sha}) -> - ?TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA; -suite({ecdhe_rsa, aes_256_cbc, sha}) -> - ?TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA; - -suite({ecdh_anon, null, sha}) -> - ?TLS_ECDH_anon_WITH_NULL_SHA; -suite({ecdh_anon, rc4_128, sha}) -> - ?TLS_ECDH_anon_WITH_RC4_128_SHA; -suite({ecdh_anon, '3des_ede_cbc', sha}) -> - ?TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA; -suite({ecdh_anon, aes_128_cbc, sha}) -> - ?TLS_ECDH_anon_WITH_AES_128_CBC_SHA; -suite({ecdh_anon, aes_256_cbc, sha}) -> - ?TLS_ECDH_anon_WITH_AES_256_CBC_SHA; - -%%% RFC 5289 EC TLS suites -suite({ecdhe_ecdsa, aes_128_cbc, sha256}) -> - ?TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256; -suite({ecdhe_ecdsa, aes_256_cbc, sha384}) -> - ?TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384; -suite({ecdh_ecdsa, aes_128_cbc, sha256}) -> - ?TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256; -suite({ecdh_ecdsa, aes_256_cbc, sha384}) -> - ?TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384; -suite({ecdhe_rsa, aes_128_cbc, sha256}) -> - ?TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256; -suite({ecdhe_rsa, aes_256_cbc, sha384}) -> - ?TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384; -suite({ecdh_rsa, aes_128_cbc, sha256}) -> - ?TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256; -suite({ecdh_rsa, aes_256_cbc, sha384}) -> - ?TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384. - +rc4_suites({3, 0}) -> + rc4_suites(0); +rc4_suites({3, Minor}) -> + rc4_suites(Minor) ++ rc4_suites(0); +rc4_suites(0) -> + [?TLS_RSA_WITH_RC4_128_SHA, + ?TLS_RSA_WITH_RC4_128_MD5]; +rc4_suites(N) when N =< 3 -> + [?TLS_ECDHE_ECDSA_WITH_RC4_128_SHA, + ?TLS_ECDHE_RSA_WITH_RC4_128_SHA, + ?TLS_ECDH_ECDSA_WITH_RC4_128_SHA, + ?TLS_ECDH_RSA_WITH_RC4_128_SHA]. %%-------------------------------------------------------------------- --spec openssl_suite(openssl_cipher_suite()) -> cipher_suite(). +-spec des_suites(Version::ssl_record:ssl_version()) -> [ssl_cipher_format:cipher_suite()]. %% -%% Description: Return TLS cipher suite definition. +%% Description: Returns a list of the cipher suites +%% with DES cipher, only supported if explicitly set by user. +%% Are not considered secure any more. %%-------------------------------------------------------------------- -%% translate constants <-> openssl-strings -openssl_suite("DHE-RSA-AES256-SHA256") -> - ?TLS_DHE_RSA_WITH_AES_256_CBC_SHA256; -openssl_suite("DHE-DSS-AES256-SHA256") -> - ?TLS_DHE_DSS_WITH_AES_256_CBC_SHA256; -openssl_suite("AES256-SHA256") -> - ?TLS_RSA_WITH_AES_256_CBC_SHA256; -openssl_suite("DHE-RSA-AES128-SHA256") -> - ?TLS_DHE_RSA_WITH_AES_128_CBC_SHA256; -openssl_suite("DHE-DSS-AES128-SHA256") -> - ?TLS_DHE_DSS_WITH_AES_128_CBC_SHA256; -openssl_suite("AES128-SHA256") -> - ?TLS_RSA_WITH_AES_128_CBC_SHA256; -openssl_suite("DHE-RSA-AES256-SHA") -> - ?TLS_DHE_RSA_WITH_AES_256_CBC_SHA; -openssl_suite("DHE-DSS-AES256-SHA") -> - ?TLS_DHE_DSS_WITH_AES_256_CBC_SHA; -openssl_suite("AES256-SHA") -> - ?TLS_RSA_WITH_AES_256_CBC_SHA; -openssl_suite("EDH-RSA-DES-CBC3-SHA") -> - ?TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA; -openssl_suite("EDH-DSS-DES-CBC3-SHA") -> - ?TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA; -openssl_suite("DES-CBC3-SHA") -> - ?TLS_RSA_WITH_3DES_EDE_CBC_SHA; -openssl_suite("DHE-RSA-AES128-SHA") -> - ?TLS_DHE_RSA_WITH_AES_128_CBC_SHA; -openssl_suite("DHE-DSS-AES128-SHA") -> - ?TLS_DHE_DSS_WITH_AES_128_CBC_SHA; -openssl_suite("AES128-SHA") -> - ?TLS_RSA_WITH_AES_128_CBC_SHA; -openssl_suite("RC4-SHA") -> - ?TLS_RSA_WITH_RC4_128_SHA; -openssl_suite("RC4-MD5") -> - ?TLS_RSA_WITH_RC4_128_MD5; -openssl_suite("EDH-RSA-DES-CBC-SHA") -> - ?TLS_DHE_RSA_WITH_DES_CBC_SHA; -openssl_suite("DES-CBC-SHA") -> - ?TLS_RSA_WITH_DES_CBC_SHA; - -%%% SRP Cipher Suites RFC 5054 - -openssl_suite("SRP-DSS-AES-256-CBC-SHA") -> - ?TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA; -openssl_suite("SRP-RSA-AES-256-CBC-SHA") -> - ?TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA; -openssl_suite("SRP-DSS-3DES-EDE-CBC-SHA") -> - ?TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA; -openssl_suite("SRP-RSA-3DES-EDE-CBC-SHA") -> - ?TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA; -openssl_suite("SRP-DSS-AES-128-CBC-SHA") -> - ?TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA; -openssl_suite("SRP-RSA-AES-128-CBC-SHA") -> - ?TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA; - -%% RFC 4492 EC TLS suites -openssl_suite("ECDH-ECDSA-RC4-SHA") -> - ?TLS_ECDH_ECDSA_WITH_RC4_128_SHA; -openssl_suite("ECDH-ECDSA-DES-CBC3-SHA") -> - ?TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA; -openssl_suite("ECDH-ECDSA-AES128-SHA") -> - ?TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA; -openssl_suite("ECDH-ECDSA-AES256-SHA") -> - ?TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA; - -openssl_suite("ECDHE-ECDSA-RC4-SHA") -> - ?TLS_ECDHE_ECDSA_WITH_RC4_128_SHA; -openssl_suite("ECDHE-ECDSA-DES-CBC3-SHA") -> - ?TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA; -openssl_suite("ECDHE-ECDSA-AES128-SHA") -> - ?TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA; -openssl_suite("ECDHE-ECDSA-AES256-SHA") -> - ?TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA; - -openssl_suite("ECDHE-RSA-RC4-SHA") -> - ?TLS_ECDHE_RSA_WITH_RC4_128_SHA; -openssl_suite("ECDHE-RSA-DES-CBC3-SHA") -> - ?TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA; -openssl_suite("ECDHE-RSA-AES128-SHA") -> - ?TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA; -openssl_suite("ECDHE-RSA-AES256-SHA") -> - ?TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA; - -openssl_suite("ECDH-RSA-RC4-SHA") -> - ?TLS_ECDH_RSA_WITH_RC4_128_SHA; -openssl_suite("ECDH-RSA-DES-CBC3-SHA") -> - ?TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA; -openssl_suite("ECDH-RSA-AES128-SHA") -> - ?TLS_ECDH_RSA_WITH_AES_128_CBC_SHA; -openssl_suite("ECDH-RSA-AES256-SHA") -> - ?TLS_ECDH_RSA_WITH_AES_256_CBC_SHA; - -%% RFC 5289 EC TLS suites -openssl_suite("ECDHE-ECDSA-AES128-SHA256") -> - ?TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256; -openssl_suite("ECDHE-ECDSA-AES256-SHA384") -> - ?TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384; -openssl_suite("ECDH-ECDSA-AES128-SHA256") -> - ?TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256; -openssl_suite("ECDH-ECDSA-AES256-SHA384") -> - ?TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384; -openssl_suite("ECDHE-RSA-AES128-SHA256") -> - ?TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256; -openssl_suite("ECDHE-RSA-AES256-SHA384") -> - ?TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384; -openssl_suite("ECDH-RSA-AES128-SHA256") -> - ?TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256; -openssl_suite("ECDH-RSA-AES256-SHA384") -> - ?TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384. +des_suites(_)-> + [?TLS_DHE_RSA_WITH_DES_CBC_SHA, + ?TLS_RSA_WITH_DES_CBC_SHA, + ?TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA, + ?TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, + ?TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA, + ?TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA, + ?TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA, + ?TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA + ]. %%-------------------------------------------------------------------- --spec openssl_suite_name(cipher_suite()) -> openssl_cipher_suite(). +-spec rsa_suites(Version::ssl_record:ssl_version() | integer()) -> [ssl_cipher_format:cipher_suite()]. %% -%% Description: Return openssl cipher suite name. -%%------------------------------------------------------------------- -openssl_suite_name(?TLS_DHE_RSA_WITH_AES_256_CBC_SHA) -> - "DHE-RSA-AES256-SHA"; -openssl_suite_name(?TLS_DHE_DSS_WITH_AES_256_CBC_SHA) -> - "DHE-DSS-AES256-SHA"; -openssl_suite_name(?TLS_RSA_WITH_AES_256_CBC_SHA) -> - "AES256-SHA"; -openssl_suite_name(?TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA) -> - "EDH-RSA-DES-CBC3-SHA"; -openssl_suite_name(?TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA) -> - "EDH-DSS-DES-CBC3-SHA"; -openssl_suite_name(?TLS_RSA_WITH_3DES_EDE_CBC_SHA) -> - "DES-CBC3-SHA"; -openssl_suite_name( ?TLS_DHE_RSA_WITH_AES_128_CBC_SHA) -> - "DHE-RSA-AES128-SHA"; -openssl_suite_name(?TLS_DHE_DSS_WITH_AES_128_CBC_SHA) -> - "DHE-DSS-AES128-SHA"; -openssl_suite_name(?TLS_RSA_WITH_AES_128_CBC_SHA) -> - "AES128-SHA"; -openssl_suite_name(?TLS_RSA_WITH_RC4_128_SHA) -> - "RC4-SHA"; -openssl_suite_name(?TLS_RSA_WITH_RC4_128_MD5) -> - "RC4-MD5"; -openssl_suite_name(?TLS_DHE_RSA_WITH_DES_CBC_SHA) -> - "EDH-RSA-DES-CBC-SHA"; -openssl_suite_name(?TLS_RSA_WITH_DES_CBC_SHA) -> - "DES-CBC-SHA"; -openssl_suite_name(?TLS_RSA_WITH_NULL_SHA256) -> - "NULL-SHA256"; -openssl_suite_name(?TLS_RSA_WITH_AES_128_CBC_SHA256) -> - "AES128-SHA256"; -openssl_suite_name(?TLS_RSA_WITH_AES_256_CBC_SHA256) -> - "AES256-SHA256"; -openssl_suite_name(?TLS_DH_DSS_WITH_AES_128_CBC_SHA256) -> - "DH-DSS-AES128-SHA256"; -openssl_suite_name(?TLS_DH_RSA_WITH_AES_128_CBC_SHA256) -> - "DH-RSA-AES128-SHA256"; -openssl_suite_name(?TLS_DHE_DSS_WITH_AES_128_CBC_SHA256) -> - "DHE-DSS-AES128-SHA256"; -openssl_suite_name(?TLS_DHE_RSA_WITH_AES_128_CBC_SHA256) -> - "DHE-RSA-AES128-SHA256"; -openssl_suite_name(?TLS_DH_DSS_WITH_AES_256_CBC_SHA256) -> - "DH-DSS-AES256-SHA256"; -openssl_suite_name(?TLS_DH_RSA_WITH_AES_256_CBC_SHA256) -> - "DH-RSA-AES256-SHA256"; -openssl_suite_name(?TLS_DHE_DSS_WITH_AES_256_CBC_SHA256) -> - "DHE-DSS-AES256-SHA256"; -openssl_suite_name(?TLS_DHE_RSA_WITH_AES_256_CBC_SHA256) -> - "DHE-RSA-AES256-SHA256"; - -%%% PSK Cipher Suites RFC 4279 - -openssl_suite_name(?TLS_PSK_WITH_AES_256_CBC_SHA) -> - "PSK-AES256-CBC-SHA"; -openssl_suite_name(?TLS_PSK_WITH_3DES_EDE_CBC_SHA) -> - "PSK-3DES-EDE-CBC-SHA"; -openssl_suite_name(?TLS_PSK_WITH_AES_128_CBC_SHA) -> - "PSK-AES128-CBC-SHA"; -openssl_suite_name(?TLS_PSK_WITH_RC4_128_SHA) -> - "PSK-RC4-SHA"; - -%%% SRP Cipher Suites RFC 5054 - -openssl_suite_name(?TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA) -> - "SRP-RSA-3DES-EDE-CBC-SHA"; -openssl_suite_name(?TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA) -> - "SRP-DSS-3DES-EDE-CBC-SHA"; -openssl_suite_name(?TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA) -> - "SRP-RSA-AES-128-CBC-SHA"; -openssl_suite_name(?TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA) -> - "SRP-DSS-AES-128-CBC-SHA"; -openssl_suite_name(?TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA) -> - "SRP-RSA-AES-256-CBC-SHA"; -openssl_suite_name(?TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA) -> - "SRP-DSS-AES-256-CBC-SHA"; - -%% RFC 4492 EC TLS suites -openssl_suite_name(?TLS_ECDH_ECDSA_WITH_RC4_128_SHA) -> - "ECDH-ECDSA-RC4-SHA"; -openssl_suite_name(?TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA) -> - "ECDH-ECDSA-DES-CBC3-SHA"; -openssl_suite_name(?TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA) -> - "ECDH-ECDSA-AES128-SHA"; -openssl_suite_name(?TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA) -> - "ECDH-ECDSA-AES256-SHA"; - -openssl_suite_name(?TLS_ECDHE_ECDSA_WITH_RC4_128_SHA) -> - "ECDHE-ECDSA-RC4-SHA"; -openssl_suite_name(?TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA) -> - "ECDHE-ECDSA-DES-CBC3-SHA"; -openssl_suite_name(?TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA) -> - "ECDHE-ECDSA-AES128-SHA"; -openssl_suite_name(?TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA) -> - "ECDHE-ECDSA-AES256-SHA"; - -openssl_suite_name(?TLS_ECDH_RSA_WITH_RC4_128_SHA) -> - "ECDH-RSA-RC4-SHA"; -openssl_suite_name(?TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA) -> - "ECDH-RSA-DES-CBC3-SHA"; -openssl_suite_name(?TLS_ECDH_RSA_WITH_AES_128_CBC_SHA) -> - "ECDH-RSA-AES128-SHA"; -openssl_suite_name(?TLS_ECDH_RSA_WITH_AES_256_CBC_SHA) -> - "ECDH-RSA-AES256-SHA"; - -openssl_suite_name(?TLS_ECDHE_RSA_WITH_RC4_128_SHA) -> - "ECDHE-RSA-RC4-SHA"; -openssl_suite_name(?TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA) -> - "ECDHE-RSA-DES-CBC3-SHA"; -openssl_suite_name(?TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA) -> - "ECDHE-RSA-AES128-SHA"; -openssl_suite_name(?TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA) -> - "ECDHE-RSA-AES256-SHA"; - -%% RFC 5289 EC TLS suites -openssl_suite_name(?TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256) -> - "ECDHE-ECDSA-AES128-SHA256"; -openssl_suite_name(?TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384) -> - "ECDHE-ECDSA-AES256-SHA384"; -openssl_suite_name(?TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256) -> - "ECDH-ECDSA-AES128-SHA256"; -openssl_suite_name(?TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384) -> - "ECDH-ECDSA-AES256-SHA384"; -openssl_suite_name(?TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256) -> - "ECDHE-RSA-AES128-SHA256"; -openssl_suite_name(?TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384) -> - "ECDHE-RSA-AES256-SHA384"; -openssl_suite_name(?TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256) -> - "ECDH-RSA-AES128-SHA256"; -openssl_suite_name(?TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384) -> - "ECDH-RSA-AES256-SHA384"; - -%% No oppenssl name -openssl_suite_name(Cipher) -> - suite_definition(Cipher). +%% Description: Returns a list of the RSA key exchange +%% cipher suites, only supported if explicitly set by user. +%% Are not considered secure any more. +%%-------------------------------------------------------------------- +rsa_suites({3, 0}) -> + rsa_suites(0); +rsa_suites({3, Minor}) -> + rsa_suites(Minor) ++ rsa_suites(0); +rsa_suites(0) -> + [?TLS_RSA_WITH_AES_256_CBC_SHA, + ?TLS_RSA_WITH_AES_128_CBC_SHA, + ?TLS_RSA_WITH_3DES_EDE_CBC_SHA + ]; +rsa_suites(N) when N =< 3 -> + [ + ?TLS_RSA_WITH_AES_256_GCM_SHA384, + ?TLS_RSA_WITH_AES_256_CBC_SHA256, + ?TLS_RSA_WITH_AES_128_GCM_SHA256, + ?TLS_RSA_WITH_AES_128_CBC_SHA256 + ]. %%-------------------------------------------------------------------- --spec filter(undefined | binary(), [cipher_suite()]) -> [cipher_suite()]. +-spec filter(undefined | binary(), [ssl_cipher_format:cipher_suite()], + ssl_record:ssl_version()) -> [ssl_cipher_format:cipher_suite()]. %% %% Description: Select the cipher suites that can be used together with the %% supplied certificate. (Server side functionality) %%------------------------------------------------------------------- -filter(undefined, Ciphers) -> +filter(undefined, Ciphers, _) -> Ciphers; -filter(DerCert, Ciphers) -> +filter(DerCert, Ciphers0, Version) -> OtpCert = public_key:pkix_decode_cert(DerCert, otp), SigAlg = OtpCert#'OTPCertificate'.signatureAlgorithm, PubKeyInfo = OtpCert#'OTPCertificate'.tbsCertificate#'OTPTBSCertificate'.subjectPublicKeyInfo, PubKeyAlg = PubKeyInfo#'OTPSubjectPublicKeyInfo'.algorithm, - Ciphers1 = - case ssl_certificate:public_key_type(PubKeyAlg#'PublicKeyAlgorithm'.algorithm) of - rsa -> - filter_keyuse(OtpCert, ((Ciphers -- dsa_signed_suites()) -- ec_keyed_suites()) -- ecdh_suites(), - rsa_suites(), dhe_rsa_suites() ++ ecdhe_rsa_suites()); - dsa -> - (Ciphers -- rsa_keyed_suites()) -- ec_keyed_suites(); - ec -> - filter_keyuse(OtpCert, (Ciphers -- rsa_keyed_suites()) -- dsa_signed_suites(), - [], ecdhe_ecdsa_suites()) - end, - - case public_key:pkix_sign_types(SigAlg#'SignatureAlgorithm'.algorithm) of - {_, rsa} -> - Ciphers1 -- ecdsa_signed_suites(); - {_, dsa} -> - Ciphers1; - {_, ecdsa} -> - Ciphers1 -- rsa_signed_suites() - end. - + Ciphers = filter_suites_pubkey( + ssl_certificate:public_key_type(PubKeyAlg#'PublicKeyAlgorithm'.algorithm), + Ciphers0, Version, OtpCert), + {_, Sign} = public_key:pkix_sign_types(SigAlg#'SignatureAlgorithm'.algorithm), + filter_suites_signature(Sign, Ciphers, Version). + +%%-------------------------------------------------------------------- +-spec filter_suites([ssl_cipher_format:erl_cipher_suite()] | [ssl_cipher_format:cipher_suite()], map()) -> + [ssl_cipher_format:erl_cipher_suite()] | [ssl_cipher_format:cipher_suite()]. +%% +%% Description: Filter suites using supplied filter funs +%%------------------------------------------------------------------- +filter_suites(Suites, Filters) -> + ApplyFilters = fun(Suite) -> + filter_suite(Suite, Filters) + end, + lists:filter(ApplyFilters, Suites). + +filter_suite(#{key_exchange := KeyExchange, + cipher := Cipher, + mac := Hash, + prf := Prf}, + #{key_exchange_filters := KeyFilters, + cipher_filters := CipherFilters, + mac_filters := HashFilters, + prf_filters := PrfFilters}) -> + all_filters(KeyExchange, KeyFilters) andalso + all_filters(Cipher, CipherFilters) andalso + all_filters(Hash, HashFilters) andalso + all_filters(Prf, PrfFilters); +filter_suite(Suite, Filters) -> + filter_suite(ssl_cipher_format:suite_definition(Suite), Filters). + %%-------------------------------------------------------------------- --spec filter_suites([cipher_suite()]) -> [cipher_suite()]. +-spec filter_suites([ssl_cipher_format:erl_cipher_suite()] | [ssl_cipher_format:cipher_suite()]) -> + [ssl_cipher_format:erl_cipher_suite()] | [ssl_cipher_format:cipher_suite()]. %% %% Description: Filter suites for algorithms supported by crypto. %%------------------------------------------------------------------- -filter_suites(Suites = [{_,_,_}|_]) -> - Algos = crypto:supports(), - lists:filter(fun({KeyExchange, Cipher, Hash}) -> - is_acceptable_keyexchange(KeyExchange, proplists:get_value(public_keys, Algos)) andalso - is_acceptable_cipher(Cipher, proplists:get_value(ciphers, Algos)) andalso - is_acceptable_hash(Hash, proplists:get_value(hashs, Algos)) - end, Suites); - -filter_suites(Suites = [{_,_,_,_}|_]) -> - Algos = crypto:supports(), - Hashs = proplists:get_value(hashs, Algos), - lists:filter(fun({KeyExchange, Cipher, Hash, Prf}) -> - is_acceptable_keyexchange(KeyExchange, proplists:get_value(public_keys, Algos)) andalso - is_acceptable_cipher(Cipher, proplists:get_value(ciphers, Algos)) andalso - is_acceptable_hash(Hash, Hashs) andalso - is_acceptable_prf(Prf, Hashs) - end, Suites); - filter_suites(Suites) -> + Filters = crypto_support_filters(), + filter_suites(Suites, Filters). + +all_filters(_, []) -> + true; +all_filters(Value, [Filter| Rest]) -> + case Filter(Value) of + true -> + all_filters(Value, Rest); + false -> + false + end. +crypto_support_filters() -> Algos = crypto:supports(), Hashs = proplists:get_value(hashs, Algos), - lists:filter(fun(Suite) -> - {KeyExchange, Cipher, Hash, Prf} = ssl_cipher:suite_definition(Suite), - is_acceptable_keyexchange(KeyExchange, proplists:get_value(public_keys, Algos)) andalso - is_acceptable_cipher(Cipher, proplists:get_value(ciphers, Algos)) andalso - is_acceptable_hash(Hash, Hashs) andalso - is_acceptable_prf(Prf, Hashs) - end, Suites). - -is_acceptable_keyexchange(KeyExchange, Algos) - when KeyExchange == ecdh_ecdsa; - KeyExchange == ecdhe_ecdsa; - KeyExchange == ecdh_rsa; - KeyExchange == ecdhe_rsa; - KeyExchange == ecdh_anon -> + #{key_exchange_filters => + [fun(KeyExchange) -> + is_acceptable_keyexchange(KeyExchange, + proplists:get_value(public_keys, Algos)) + end], + cipher_filters => + [fun(Cipher) -> + is_acceptable_cipher(Cipher, + proplists:get_value(ciphers, Algos)) + end], + mac_filters => + [fun(Hash) -> + is_acceptable_hash(Hash, Hashs) + end], + prf_filters => + [fun(Prf) -> + is_acceptable_prf(Prf, + proplists:get_value(hashs, Algos)) + end]}. + +is_acceptable_keyexchange(KeyExchange, _Algos) when KeyExchange == psk; + KeyExchange == null -> + true; +is_acceptable_keyexchange(KeyExchange, Algos) when KeyExchange == dh_anon; + KeyExchange == dhe_psk -> + proplists:get_bool(dh, Algos); +is_acceptable_keyexchange(dhe_dss, Algos) -> + proplists:get_bool(dh, Algos) andalso + proplists:get_bool(dss, Algos); +is_acceptable_keyexchange(dhe_rsa, Algos) -> + proplists:get_bool(dh, Algos) andalso + proplists:get_bool(rsa, Algos); +is_acceptable_keyexchange(KeyExchange, Algos) when KeyExchange == ecdh_anon; + KeyExchange == ecdhe_psk -> proplists:get_bool(ecdh, Algos); -is_acceptable_keyexchange(_, _) -> - true. - -is_acceptable_cipher(_, _) -> - true. +is_acceptable_keyexchange(KeyExchange, Algos) when KeyExchange == ecdh_ecdsa; + KeyExchange == ecdhe_ecdsa -> + proplists:get_bool(ecdh, Algos) andalso + proplists:get_bool(ecdsa, Algos); +is_acceptable_keyexchange(KeyExchange, Algos) when KeyExchange == ecdh_rsa; + KeyExchange == ecdhe_rsa -> + proplists:get_bool(ecdh, Algos) andalso + proplists:get_bool(rsa, Algos); +is_acceptable_keyexchange(KeyExchange, Algos) when KeyExchange == rsa; + KeyExchange == rsa_psk -> + proplists:get_bool(rsa, Algos); +is_acceptable_keyexchange(srp_anon, Algos) -> + proplists:get_bool(srp, Algos); +is_acceptable_keyexchange(srp_dss, Algos) -> + proplists:get_bool(srp, Algos) andalso + proplists:get_bool(dss, Algos); +is_acceptable_keyexchange(srp_rsa, Algos) -> + proplists:get_bool(srp, Algos) andalso + proplists:get_bool(rsa, Algos); +is_acceptable_keyexchange(_KeyExchange, _Algos) -> + false. + +is_acceptable_cipher(null, _Algos) -> + true; +is_acceptable_cipher(rc4_128, Algos) -> + proplists:get_bool(rc4, Algos); +is_acceptable_cipher(des_cbc, Algos) -> + proplists:get_bool(des_cbc, Algos); +is_acceptable_cipher('3des_ede_cbc', Algos) -> + proplists:get_bool(des3_cbc, Algos); +is_acceptable_cipher(aes_128_cbc, Algos) -> + proplists:get_bool(aes_cbc128, Algos); +is_acceptable_cipher(aes_256_cbc, Algos) -> + proplists:get_bool(aes_cbc256, Algos); +is_acceptable_cipher(Cipher, Algos) + when Cipher == aes_128_gcm; + Cipher == aes_256_gcm -> + proplists:get_bool(aes_gcm, Algos); +is_acceptable_cipher(Cipher, Algos) -> + proplists:get_bool(Cipher, Algos). is_acceptable_hash(null, _Algos) -> true; +is_acceptable_hash(aead, _Algos) -> + true; is_acceptable_hash(Hash, Algos) -> proplists:get_bool(Hash, Algos). @@ -1111,9 +620,42 @@ 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). + +calc_mac_hash(Type, Version, + PlainFragment, #{sequence_number := SeqNo, + mac_secret := MacSecret, + security_parameters:= + SecPars}) -> + Length = erlang:iolist_size(PlainFragment), + mac_hash(Version, SecPars#security_parameters.mac_algorithm, + MacSecret, SeqNo, Type, + Length, PlainFragment). + +is_stream_ciphersuite(#{cipher := rc4_128}) -> + true; +is_stream_ciphersuite(_) -> + false. %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- +mac_hash({_,_}, ?NULL, _MacSecret, _SeqNo, _Type, + _Length, _Fragment) -> + <<>>; +mac_hash({3, 0}, MacAlg, MacSecret, SeqNo, Type, Length, Fragment) -> + ssl_v3:mac_hash(MacAlg, MacSecret, SeqNo, Type, Length, Fragment); +mac_hash({3, N} = Version, MacAlg, MacSecret, SeqNo, Type, Length, Fragment) + when N =:= 1; N =:= 2; N =:= 3 -> + tls_v1:mac_hash(MacAlg, MacSecret, SeqNo, Type, Version, + Length, Fragment). bulk_cipher_algorithm(null) -> ?NULL; @@ -1125,7 +667,12 @@ bulk_cipher_algorithm('3des_ede_cbc') -> ?'3DES'; bulk_cipher_algorithm(Cipher) when Cipher == aes_128_cbc; Cipher == aes_256_cbc -> - ?AES. + ?AES_CBC; +bulk_cipher_algorithm(Cipher) when Cipher == aes_128_gcm; + Cipher == aes_256_gcm -> + ?AES_GCM; +bulk_cipher_algorithm(chacha20_poly1305) -> + ?CHACHA20_POLY1305. type(Cipher) when Cipher == null; Cipher == rc4_128 -> @@ -1135,7 +682,11 @@ type(Cipher) when Cipher == des_cbc; Cipher == '3des_ede_cbc'; Cipher == aes_128_cbc; Cipher == aes_256_cbc -> - ?BLOCK. + ?BLOCK; +type(Cipher) when Cipher == aes_128_gcm; + Cipher == aes_256_gcm; + Cipher == chacha20_poly1305 -> + ?AEAD. key_material(null) -> 0; @@ -1148,6 +699,12 @@ key_material('3des_ede_cbc') -> key_material(aes_128_cbc) -> 16; key_material(aes_256_cbc) -> + 32; +key_material(aes_128_gcm) -> + 16; +key_material(aes_256_gcm) -> + 32; +key_material(chacha20_poly1305) -> 32. expanded_key_material(null) -> @@ -1159,7 +716,10 @@ expanded_key_material(Cipher) when Cipher == des_cbc -> expanded_key_material('3des_ede_cbc') -> 24; expanded_key_material(Cipher) when Cipher == aes_128_cbc; - Cipher == aes_256_cbc -> + Cipher == aes_256_cbc; + Cipher == aes_128_gcm; + Cipher == aes_256_gcm; + Cipher == chacha20_poly1305 -> unknown. @@ -1168,16 +728,25 @@ effective_key_bits(null) -> effective_key_bits(des_cbc) -> 56; effective_key_bits(Cipher) when Cipher == rc4_128; - Cipher == aes_128_cbc -> + Cipher == aes_128_cbc; + Cipher == aes_128_gcm -> 128; effective_key_bits('3des_ede_cbc') -> 168; -effective_key_bits(aes_256_cbc) -> +effective_key_bits(Cipher) when Cipher == aes_256_cbc; + Cipher == aes_256_gcm; + Cipher == chacha20_poly1305 -> 256. iv_size(Cipher) when Cipher == null; - Cipher == rc4_128 -> + Cipher == rc4_128; + Cipher == chacha20_poly1305-> 0; + +iv_size(Cipher) when Cipher == aes_128_gcm; + Cipher == aes_256_gcm -> + 4; + iv_size(Cipher) -> block_size(Cipher). @@ -1186,7 +755,10 @@ block_size(Cipher) when Cipher == des_cbc; 8; block_size(Cipher) when Cipher == aes_128_cbc; - Cipher == aes_256_cbc -> + Cipher == aes_256_cbc; + Cipher == aes_128_gcm; + Cipher == aes_256_gcm; + Cipher == chacha20_poly1305 -> 16. prf_algorithm(default_prf, {3, N}) when N >= 3 -> @@ -1196,6 +768,11 @@ prf_algorithm(default_prf, {3, _}) -> prf_algorithm(Algo, _) -> hash_algorithm(Algo). +mac_algorithm(aead) -> + aead; +mac_algorithm(Algo) -> + hash_algorithm(Algo). + hash_algorithm(null) -> ?NULL; hash_algorithm(md5) -> ?MD5; hash_algorithm(sha) -> ?SHA; %% Only sha always refers to "SHA-1" @@ -1210,6 +787,7 @@ hash_algorithm(?SHA224) -> sha224; hash_algorithm(?SHA256) -> sha256; hash_algorithm(?SHA384) -> sha384; hash_algorithm(?SHA512) -> sha512; +hash_algorithm(Other) when is_integer(Other) andalso ((Other >= 7) and (Other =< 223)) -> unassigned; hash_algorithm(Other) when is_integer(Other) andalso ((Other >= 224) and (Other =< 255)) -> Other. sign_algorithm(anon) -> ?ANON; @@ -1220,10 +798,15 @@ sign_algorithm(?ANON) -> anon; sign_algorithm(?RSA) -> rsa; sign_algorithm(?DSA) -> dsa; sign_algorithm(?ECDSA) -> ecdsa; +sign_algorithm(Other) when is_integer(Other) andalso ((Other >= 4) and (Other =< 223)) -> unassigned; sign_algorithm(Other) when is_integer(Other) andalso ((Other >= 224) and (Other =< 255)) -> Other. hash_size(null) -> 0; +%% The AEAD MAC hash size is not used in the context +%% of calculating the master secret. See RFC 5246 Section 6.2.3.3. +hash_size(aead) -> + 0; hash_size(md5) -> 16; hash_size(sha) -> @@ -1306,7 +889,7 @@ is_correct_padding(GenBlockCipher, {3, 1}, false) -> is_correct_padding(#generic_block_cipher{padding_length = Len, padding = Padding}, _, _) -> Len == byte_size(Padding) andalso - list_to_binary(lists:duplicate(Len, Len)) == Padding. + binary:copy(?byte(Len), Len) == Padding. get_padding(Length, BlockSize) -> get_padding_aux(BlockSize, Length rem BlockSize). @@ -1315,11 +898,11 @@ get_padding_aux(_, 0) -> {0, <<>>}; get_padding_aux(BlockSize, PadLength) -> N = BlockSize - PadLength, - {N, list_to_binary(lists:duplicate(N, N))}. + {N, binary:copy(?byte(N), N)}. random_iv(IV) -> IVSz = byte_size(IV), - ssl:random_bytes(IVSz). + random_bytes(IVSz). next_iv(Bin, IV) -> BinSz = byte_size(Bin), @@ -1328,123 +911,214 @@ next_iv(Bin, IV) -> <<_:FirstPart/binary, NextIV:IVSz/binary>> = Bin, NextIV. -rsa_signed_suites() -> - dhe_rsa_suites() ++ rsa_suites() ++ - psk_rsa_suites() ++ srp_rsa_suites() ++ - ecdh_rsa_suites() ++ ecdhe_rsa_suites(). - -rsa_keyed_suites() -> - dhe_rsa_suites() ++ rsa_suites() ++ - psk_rsa_suites() ++ srp_rsa_suites() ++ - ecdhe_rsa_suites(). - -dhe_rsa_suites() -> - [?TLS_DHE_RSA_WITH_AES_256_CBC_SHA256, - ?TLS_DHE_RSA_WITH_AES_256_CBC_SHA, - ?TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA, - ?TLS_DHE_RSA_WITH_AES_128_CBC_SHA256, - ?TLS_DHE_RSA_WITH_AES_128_CBC_SHA, - ?TLS_DHE_RSA_WITH_DES_CBC_SHA]. - -psk_rsa_suites() -> - [?TLS_RSA_PSK_WITH_AES_256_CBC_SHA384, - ?TLS_RSA_PSK_WITH_AES_128_CBC_SHA256, - ?TLS_RSA_PSK_WITH_AES_256_CBC_SHA, - ?TLS_RSA_PSK_WITH_AES_128_CBC_SHA, - ?TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA, - ?TLS_RSA_PSK_WITH_RC4_128_SHA]. - -srp_rsa_suites() -> - [?TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA, - ?TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA, - ?TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA]. - -rsa_suites() -> - [?TLS_RSA_WITH_AES_256_CBC_SHA256, - ?TLS_RSA_WITH_AES_256_CBC_SHA, - ?TLS_RSA_WITH_3DES_EDE_CBC_SHA, - ?TLS_RSA_WITH_AES_128_CBC_SHA256, - ?TLS_RSA_WITH_AES_128_CBC_SHA, - ?TLS_RSA_WITH_RC4_128_SHA, - ?TLS_RSA_WITH_RC4_128_MD5, - ?TLS_RSA_WITH_DES_CBC_SHA]. - -ecdh_rsa_suites() -> - [?TLS_ECDH_RSA_WITH_NULL_SHA, - ?TLS_ECDH_RSA_WITH_RC4_128_SHA, - ?TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA, - ?TLS_ECDH_RSA_WITH_AES_128_CBC_SHA, - ?TLS_ECDH_RSA_WITH_AES_256_CBC_SHA, - ?TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256, - ?TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384]. - -ecdhe_rsa_suites() -> - [?TLS_ECDHE_RSA_WITH_NULL_SHA, - ?TLS_ECDHE_RSA_WITH_RC4_128_SHA, - ?TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, - ?TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, - ?TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, - ?TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, - ?TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384]. - -dsa_signed_suites() -> - dhe_dss_suites() ++ srp_dss_suites(). - -dhe_dss_suites() -> - [?TLS_DHE_DSS_WITH_AES_256_CBC_SHA256, - ?TLS_DHE_DSS_WITH_AES_256_CBC_SHA, - ?TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA, - ?TLS_DHE_DSS_WITH_AES_128_CBC_SHA256, - ?TLS_DHE_DSS_WITH_AES_128_CBC_SHA, - ?TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA]. - -srp_dss_suites() -> - [?TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA, - ?TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA, - ?TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA]. - -ec_keyed_suites() -> - ecdh_ecdsa_suites() ++ ecdhe_ecdsa_suites() - ++ ecdh_rsa_suites(). - -ecdsa_signed_suites() -> - ecdh_ecdsa_suites() ++ ecdhe_ecdsa_suites(). - -ecdh_suites() -> - ecdh_rsa_suites() ++ ecdh_ecdsa_suites(). +filter_suites_pubkey(rsa, CiphersSuites0, _Version, OtpCert) -> + KeyUses = key_uses(OtpCert), + NotECDSAKeyed = (CiphersSuites0 -- ec_keyed_suites(CiphersSuites0)) + -- dss_keyed_suites(CiphersSuites0), + CiphersSuites = filter_keyuse_suites(keyEncipherment, KeyUses, + NotECDSAKeyed, + rsa_suites_encipher(CiphersSuites0)), + filter_keyuse_suites(digitalSignature, KeyUses, CiphersSuites, + rsa_ecdhe_dhe_suites(CiphersSuites)); +filter_suites_pubkey(dsa, Ciphers, _, OtpCert) -> + KeyUses = key_uses(OtpCert), + NotECRSAKeyed = (Ciphers -- rsa_keyed_suites(Ciphers)) -- ec_keyed_suites(Ciphers), + filter_keyuse_suites(digitalSignature, KeyUses, NotECRSAKeyed, + dss_dhe_suites(Ciphers)); +filter_suites_pubkey(ec, Ciphers, _, OtpCert) -> + Uses = key_uses(OtpCert), + NotRSADSAKeyed = (Ciphers -- rsa_keyed_suites(Ciphers)) -- dss_keyed_suites(Ciphers), + CiphersSuites = filter_keyuse_suites(digitalSignature, Uses, NotRSADSAKeyed, + ec_ecdhe_suites(Ciphers)), + filter_keyuse_suites(keyAgreement, Uses, CiphersSuites, ec_ecdh_suites(Ciphers)). + +filter_suites_signature(_, Ciphers, {3, N}) when N >= 3 -> + Ciphers; +filter_suites_signature(rsa, Ciphers, Version) -> + (Ciphers -- ecdsa_signed_suites(Ciphers, Version)) -- dsa_signed_suites(Ciphers, Version); +filter_suites_signature(dsa, Ciphers, Version) -> + (Ciphers -- ecdsa_signed_suites(Ciphers, Version)) -- rsa_signed_suites(Ciphers, Version); +filter_suites_signature(ecdsa, Ciphers, Version) -> + (Ciphers -- rsa_signed_suites(Ciphers, Version)) -- dsa_signed_suites(Ciphers, Version). + + +%% From RFC 5246 - Section 7.4.2. Server Certificate +%% If the client provided a "signature_algorithms" extension, then all +%% certificates provided by the server MUST be signed by a +%% hash/signature algorithm pair that appears in that extension. Note +%% that this implies that a certificate containing a key for one +%% signature algorithm MAY be signed using a different signature +%% algorithm (for instance, an RSA key signed with a DSA key). This is +%% a departure from TLS 1.1, which required that the algorithms be the +%% same. +%% Note that this also implies that the DH_DSS, DH_RSA, +%% ECDH_ECDSA, and ECDH_RSA key exchange algorithms do not restrict the +%% algorithm used to sign the certificate. Fixed DH certificates MAY be +%% signed with any hash/signature algorithm pair appearing in the +%% extension. The names DH_DSS, DH_RSA, ECDH_ECDSA, and ECDH_RSA are +%% historical. +%% Note: DH_DSS and DH_RSA is not supported +rsa_signed({3,N}) when N >= 3 -> + fun(rsa) -> true; + (dhe_rsa) -> true; + (ecdhe_rsa) -> true; + (rsa_psk) -> true; + (srp_rsa) -> true; + (_) -> false + end; +rsa_signed(_) -> + fun(rsa) -> true; + (dhe_rsa) -> true; + (ecdhe_rsa) -> true; + (ecdh_rsa) -> true; + (rsa_psk) -> true; + (srp_rsa) -> true; + (_) -> false + end. +%% Cert should be signed by RSA +rsa_signed_suites(Ciphers, Version) -> + filter_suites(Ciphers, #{key_exchange_filters => [rsa_signed(Version)], + cipher_filters => [], + mac_filters => [], + prf_filters => []}). +ecdsa_signed({3,N}) when N >= 3 -> + fun(ecdhe_ecdsa) -> true; + (_) -> false + end; +ecdsa_signed(_) -> + fun(ecdhe_ecdsa) -> true; + (ecdh_ecdsa) -> true; + (_) -> false + end. + +%% Cert should be signed by ECDSA +ecdsa_signed_suites(Ciphers, Version) -> + filter_suites(Ciphers, #{key_exchange_filters => [ecdsa_signed(Version)], + cipher_filters => [], + mac_filters => [], + prf_filters => []}). + +rsa_keyed(dhe_rsa) -> + true; +rsa_keyed(ecdhe_rsa) -> + true; +rsa_keyed(rsa) -> + true; +rsa_keyed(rsa_psk) -> + true; +rsa_keyed(srp_rsa) -> + true; +rsa_keyed(_) -> + false. + +%% Certs key is an RSA key +rsa_keyed_suites(Ciphers) -> + filter_suites(Ciphers, #{key_exchange_filters => [fun(Kex) -> rsa_keyed(Kex) end], + cipher_filters => [], + mac_filters => [], + prf_filters => []}). + +%% RSA Certs key can be used for encipherment +rsa_suites_encipher(Ciphers) -> + filter_suites(Ciphers, #{key_exchange_filters => [fun(rsa) -> true; + (rsa_psk) -> true; + (_) -> false + end], + cipher_filters => [], + mac_filters => [], + prf_filters => []}). + +dss_keyed(dhe_dss) -> + true; +dss_keyed(spr_dss) -> + true; +dss_keyed(_) -> + false. + +%% Cert should be have DSS key (DSA) +dss_keyed_suites(Ciphers) -> + filter_suites(Ciphers, #{key_exchange_filters => [fun(Kex) -> dss_keyed(Kex) end], + cipher_filters => [], + mac_filters => [], + prf_filters => []}). + +%% Cert should be signed by DSS (DSA) +dsa_signed_suites(Ciphers, Version) -> + filter_suites(Ciphers, #{key_exchange_filters => [dsa_signed(Version)], + cipher_filters => [], + mac_filters => [], + prf_filters => []}). +dsa_signed(_) -> + fun(dhe_dss) -> true; + (_) -> false + end. -ecdh_ecdsa_suites() -> - [?TLS_ECDH_ECDSA_WITH_NULL_SHA, - ?TLS_ECDH_ECDSA_WITH_RC4_128_SHA, - ?TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA, - ?TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA, - ?TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA, - ?TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256, - ?TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384]. - -ecdhe_ecdsa_suites() -> - [?TLS_ECDHE_ECDSA_WITH_NULL_SHA, - ?TLS_ECDHE_ECDSA_WITH_RC4_128_SHA, - ?TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA, - ?TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, - ?TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, - ?TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, - ?TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384]. +dss_dhe_suites(Ciphers) -> + filter_suites(Ciphers, #{key_exchange_filters => [fun(dhe_dss) -> true; + (_) -> false + end], + cipher_filters => [], + mac_filters => [], + prf_filters => []}). -filter_keyuse(OtpCert, Ciphers, Suites, SignSuites) -> +ec_keyed(ecdh_ecdsa) -> + true; +ec_keyed(ecdh_rsa) -> + true; +ec_keyed(ecdhe_ecdsa) -> + true; +ec_keyed(_) -> + false. + +%% Certs key is an ECC key +ec_keyed_suites(Ciphers) -> + filter_suites(Ciphers, #{key_exchange_filters => [fun(Kex) -> ec_keyed(Kex) end], + cipher_filters => [], + mac_filters => [], + prf_filters => []}). + +%% EC Certs key usage keyAgreement +ec_ecdh_suites(Ciphers)-> + filter_suites(Ciphers, #{key_exchange_filters => [fun(ecdh_ecdsa) -> true; + (_) -> false + end], + cipher_filters => [], + mac_filters => [], + prf_filters => []}). + +%% EC Certs key usage digitalSignature +ec_ecdhe_suites(Ciphers) -> + filter_suites(Ciphers, #{key_exchange_filters => [fun(ecdhe_ecdsa) -> true; + (ecdhe_rsa) -> true; + (_) -> false + end], + cipher_filters => [], + mac_filters => [], + prf_filters => []}). +%% RSA Certs key usage digitalSignature +rsa_ecdhe_dhe_suites(Ciphers) -> + filter_suites(Ciphers, #{key_exchange_filters => [fun(dhe_rsa) -> true; + (ecdhe_rsa) -> true; + (_) -> false + end], + cipher_filters => [], + mac_filters => [], + prf_filters => []}). + +key_uses(OtpCert) -> TBSCert = OtpCert#'OTPCertificate'.tbsCertificate, TBSExtensions = TBSCert#'OTPTBSCertificate'.extensions, Extensions = ssl_certificate:extensions_list(TBSExtensions), case ssl_certificate:select_extension(?'id-ce-keyUsage', Extensions) of undefined -> - Ciphers; - #'Extension'{extnValue = KeyUse} -> - Result = filter_keyuse_suites(keyEncipherment, - KeyUse, Ciphers, Suites), - filter_keyuse_suites(digitalSignature, - KeyUse, Result, SignSuites) + []; + #'Extension'{extnValue = KeyUses} -> + KeyUses end. +%% If no key-usage extension is defined all key-usages are allowed +filter_keyuse_suites(_, [], CiphersSuites, _) -> + CiphersSuites; filter_keyuse_suites(Use, KeyUse, CipherSuits, Suites) -> case ssl_certificate:is_valid_key_usage(KeyUse, Use) of true -> diff --git a/lib/ssl/src/ssl_cipher.hrl b/lib/ssl/src/ssl_cipher.hrl index 3e50563f0a..2371e8bd32 100644 --- a/lib/ssl/src/ssl_cipher.hrl +++ b/lib/ssl/src/ssl_cipher.hrl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2015. All Rights Reserved. +%% Copyright Ericsson AB 2007-2018. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. +%% 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 %% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% 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% %% @@ -46,7 +47,9 @@ -record(cipher_state, { iv, key, - state + state, + nonce, + tag_len }). %%% TLS_NULL_WITH_NULL_NULL is specified and is the initial state of a @@ -397,8 +400,37 @@ %% TLS_RSA_PSK_WITH_AES_256_CBC_SHA = { 0x00, 0x95 }; -define(TLS_RSA_PSK_WITH_AES_256_CBC_SHA, <<?BYTE(16#00), ?BYTE(16#95)>>). +%%% PSK NULL Cipher Suites RFC 4785 + +%% TLS_PSK_WITH_NULL_SHA = { 0x00, 0x2C }; +-define(TLS_PSK_WITH_NULL_SHA, <<?BYTE(16#00), ?BYTE(16#2C)>>). + +%% TLS_DHE_PSK_WITH_NULL_SHA = { 0x00, 0x2D }; +-define(TLS_DHE_PSK_WITH_NULL_SHA, <<?BYTE(16#00), ?BYTE(16#2D)>>). + +%% TLS_RSA_PSK_WITH_NULL_SHA = { 0x00, 0x2E }; +-define(TLS_RSA_PSK_WITH_NULL_SHA, <<?BYTE(16#00), ?BYTE(16#2E)>>). + %%% TLS 1.2 PSK Cipher Suites RFC 5487 +%% TLS_PSK_WITH_AES_128_GCM_SHA256 = {0x00,0xA8}; +-define(TLS_PSK_WITH_AES_128_GCM_SHA256, <<?BYTE(16#00), ?BYTE(16#A8)>>). + +%% TLS_PSK_WITH_AES_256_GCM_SHA384 = {0x00,0xA9}; +-define(TLS_PSK_WITH_AES_256_GCM_SHA384, <<?BYTE(16#00), ?BYTE(16#A9)>>). + +%% TLS_DHE_PSK_WITH_AES_128_GCM_SHA256 = {0x00,0xAA}; +-define(TLS_DHE_PSK_WITH_AES_128_GCM_SHA256, <<?BYTE(16#00), ?BYTE(16#AA)>>). + +%% TLS_DHE_PSK_WITH_AES_256_GCM_SHA384 = {0x00,0xAB}; +-define(TLS_DHE_PSK_WITH_AES_256_GCM_SHA384, <<?BYTE(16#00), ?BYTE(16#AB)>>). + +%% TLS_RSA_PSK_WITH_AES_128_GCM_SHA256 = {0x00,0xAC}; +-define(TLS_RSA_PSK_WITH_AES_128_GCM_SHA256, <<?BYTE(16#00), ?BYTE(16#AC)>>). + +%% TLS_RSA_PSK_WITH_AES_256_GCM_SHA384 = {0x00,0xAD}; +-define(TLS_RSA_PSK_WITH_AES_256_GCM_SHA384, <<?BYTE(16#00), ?BYTE(16#AD)>>). + %% TLS_PSK_WITH_AES_128_CBC_SHA256 = {0x00,0xAE}; -define(TLS_PSK_WITH_AES_128_CBC_SHA256, <<?BYTE(16#00), ?BYTE(16#AE)>>). @@ -435,6 +467,46 @@ %% TLS_RSA_PSK_WITH_NULL_SHA384 = {0x00,0xB9}; -define(TLS_RSA_PSK_WITH_NULL_SHA384, <<?BYTE(16#00), ?BYTE(16#B9)>>). +%%% ECDHE PSK Cipher Suites RFC 5489 + +%% TLS_ECDHE_PSK_WITH_RC4_128_SHA = {0xC0,0x33}; +-define(TLS_ECDHE_PSK_WITH_RC4_128_SHA, <<?BYTE(16#C0), ?BYTE(16#33)>>). + +%% TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA = {0xC0,0x34}; +-define(TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA, <<?BYTE(16#C0), ?BYTE(16#34)>>). + +%% TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA = {0xC0,0x35}; +-define(TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA, <<?BYTE(16#C0), ?BYTE(16#35)>>). + +%% TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA = {0xC0,0x36}; +-define(TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA, <<?BYTE(16#C0), ?BYTE(16#36)>>). + +%% TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256 = {0xC0,0x37}; +-define(TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256, <<?BYTE(16#C0), ?BYTE(16#37)>>). + +%% TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384 = {0xC0,0x38}; +-define(TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384, <<?BYTE(16#C0), ?BYTE(16#38)>>). + +%% TLS_ECDHE_PSK_WITH_NULL_SHA256 = {0xC0,0x3A}; +-define(TLS_ECDHE_PSK_WITH_NULL_SHA256, <<?BYTE(16#C0), ?BYTE(16#3A)>>). + +%% TLS_ECDHE_PSK_WITH_NULL_SHA384 = {0xC0,0x3B}; +-define(TLS_ECDHE_PSK_WITH_NULL_SHA384, <<?BYTE(16#C0), ?BYTE(16#3B)>>). + +%%% ECDHE_PSK with AES-GCM and AES-CCM Cipher Suites, draft-ietf-tls-ecdhe-psk-aead-05 + +%% TLS_ECDHE_PSK_WITH_AES_128_GCM_SHA256 = {0xTBD; 0xTBD} {0xD0,0x01}; +-define(TLS_ECDHE_PSK_WITH_AES_128_GCM_SHA256, <<?BYTE(16#D0), ?BYTE(16#01)>>). + +%% TLS_ECDHE_PSK_WITH_AES_256_GCM_SHA384 = {0xTBD; 0xTBD} {0xD0,0x02}; +-define(TLS_ECDHE_PSK_WITH_AES_256_GCM_SHA384, <<?BYTE(16#D0), ?BYTE(16#02)>>). + +%% TLS_ECDHE_PSK_WITH_AES_128_CCM_8_SHA256 = {0xTBD; 0xTBD} {0xD0,0x03}; +-define(TLS_ECDHE_PSK_WITH_AES_128_CCM_8_SHA256, <<?BYTE(16#D0), ?BYTE(16#03)>>). + +%% TLS_ECDHE_PSK_WITH_AES_128_CCM_SHA256 = {0xTBD; 0xTBD} {0xD0,0x05}; +-define(TLS_ECDHE_PSK_WITH_AES_128_CCM_SHA256, <<?BYTE(16#D0), ?BYTE(16#05)>>). + %%% SRP Cipher Suites RFC 5054 %% TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA = { 0xC0,0x1A }; @@ -464,4 +536,79 @@ %% TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA = { 0xC0,0x22 }; -define(TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA, <<?BYTE(16#C0), ?BYTE(16#22)>>). +%%% AES-GCM Cipher Suites RFC 5288 + +%% TLS_RSA_WITH_AES_128_GCM_SHA256 = {0x00,0x9C} +-define(TLS_RSA_WITH_AES_128_GCM_SHA256, <<?BYTE(16#00), ?BYTE(16#9C)>>). + +%% TLS_RSA_WITH_AES_256_GCM_SHA384 = {0x00,0x9D} +-define(TLS_RSA_WITH_AES_256_GCM_SHA384, <<?BYTE(16#00), ?BYTE(16#9D)>>). + +%% TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 = {0x00,0x9E} +-define(TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, <<?BYTE(16#00), ?BYTE(16#9E)>>). + +%% TLS_DHE_RSA_WITH_AES_256_GCM_SHA384 = {0x00,0x9F} +-define(TLS_DHE_RSA_WITH_AES_256_GCM_SHA384, <<?BYTE(16#00), ?BYTE(16#9F)>>). + +%% TLS_DH_RSA_WITH_AES_128_GCM_SHA256 = {0x00,0xA0} +-define(TLS_DH_RSA_WITH_AES_128_GCM_SHA256, <<?BYTE(16#00), ?BYTE(16#A0)>>). + +%% TLS_DH_RSA_WITH_AES_256_GCM_SHA384 = {0x00,0xA1} +-define(TLS_DH_RSA_WITH_AES_256_GCM_SHA384, <<?BYTE(16#00), ?BYTE(16#A1)>>). + +%% TLS_DHE_DSS_WITH_AES_128_GCM_SHA256 = {0x00,0xA2} +-define(TLS_DHE_DSS_WITH_AES_128_GCM_SHA256, <<?BYTE(16#00), ?BYTE(16#A2)>>). + +%% TLS_DHE_DSS_WITH_AES_256_GCM_SHA384 = {0x00,0xA3} +-define(TLS_DHE_DSS_WITH_AES_256_GCM_SHA384, <<?BYTE(16#00), ?BYTE(16#A3)>>). + +%% TLS_DH_DSS_WITH_AES_128_GCM_SHA256 = {0x00,0xA4} +-define(TLS_DH_DSS_WITH_AES_128_GCM_SHA256, <<?BYTE(16#00), ?BYTE(16#A4)>>). + +%% TLS_DH_DSS_WITH_AES_256_GCM_SHA384 = {0x00,0xA5} +-define(TLS_DH_DSS_WITH_AES_256_GCM_SHA384, <<?BYTE(16#00), ?BYTE(16#A5)>>). + +%% TLS_DH_anon_WITH_AES_128_GCM_SHA256 = {0x00,0xA6} +-define(TLS_DH_anon_WITH_AES_128_GCM_SHA256, <<?BYTE(16#00), ?BYTE(16#A6)>>). + +%% TLS_DH_anon_WITH_AES_256_GCM_SHA384 = {0x00,0xA7} +-define(TLS_DH_anon_WITH_AES_256_GCM_SHA384, <<?BYTE(16#00), ?BYTE(16#A7)>>). + +%%% ECC AES-GCM Cipher Suites RFC 5289 + +%% TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 = {0xC0,0x2B}; +-define(TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, <<?BYTE(16#C0), ?BYTE(16#2B)>>). + +%% TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 = {0xC0,0x2C}; +-define(TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, <<?BYTE(16#C0), ?BYTE(16#2C)>>). + +%% TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256 = {0xC0,0x2D}; +-define(TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256, <<?BYTE(16#C0), ?BYTE(16#2D)>>). + +%% TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384 = {0xC0,0x2E}; +-define(TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384, <<?BYTE(16#C0), ?BYTE(16#2E)>>). + +%% TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 = {0xC0,0x2F}; +-define(TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, <<?BYTE(16#C0), ?BYTE(16#2F)>>). + +%% TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 = {0xC0,0x30}; +-define(TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, <<?BYTE(16#C0), ?BYTE(16#30)>>). + +%% TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256 = {0xC0,0x31}; +-define(TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256, <<?BYTE(16#C0), ?BYTE(16#31)>>). + +%% TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384 = {0xC0,0x32}; +-define(TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384, <<?BYTE(16#C0), ?BYTE(16#32)>>). + +%%% Chacha20/Poly1305 Suites draft-agl-tls-chacha20poly1305-04 + +%% TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 = {0xcc, 0x13} +-define(TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, <<?BYTE(16#CC), ?BYTE(16#13)>>). + +%% TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 = {0xcc, 0x14} +-define(TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, <<?BYTE(16#CC), ?BYTE(16#14)>>). + +%% TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256 = {0xcc, 0x15} +-define(TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256, <<?BYTE(16#CC), ?BYTE(16#15)>>). + -endif. % -ifdef(ssl_cipher). diff --git a/lib/ssl/src/ssl_cipher_format.erl b/lib/ssl/src/ssl_cipher_format.erl new file mode 100644 index 0000000000..c311c0d097 --- /dev/null +++ b/lib/ssl/src/ssl_cipher_format.erl @@ -0,0 +1,1764 @@ +% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2018-2018. 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% +%% + +%% +%%---------------------------------------------------------------------- +%% Purpose: Convert between diffrent cipher suite representations +%% +%%---------------------------------------------------------------------- +-module(ssl_cipher_format). + +-include("ssl_cipher.hrl"). +-include("ssl_internal.hrl"). +-include_lib("public_key/include/public_key.hrl"). + +-export_type([cipher_suite/0, + erl_cipher_suite/0, old_erl_cipher_suite/0, openssl_cipher_suite/0, + hash/0, key_algo/0, sign_algo/0]). + +-type cipher() :: null |rc4_128 | des_cbc | '3des_ede_cbc' | aes_128_cbc | aes_256_cbc | aes_128_gcm | aes_256_gcm | chacha20_poly1305. +-type hash() :: null | md5 | sha | 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 erl_cipher_suite() :: #{key_exchange := key_algo(), + cipher := cipher(), + mac := hash() | aead, + prf := hash() | default_prf %% Old cipher suites, version dependent + }. +-type old_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}. +-type cipher_suite() :: binary(). +-type openssl_cipher_suite() :: string(). + + +-export([suite_to_str/1, suite_definition/1, suite/1, erl_suite_definition/1, + openssl_suite/1, openssl_suite_name/1]). + +%%-------------------------------------------------------------------- +-spec suite_to_str(erl_cipher_suite()) -> string(). +%% +%% Description: Return the string representation of a cipher suite. +%%-------------------------------------------------------------------- +suite_to_str(#{key_exchange := null, + cipher := null, + mac := null, + prf := null}) -> + "TLS_EMPTY_RENEGOTIATION_INFO_SCSV"; +suite_to_str(#{key_exchange := Kex, + cipher := Cipher, + mac := aead, + prf := PRF}) -> + "TLS_" ++ string:to_upper(atom_to_list(Kex)) ++ + "_WITH_" ++ string:to_upper(atom_to_list(Cipher)) ++ + "_" ++ string:to_upper(atom_to_list(PRF)); +suite_to_str(#{key_exchange := Kex, + cipher := Cipher, + mac := Mac}) -> + "TLS_" ++ string:to_upper(atom_to_list(Kex)) ++ + "_WITH_" ++ string:to_upper(atom_to_list(Cipher)) ++ + "_" ++ string:to_upper(atom_to_list(Mac)). + +%%-------------------------------------------------------------------- +-spec suite_definition(cipher_suite()) -> erl_cipher_suite(). +%% +%% Description: Return erlang cipher suite definition. +%% Note: Currently not supported suites are commented away. +%% They should be supported or removed in the future. +%%------------------------------------------------------------------- +%% TLS v1.1 suites +suite_definition(?TLS_NULL_WITH_NULL_NULL) -> + #{key_exchange => null, + cipher => null, + mac => null, + prf => null}; +%% RFC 5746 - Not a real cipher suite used to signal empty "renegotiation_info" extension +%% to avoid handshake failure from old servers that do not ignore +%% hello extension data as they should. +suite_definition(?TLS_EMPTY_RENEGOTIATION_INFO_SCSV) -> + #{key_exchange => null, + cipher => null, + mac => null, + prf => null}; +suite_definition(?TLS_RSA_WITH_RC4_128_MD5) -> + #{key_exchange => rsa, + cipher => rc4_128, + mac => md5, + prf => default_prf}; +suite_definition(?TLS_RSA_WITH_RC4_128_SHA) -> + #{key_exchange => rsa, + cipher => rc4_128, + mac => sha, + prf => default_prf}; +suite_definition(?TLS_RSA_WITH_DES_CBC_SHA) -> + #{key_exchange => rsa, + cipher => des_cbc, + mac => sha, + prf => default_prf}; +suite_definition(?TLS_RSA_WITH_3DES_EDE_CBC_SHA) -> + #{key_exchange => rsa, + cipher => '3des_ede_cbc', + mac => sha, + prf => default_prf}; +suite_definition(?TLS_DHE_DSS_WITH_DES_CBC_SHA) -> + #{key_exchange => dhe_dss, + cipher => des_cbc, + mac => sha, + prf => default_prf}; +suite_definition(?TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA) -> + #{key_exchange => dhe_dss, + cipher => '3des_ede_cbc', + mac => sha, + prf => default_prf}; +suite_definition(?TLS_DHE_RSA_WITH_DES_CBC_SHA) -> + #{key_exchange => dhe_rsa, + cipher => des_cbc, + mac => sha, + prf => default_prf}; +suite_definition(?TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA) -> + #{key_exchange => dhe_rsa, + cipher => '3des_ede_cbc', + mac => sha, + prf => default_prf}; +%%% TSL V1.1 AES suites +suite_definition(?TLS_RSA_WITH_AES_128_CBC_SHA) -> + #{key_exchange => rsa, + cipher => aes_128_cbc, + mac => sha, + prf => default_prf}; +suite_definition(?TLS_DHE_DSS_WITH_AES_128_CBC_SHA) -> + #{key_exchange => dhe_dss, + cipher => aes_128_cbc, + mac => sha, + prf => default_prf}; +suite_definition(?TLS_DHE_RSA_WITH_AES_128_CBC_SHA) -> + #{key_exchange => dhe_rsa, + cipher => aes_128_cbc, + mac => sha, + prf => default_prf}; +suite_definition(?TLS_RSA_WITH_AES_256_CBC_SHA) -> + #{key_exchange => rsa, + cipher => aes_256_cbc, + mac => sha, + prf => default_prf}; +suite_definition(?TLS_DHE_DSS_WITH_AES_256_CBC_SHA) -> + #{key_exchange => dhe_dss, + cipher => aes_256_cbc, + mac => sha, + prf => default_prf}; +suite_definition(?TLS_DHE_RSA_WITH_AES_256_CBC_SHA) -> + #{key_exchange => dhe_rsa, + cipher => aes_256_cbc, + mac => sha, + prf => default_prf}; +%% TLS v1.2 suites +%% suite_definition(?TLS_RSA_WITH_NULL_SHA) -> +%% {rsa, null, sha, default_prf}; +suite_definition(?TLS_RSA_WITH_AES_128_CBC_SHA256) -> + #{key_exchange => rsa, + cipher => aes_128_cbc, + mac => sha256, + prf => default_prf}; +suite_definition(?TLS_RSA_WITH_AES_256_CBC_SHA256) -> + #{key_exchange => rsa, + cipher => aes_256_cbc, + mac => sha256, + prf => default_prf}; +suite_definition(?TLS_DHE_DSS_WITH_AES_128_CBC_SHA256) -> + #{key_exchange => dhe_dss, + cipher => aes_128_cbc, + mac => sha256, + prf => default_prf}; +suite_definition(?TLS_DHE_RSA_WITH_AES_128_CBC_SHA256) -> + #{key_exchange => dhe_rsa, + cipher => aes_128_cbc, + mac => sha256, + prf => default_prf}; +suite_definition(?TLS_DHE_DSS_WITH_AES_256_CBC_SHA256) -> + #{key_exchange => dhe_dss, + cipher => aes_256_cbc, + mac => sha256, + prf => default_prf}; +suite_definition(?TLS_DHE_RSA_WITH_AES_256_CBC_SHA256) -> + #{key_exchange => dhe_rsa, + cipher => aes_256_cbc, + mac => sha256, + prf => default_prf}; +%% not defined YET: +%% TLS_DH_DSS_WITH_AES_128_CBC_SHA256 DH_DSS AES_128_CBC SHA256 +%% TLS_DH_RSA_WITH_AES_128_CBC_SHA256 DH_RSA AES_128_CBC SHA256 +%% TLS_DH_DSS_WITH_AES_256_CBC_SHA256 DH_DSS AES_256_CBC SHA256 +%% TLS_DH_RSA_WITH_AES_256_CBC_SHA256 DH_RSA AES_256_CBC SHA256 +%%% DH-ANON deprecated by TLS spec and not available +%%% by default, but good for testing purposes. +suite_definition(?TLS_DH_anon_WITH_RC4_128_MD5) -> + #{key_exchange => dh_anon, + cipher => rc4_128, + mac => md5, + prf => default_prf}; +suite_definition(?TLS_DH_anon_WITH_DES_CBC_SHA) -> + #{key_exchange => dh_anon, + cipher => des_cbc, + mac => sha, + prf => default_prf}; +suite_definition(?TLS_DH_anon_WITH_3DES_EDE_CBC_SHA) -> + #{key_exchange => dh_anon, + cipher => '3des_ede_cbc', + mac => sha, + prf => default_prf}; +suite_definition(?TLS_DH_anon_WITH_AES_128_CBC_SHA) -> + #{key_exchange => dh_anon, + cipher => aes_128_cbc, + mac => sha, + prf => default_prf}; +suite_definition(?TLS_DH_anon_WITH_AES_256_CBC_SHA) -> + #{key_exchange => dh_anon, + cipher => aes_256_cbc, + mac => sha, + prf => default_prf}; +suite_definition(?TLS_DH_anon_WITH_AES_128_CBC_SHA256) -> + #{key_exchange => dh_anon, + cipher => aes_128_cbc, + mac => sha256, + prf => default_prf}; +suite_definition(?TLS_DH_anon_WITH_AES_256_CBC_SHA256) -> + #{key_exchange => dh_anon, + cipher => aes_256_cbc, + mac => sha256, + prf => default_prf}; +%%% PSK Cipher Suites RFC 4279 +suite_definition(?TLS_PSK_WITH_RC4_128_SHA) -> + #{key_exchange => psk, + cipher => rc4_128, + mac => sha, + prf => default_prf}; +suite_definition(?TLS_PSK_WITH_3DES_EDE_CBC_SHA) -> + #{key_exchange => psk, + cipher => '3des_ede_cbc', + mac => sha, + prf => default_prf}; +suite_definition(?TLS_PSK_WITH_AES_128_CBC_SHA) -> + #{key_exchange => psk, + cipher => aes_128_cbc, + mac => sha, + prf => default_prf}; +suite_definition(?TLS_PSK_WITH_AES_256_CBC_SHA) -> + #{key_exchange => psk, + cipher => aes_256_cbc, + mac => sha, + prf => default_prf}; +suite_definition(?TLS_DHE_PSK_WITH_RC4_128_SHA) -> + #{key_exchange => dhe_psk, + cipher => rc4_128, + mac => sha, + prf => default_prf}; +suite_definition(?TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA) -> + #{key_exchange => dhe_psk, + cipher => '3des_ede_cbc', + mac => sha, + prf => default_prf}; +suite_definition(?TLS_DHE_PSK_WITH_AES_128_CBC_SHA) -> + #{key_exchange => dhe_psk, + cipher => aes_128_cbc, + mac => sha, + prf => default_prf}; +suite_definition(?TLS_DHE_PSK_WITH_AES_256_CBC_SHA) -> + #{key_exchange => dhe_psk, + cipher => aes_256_cbc, + mac => sha, + prf => default_prf}; +suite_definition(?TLS_RSA_PSK_WITH_RC4_128_SHA) -> + #{key_exchange => rsa_psk, + cipher => rc4_128, + mac => sha, + prf => default_prf}; +suite_definition(?TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA) -> + #{key_exchange => rsa_psk, + cipher => '3des_ede_cbc', + mac => sha, + prf => default_prf}; +suite_definition(?TLS_RSA_PSK_WITH_AES_128_CBC_SHA) -> + #{key_exchange => rsa_psk, + cipher => aes_128_cbc, + mac => sha, + prf => default_prf}; +suite_definition(?TLS_RSA_PSK_WITH_AES_256_CBC_SHA) -> + #{key_exchange => rsa_psk, + cipher => aes_256_cbc, + mac => sha, + prf => default_prf}; +%%% PSK NULL Cipher Suites RFC 4785 +suite_definition(?TLS_PSK_WITH_NULL_SHA) -> + #{key_exchange => psk, + cipher => null, + mac => sha, + prf => default_prf}; +suite_definition(?TLS_DHE_PSK_WITH_NULL_SHA) -> + #{key_exchange => dhe_psk, + cipher => null, + mac => sha, + prf => default_prf}; +suite_definition(?TLS_RSA_PSK_WITH_NULL_SHA) -> + #{key_exchange => rsa_psk, + cipher => null, + mac => sha, + prf => default_prf}; +%%% TLS 1.2 PSK Cipher Suites RFC 5487 +suite_definition(?TLS_PSK_WITH_AES_128_GCM_SHA256) -> + #{key_exchange => psk, + cipher => aes_128_gcm, + mac => aead, + prf => sha256}; +suite_definition(?TLS_PSK_WITH_AES_256_GCM_SHA384) -> + #{key_exchange => psk, + cipher => aes_256_gcm, + mac => aead, + prf => sha384}; +suite_definition(?TLS_DHE_PSK_WITH_AES_128_GCM_SHA256) -> + #{key_exchange => dhe_psk, + cipher => aes_128_gcm, + mac => aead, + prf => sha256}; +suite_definition(?TLS_DHE_PSK_WITH_AES_256_GCM_SHA384) -> + #{key_exchange => dhe_psk, + cipher => aes_256_gcm, + mac => aead, + prf => sha384}; +suite_definition(?TLS_RSA_PSK_WITH_AES_128_GCM_SHA256) -> + #{key_exchange => rsa_psk, + cipher => aes_128_gcm, + mac => aead, + prf => sha256}; +suite_definition(?TLS_RSA_PSK_WITH_AES_256_GCM_SHA384) -> + #{key_exchange => rsa_psk, + cipher => aes_256_gcm, + mac => aead, + prf => sha384}; +suite_definition(?TLS_PSK_WITH_AES_128_CBC_SHA256) -> + #{key_exchange => psk, + cipher => aes_128_cbc, + mac => sha256, + prf => default_prf}; +suite_definition(?TLS_PSK_WITH_AES_256_CBC_SHA384) -> + #{key_exchange => psk, + cipher => aes_256_cbc, + mac => sha384, + prf => default_prf}; +suite_definition(?TLS_DHE_PSK_WITH_AES_128_CBC_SHA256) -> + #{key_exchange => dhe_psk, + cipher => aes_128_cbc, + mac => sha256, + prf => default_prf}; +suite_definition(?TLS_DHE_PSK_WITH_AES_256_CBC_SHA384) -> + #{key_exchange => dhe_psk, + cipher => aes_256_cbc, + mac => sha384, + prf => default_prf}; +suite_definition(?TLS_RSA_PSK_WITH_AES_128_CBC_SHA256) -> + #{key_exchange => rsa_psk, + cipher => aes_128_cbc, + mac => sha256, + prf => default_prf}; +suite_definition(?TLS_RSA_PSK_WITH_AES_256_CBC_SHA384) -> + #{key_exchange => rsa_psk, + cipher => aes_256_cbc, + mac => sha384, + prf => default_prf}; +suite_definition(?TLS_PSK_WITH_NULL_SHA256) -> + #{key_exchange => psk, + cipher => null, + mac => sha256, + prf => default_prf}; +suite_definition(?TLS_PSK_WITH_NULL_SHA384) -> + #{key_exchange => psk, + cipher => null, + mac => sha384, + prf => default_prf}; +suite_definition(?TLS_DHE_PSK_WITH_NULL_SHA256) -> + #{key_exchange => dhe_psk, + cipher => null, + mac => sha256, + prf => default_prf}; +suite_definition(?TLS_DHE_PSK_WITH_NULL_SHA384) -> + #{key_exchange => dhe_psk, + cipher => null, + mac => sha384, + prf => default_prf}; +suite_definition(?TLS_RSA_PSK_WITH_NULL_SHA256) -> + #{key_exchange => rsa_psk, + cipher => null, + mac => sha256, + prf => default_prf}; +suite_definition(?TLS_RSA_PSK_WITH_NULL_SHA384) -> + #{key_exchange => rsa_psk, + cipher => null, + mac => sha384, + prf => default_prf}; +%%% ECDHE PSK Cipher Suites RFC 5489 +suite_definition(?TLS_ECDHE_PSK_WITH_RC4_128_SHA) -> + #{key_exchange => ecdhe_psk, + cipher => rc4_128, + mac => sha, + prf => default_prf}; +suite_definition(?TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA) -> + #{key_exchange => ecdhe_psk, + cipher => '3des_ede_cbc', + mac => sha, + prf => default_prf}; +suite_definition(?TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA) -> + #{key_exchange => ecdhe_psk, + cipher => aes_128_cbc, + mac => sha, + prf => default_prf}; +suite_definition(?TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA) -> + #{key_exchange => ecdhe_psk, + cipher => aes_256_cbc, + mac => sha, + prf => default_prf}; +suite_definition(?TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256) -> + #{key_exchange => ecdhe_psk, + cipher => aes_128_cbc, + mac => sha256, + prf => default_prf}; +suite_definition(?TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384) -> + #{key_exchange => ecdhe_psk, + cipher => aes_256_cbc, + mac => sha384, + prf => default_prf}; +suite_definition(?TLS_ECDHE_PSK_WITH_NULL_SHA256) -> + #{key_exchange => ecdhe_psk, + cipher => null, + mac => sha256, + prf => default_prf}; +suite_definition(?TLS_ECDHE_PSK_WITH_NULL_SHA384) -> + #{key_exchange => ecdhe_psk, + cipher => null, mac => sha384, + prf => default_prf}; +%%% ECDHE_PSK with AES-GCM and AES-CCM Cipher Suites, draft-ietf-tls-ecdhe-psk-aead-05 +suite_definition(?TLS_ECDHE_PSK_WITH_AES_128_GCM_SHA256) -> + #{key_exchange => ecdhe_psk, + cipher => aes_128_gcm, + mac => null, + prf => sha256}; +suite_definition(?TLS_ECDHE_PSK_WITH_AES_256_GCM_SHA384) -> + #{key_exchange => ecdhe_psk, + cipher => aes_256_gcm, + mac => null, + prf => sha384}; +%% suite_definition(?TLS_ECDHE_PSK_WITH_AES_128_CCM_8_SHA256) -> +%% #{key_exchange => ecdhe_psk, +%% cipher => aes_128_ccm, +%% mac => null, +%% prf =>sha256}; +%% suite_definition(?TLS_ECDHE_PSK_WITH_AES_128_CCM_SHA256) -> +%% #{key_exchange => ecdhe_psk, +%% cipher => aes_256_ccm, +%% mac => null, +%% prf => sha256}; +%%% SRP Cipher Suites RFC 5054 +suite_definition(?TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA) -> + #{key_exchange => srp_anon, + cipher => '3des_ede_cbc', + mac => sha, + prf => default_prf}; +suite_definition(?TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA) -> + #{key_exchange => srp_rsa, + cipher => '3des_ede_cbc', + mac => sha, + prf => default_prf}; +suite_definition(?TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA) -> + #{key_exchange => srp_dss, + cipher => '3des_ede_cbc', + mac => sha, + prf => default_prf}; +suite_definition(?TLS_SRP_SHA_WITH_AES_128_CBC_SHA) -> + #{key_exchange => srp_anon, + cipher => aes_128_cbc, + mac => sha, + prf => default_prf}; +suite_definition(?TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA) -> + #{key_exchange => srp_rsa, + cipher => aes_128_cbc, + mac => sha, + prf => default_prf}; +suite_definition(?TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA) -> + #{key_exchange => srp_dss, + cipher => aes_128_cbc, + mac => sha, + prf => default_prf}; +suite_definition(?TLS_SRP_SHA_WITH_AES_256_CBC_SHA) -> + #{key_exchange => srp_anon, + cipher => aes_256_cbc, + mac => sha, + prf => default_prf}; +suite_definition(?TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA) -> + #{key_exchange => srp_rsa, + cipher => aes_256_cbc, + mac => sha, + prf => default_prf}; +suite_definition(?TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA) -> + #{key_exchange => srp_dss, + cipher => aes_256_cbc, + mac => sha, + prf => default_prf}; +%% RFC 4492 EC TLS suites +suite_definition(?TLS_ECDH_ECDSA_WITH_NULL_SHA) -> + #{key_exchange => ecdh_ecdsa, + cipher => null, + mac => sha, + prf => default_prf}; +suite_definition(?TLS_ECDH_ECDSA_WITH_RC4_128_SHA) -> + #{key_exchange => ecdh_ecdsa, + cipher => rc4_128, + mac => sha, + prf => default_prf}; +suite_definition(?TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA) -> + #{key_exchange => ecdh_ecdsa, + cipher => '3des_ede_cbc', + mac => sha, + prf => default_prf}; +suite_definition(?TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA) -> + #{key_exchange => ecdh_ecdsa, + cipher => aes_128_cbc, + mac => sha, + prf => default_prf}; +suite_definition(?TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA) -> + #{key_exchange => ecdh_ecdsa, + cipher => aes_256_cbc, + mac => sha, + prf => default_prf}; +suite_definition(?TLS_ECDHE_ECDSA_WITH_NULL_SHA) -> + #{key_exchange => ecdhe_ecdsa, + cipher => null, + mac => sha, + prf => default_prf}; +suite_definition(?TLS_ECDHE_ECDSA_WITH_RC4_128_SHA) -> + #{key_exchange => ecdhe_ecdsa, + cipher => rc4_128, + mac => sha, + prf => default_prf}; +suite_definition(?TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA) -> + #{key_exchange => ecdhe_ecdsa, + cipher => '3des_ede_cbc', + mac => sha, + prf => default_prf}; +suite_definition(?TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA) -> + #{key_exchange => ecdhe_ecdsa, + cipher => aes_128_cbc, + mac => sha, + prf => default_prf}; +suite_definition(?TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA) -> + #{key_exchange => ecdhe_ecdsa, + cipher => aes_256_cbc, + mac => sha, + prf => default_prf}; +suite_definition(?TLS_ECDH_RSA_WITH_NULL_SHA) -> + #{key_exchange => ecdh_rsa, + cipher => null, + mac => sha, + prf => default_prf}; +suite_definition(?TLS_ECDH_RSA_WITH_RC4_128_SHA) -> + #{key_exchange => ecdh_rsa, + cipher => rc4_128, + mac => sha, + prf => default_prf}; +suite_definition(?TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA) -> + #{key_exchange => ecdh_rsa, + cipher => '3des_ede_cbc', + mac => sha, + prf => default_prf}; +suite_definition(?TLS_ECDH_RSA_WITH_AES_128_CBC_SHA) -> + #{key_exchange => ecdh_rsa, + cipher => aes_128_cbc, + mac => sha, + prf => default_prf}; +suite_definition(?TLS_ECDH_RSA_WITH_AES_256_CBC_SHA) -> + #{key_exchange => ecdh_rsa, + cipher => aes_256_cbc, + mac => sha, + prf => default_prf}; +suite_definition(?TLS_ECDHE_RSA_WITH_NULL_SHA) -> + #{key_exchange => ecdhe_rsa, + cipher => null, + mac => sha, + prf => default_prf}; +suite_definition(?TLS_ECDHE_RSA_WITH_RC4_128_SHA) -> + #{key_exchange => ecdhe_rsa, + cipher => rc4_128, + mac => sha, + prf => default_prf}; +suite_definition(?TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA) -> + #{key_exchange => ecdhe_rsa, + cipher => '3des_ede_cbc', + mac => sha, + prf => default_prf}; +suite_definition(?TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA) -> + #{key_exchange => ecdhe_rsa, + cipher => aes_128_cbc, + mac => sha, + prf => default_prf}; +suite_definition(?TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA) -> + #{key_exchange => ecdhe_rsa, + cipher => aes_256_cbc, + mac => sha, + prf => default_prf}; +suite_definition(?TLS_ECDH_anon_WITH_NULL_SHA) -> + #{key_exchange => ecdh_anon, + cipher => null, + mac => sha, + prf => default_prf}; +suite_definition(?TLS_ECDH_anon_WITH_RC4_128_SHA) -> + #{key_exchange => ecdh_anon, + cipher => rc4_128, + mac => sha, + prf => default_prf}; +suite_definition(?TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA) -> + #{key_exchange => ecdh_anon, + cipher => '3des_ede_cbc', + mac => sha, + prf => default_prf}; +suite_definition(?TLS_ECDH_anon_WITH_AES_128_CBC_SHA) -> + #{key_exchange => ecdh_anon, + cipher => aes_128_cbc, + mac => sha, + prf => default_prf}; +suite_definition(?TLS_ECDH_anon_WITH_AES_256_CBC_SHA) -> + #{key_exchange => ecdh_anon, + cipher => aes_256_cbc, + mac => sha, + prf => default_prf}; +%% RFC 5289 EC TLS suites +suite_definition(?TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256) -> + #{key_exchange => ecdhe_ecdsa, + cipher => aes_128_cbc, + mac => sha256, + prf => sha256}; +suite_definition(?TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384) -> + #{key_exchange => ecdhe_ecdsa, + cipher => aes_256_cbc, + mac => sha384, + prf => sha384}; +suite_definition(?TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256) -> + #{key_exchange => ecdh_ecdsa, + cipher => aes_128_cbc, + mac => sha256, + prf => sha256}; +suite_definition(?TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384) -> + #{key_exchange => ecdh_ecdsa, + cipher => aes_256_cbc, + mac => sha384, + prf => sha384}; +suite_definition(?TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256) -> + #{key_exchange => ecdhe_rsa, + cipher => aes_128_cbc, + mac => sha256, + prf => sha256}; +suite_definition(?TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384) -> + #{key_exchange => ecdhe_rsa, + cipher => aes_256_cbc, + mac => sha384, + prf => sha384}; +suite_definition(?TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256) -> + #{key_exchange => ecdh_rsa, + cipher => aes_128_cbc, + mac => sha256, + prf => sha256}; +suite_definition(?TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384) -> + #{key_exchange => ecdh_rsa, + cipher => aes_256_cbc, + mac => sha384, + prf => sha384}; +%% RFC 5288 AES-GCM Cipher Suites +suite_definition(?TLS_RSA_WITH_AES_128_GCM_SHA256) -> + #{key_exchange => rsa, + cipher => aes_128_gcm, + mac => aead, + prf => sha256}; +suite_definition(?TLS_RSA_WITH_AES_256_GCM_SHA384) -> + #{key_exchange => rsa, + cipher => aes_256_gcm, + mac => aead, + prf => sha384}; +suite_definition(?TLS_DHE_RSA_WITH_AES_128_GCM_SHA256) -> + #{key_exchange => dhe_rsa, + cipher => aes_128_gcm, + mac => aead, + prf => sha256}; +suite_definition(?TLS_DHE_RSA_WITH_AES_256_GCM_SHA384) -> + #{key_exchange => dhe_rsa, + cipher => aes_256_gcm, + mac => aead, + prf => sha384}; +suite_definition(?TLS_DH_RSA_WITH_AES_128_GCM_SHA256) -> + #{key_exchange => dh_rsa, + cipher => aes_128_gcm, + mac => aead, + prf => sha256}; +suite_definition(?TLS_DH_RSA_WITH_AES_256_GCM_SHA384) -> + #{key_exchange => dh_rsa, + cipher => aes_256_gcm, + mac => aead, + prf => sha384}; +suite_definition(?TLS_DHE_DSS_WITH_AES_128_GCM_SHA256) -> + #{key_exchange => dhe_dss, + cipher => aes_128_gcm, + mac => aead, + prf => sha256}; +suite_definition(?TLS_DHE_DSS_WITH_AES_256_GCM_SHA384) -> + #{key_exchange => dhe_dss, + cipher => aes_256_gcm, + mac => aead, + prf => sha384}; +suite_definition(?TLS_DH_DSS_WITH_AES_128_GCM_SHA256) -> + #{key_exchange => dh_dss, + cipher => aes_128_gcm, + mac => null, + prf => sha256}; +suite_definition(?TLS_DH_DSS_WITH_AES_256_GCM_SHA384) -> + #{key_exchange => dh_dss, + cipher => aes_256_gcm, + mac => aead, + prf => sha384}; +suite_definition(?TLS_DH_anon_WITH_AES_128_GCM_SHA256) -> + #{key_exchange => dh_anon, + cipher => aes_128_gcm, + mac => aead, + prf => sha256}; +suite_definition(?TLS_DH_anon_WITH_AES_256_GCM_SHA384) -> + #{key_exchange => dh_anon, + cipher => aes_256_gcm, + mac => aead, + prf => sha384}; +%% RFC 5289 ECC AES-GCM Cipher Suites +suite_definition(?TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256) -> + #{key_exchange => ecdhe_ecdsa, + cipher => aes_128_gcm, + mac => aead, + prf => sha256}; +suite_definition(?TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384) -> + #{key_exchange => ecdhe_ecdsa, + cipher => aes_256_gcm, + mac => aead, + prf => sha384}; +suite_definition(?TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256) -> + #{key_exchange => ecdh_ecdsa, + cipher => aes_128_gcm, + mac => aead, + prf => sha256}; +suite_definition(?TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384) -> + #{key_exchange => ecdh_ecdsa, + cipher => aes_256_gcm, + mac => aead, + prf => sha384}; +suite_definition(?TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) -> + #{key_exchange => ecdhe_rsa, + cipher => aes_128_gcm, + mac => aead, + prf => sha256}; +suite_definition(?TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) -> + #{key_exchange => ecdhe_rsa, + cipher => aes_256_gcm, + mac => aead, + prf => sha384}; +suite_definition(?TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256) -> + #{key_exchange => ecdh_rsa, + cipher => aes_128_gcm, + mac => aead, + prf => sha256}; +suite_definition(?TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384) -> + #{key_exchange => ecdh_rsa, + cipher => aes_256_gcm, + mac => aead, + prf => sha384}; +%% draft-agl-tls-chacha20poly1305-04 Chacha20/Poly1305 Suites +suite_definition(?TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256) -> + #{key_exchange => ecdhe_rsa, + cipher => chacha20_poly1305, + mac => aead, + prf => sha256}; +suite_definition(?TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256) -> + #{key_exchange => ecdhe_ecdsa, + cipher => chacha20_poly1305, + mac => aead, + prf => sha256}; +suite_definition(?TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256) -> + #{key_exchange => dhe_rsa, + cipher => chacha20_poly1305, + mac => aead, + prf => sha256}. + +%%-------------------------------------------------------------------- +-spec erl_suite_definition(cipher_suite() | erl_cipher_suite()) -> old_erl_cipher_suite(). +%% +%% Description: Return erlang cipher suite definition. Filters last value +%% for now (compatibility reasons). +%%-------------------------------------------------------------------- +erl_suite_definition(Bin) when is_binary(Bin) -> + erl_suite_definition(suite_definition(Bin)); +erl_suite_definition(#{key_exchange := KeyExchange, cipher := Cipher, + mac := Hash, prf := Prf}) -> + case Prf of + default_prf -> + {KeyExchange, Cipher, Hash}; + _ -> + {KeyExchange, Cipher, Hash, Prf} + end. + +%%-------------------------------------------------------------------- +-spec suite(erl_cipher_suite()) -> cipher_suite(). +%% +%% Description: Return TLS cipher suite definition. +%%-------------------------------------------------------------------- +%% TLS v1.1 suites +suite(#{key_exchange := rsa, + cipher := rc4_128, + mac := md5}) -> + ?TLS_RSA_WITH_RC4_128_MD5; +suite(#{key_exchange := rsa, + cipher := rc4_128, + mac := sha}) -> + ?TLS_RSA_WITH_RC4_128_SHA; +suite(#{key_exchange := rsa, + cipher := des_cbc, + mac := sha}) -> + ?TLS_RSA_WITH_DES_CBC_SHA; +suite(#{key_exchange := rsa, + cipher :='3des_ede_cbc', + mac := sha}) -> + ?TLS_RSA_WITH_3DES_EDE_CBC_SHA; +suite(#{key_exchange := dhe_dss, + cipher:= des_cbc, + mac := sha}) -> + ?TLS_DHE_DSS_WITH_DES_CBC_SHA; +suite(#{key_exchange := dhe_dss, + cipher:= '3des_ede_cbc', + mac := sha}) -> + ?TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA; +suite(#{key_exchange := dhe_rsa, + cipher:= des_cbc, + mac := sha}) -> + ?TLS_DHE_RSA_WITH_DES_CBC_SHA; +suite(#{key_exchange := dhe_rsa, + cipher:= '3des_ede_cbc', + mac := sha}) -> + ?TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA; +suite(#{key_exchange := dh_anon, + cipher:= rc4_128, + mac := md5}) -> + ?TLS_DH_anon_WITH_RC4_128_MD5; +suite(#{key_exchange := dh_anon, + cipher:= des_cbc, + mac := sha}) -> + ?TLS_DH_anon_WITH_DES_CBC_SHA; +suite(#{key_exchange := dh_anon, + cipher:= '3des_ede_cbc', + mac := sha}) -> + ?TLS_DH_anon_WITH_3DES_EDE_CBC_SHA; +%%% TSL V1.1 AES suites +suite(#{key_exchange := rsa, + cipher := aes_128_cbc, + mac := sha}) -> + ?TLS_RSA_WITH_AES_128_CBC_SHA; +suite(#{key_exchange := dhe_dss, + cipher := aes_128_cbc, + mac := sha}) -> + ?TLS_DHE_DSS_WITH_AES_128_CBC_SHA; +suite(#{key_exchange := dhe_rsa, + cipher := aes_128_cbc, + mac := sha}) -> + ?TLS_DHE_RSA_WITH_AES_128_CBC_SHA; +suite(#{key_exchange := dh_anon, + cipher := aes_128_cbc, + mac := sha}) -> + ?TLS_DH_anon_WITH_AES_128_CBC_SHA; +suite(#{key_exchange := rsa, + cipher := aes_256_cbc, + mac := sha}) -> + ?TLS_RSA_WITH_AES_256_CBC_SHA; +suite(#{key_exchange := dhe_dss, + cipher := aes_256_cbc, + mac := sha}) -> + ?TLS_DHE_DSS_WITH_AES_256_CBC_SHA; +suite(#{key_exchange := dhe_rsa, + cipher := aes_256_cbc, + mac := sha}) -> + ?TLS_DHE_RSA_WITH_AES_256_CBC_SHA; +suite(#{key_exchange := dh_anon, + cipher := aes_256_cbc, + mac := sha}) -> + ?TLS_DH_anon_WITH_AES_256_CBC_SHA; +%% TLS v1.2 suites +suite(#{key_exchange := rsa, + cipher := aes_128_cbc, + mac := sha256}) -> + ?TLS_RSA_WITH_AES_128_CBC_SHA256; +suite(#{key_exchange := rsa, + cipher := aes_256_cbc, + mac := sha256}) -> + ?TLS_RSA_WITH_AES_256_CBC_SHA256; +suite(#{key_exchange := dhe_dss, + cipher := aes_128_cbc, + mac := sha256}) -> + ?TLS_DHE_DSS_WITH_AES_128_CBC_SHA256; +suite(#{key_exchange := dhe_rsa, + cipher := aes_128_cbc, + mac := sha256}) -> + ?TLS_DHE_RSA_WITH_AES_128_CBC_SHA256; +suite(#{key_exchange := dhe_dss, + cipher := aes_256_cbc, + mac := sha256}) -> + ?TLS_DHE_DSS_WITH_AES_256_CBC_SHA256; +suite(#{key_exchange := dhe_rsa, + cipher := aes_256_cbc, + mac := sha256}) -> + ?TLS_DHE_RSA_WITH_AES_256_CBC_SHA256; +suite(#{key_exchange := dh_anon, + cipher := aes_128_cbc, + mac := sha256}) -> + ?TLS_DH_anon_WITH_AES_128_CBC_SHA256; +suite(#{key_exchange := dh_anon, + cipher := aes_256_cbc, + mac := sha256}) -> + ?TLS_DH_anon_WITH_AES_256_CBC_SHA256; +%%% PSK Cipher Suites RFC 4279 +suite(#{key_exchange := psk, + cipher := rc4_128, + mac := sha}) -> + ?TLS_PSK_WITH_RC4_128_SHA; +suite(#{key_exchange := psk, + cipher := '3des_ede_cbc', + mac := sha}) -> + ?TLS_PSK_WITH_3DES_EDE_CBC_SHA; +suite(#{key_exchange := psk, + cipher := aes_128_cbc, + mac := sha}) -> + ?TLS_PSK_WITH_AES_128_CBC_SHA; +suite(#{key_exchange := psk, + cipher := aes_256_cbc, + mac := sha}) -> + ?TLS_PSK_WITH_AES_256_CBC_SHA; +suite(#{key_exchange := dhe_psk, + cipher := rc4_128, + mac := sha}) -> + ?TLS_DHE_PSK_WITH_RC4_128_SHA; +suite(#{key_exchange := dhe_psk, + cipher := '3des_ede_cbc', + mac := sha}) -> + ?TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA; +suite(#{key_exchange := dhe_psk, + cipher := aes_128_cbc, + mac := sha}) -> + ?TLS_DHE_PSK_WITH_AES_128_CBC_SHA; +suite(#{key_exchange := dhe_psk, + cipher := aes_256_cbc, + mac := sha}) -> + ?TLS_DHE_PSK_WITH_AES_256_CBC_SHA; +suite(#{key_exchange := rsa_psk, + cipher := rc4_128, + mac := sha}) -> + ?TLS_RSA_PSK_WITH_RC4_128_SHA; +suite(#{key_exchange := rsa_psk, + cipher := '3des_ede_cbc', + mac := sha}) -> + ?TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA; +suite(#{key_exchange := rsa_psk, + cipher := aes_128_cbc, + mac := sha}) -> + ?TLS_RSA_PSK_WITH_AES_128_CBC_SHA; +suite(#{key_exchange := rsa_psk, + cipher := aes_256_cbc, + mac := sha}) -> + ?TLS_RSA_PSK_WITH_AES_256_CBC_SHA; +%%% PSK NULL Cipher Suites RFC 4785 +suite(#{key_exchange := psk, + cipher := null, + mac := sha}) -> + ?TLS_PSK_WITH_NULL_SHA; +suite(#{key_exchange := dhe_psk, + cipher := null, + mac := sha}) -> + ?TLS_DHE_PSK_WITH_NULL_SHA; +suite(#{key_exchange := rsa_psk, + cipher := null, + mac := sha}) -> + ?TLS_RSA_PSK_WITH_NULL_SHA; +%%% TLS 1.2 PSK Cipher Suites RFC 5487 +suite(#{key_exchange := psk, + cipher := aes_128_gcm, + mac := aead, + prf := sha256}) -> + ?TLS_PSK_WITH_AES_128_GCM_SHA256; +suite(#{key_exchange := psk, + cipher := aes_256_gcm, + mac := aead, + prf := sha384}) -> + ?TLS_PSK_WITH_AES_256_GCM_SHA384; +suite(#{key_exchange := dhe_psk, + cipher := aes_128_gcm, + mac := aead, + prf := sha256}) -> + ?TLS_DHE_PSK_WITH_AES_128_GCM_SHA256; +suite(#{key_exchange := dhe_psk, + cipher := aes_256_gcm, + mac := aead, + prf := sha384}) -> + ?TLS_DHE_PSK_WITH_AES_256_GCM_SHA384; +suite(#{key_exchange := rsa_psk, + cipher := aes_128_gcm, + mac := aead, + prf := sha256}) -> + ?TLS_RSA_PSK_WITH_AES_128_GCM_SHA256; +suite(#{key_exchange := rsa_psk, + cipher := aes_256_gcm, + mac := aead, + prf := sha384}) -> + ?TLS_RSA_PSK_WITH_AES_256_GCM_SHA384; +suite(#{key_exchange := psk, + cipher := aes_128_cbc, + mac := sha256}) -> + ?TLS_PSK_WITH_AES_128_CBC_SHA256; +suite(#{key_exchange := psk, + cipher := aes_256_cbc, + mac := sha384}) -> + ?TLS_PSK_WITH_AES_256_CBC_SHA384; +suite(#{key_exchange := dhe_psk, + cipher := aes_128_cbc, + mac := sha256}) -> + ?TLS_DHE_PSK_WITH_AES_128_CBC_SHA256; +suite(#{key_exchange := dhe_psk, + cipher := aes_256_cbc, + mac := sha384}) -> + ?TLS_DHE_PSK_WITH_AES_256_CBC_SHA384; +suite(#{key_exchange := rsa_psk, + cipher := aes_128_cbc, + mac := sha256}) -> + ?TLS_RSA_PSK_WITH_AES_128_CBC_SHA256; +suite(#{key_exchange := rsa_psk, + cipher := aes_256_cbc, + mac := sha384}) -> + ?TLS_RSA_PSK_WITH_AES_256_CBC_SHA384; +suite(#{key_exchange := psk, + cipher := null, + mac := sha256}) -> + ?TLS_PSK_WITH_NULL_SHA256; +suite(#{key_exchange := psk, + cipher := null, + mac := sha384}) -> + ?TLS_PSK_WITH_NULL_SHA384; +suite(#{key_exchange := dhe_psk, + cipher := null, + mac := sha256}) -> + ?TLS_DHE_PSK_WITH_NULL_SHA256; +suite(#{key_exchange := dhe_psk, + cipher := null, + mac := sha384}) -> + ?TLS_DHE_PSK_WITH_NULL_SHA384; +suite(#{key_exchange := rsa_psk, + cipher := null, + mac := sha256}) -> + ?TLS_RSA_PSK_WITH_NULL_SHA256; +suite(#{key_exchange := rsa_psk, + cipher := null, + mac := sha384}) -> + ?TLS_RSA_PSK_WITH_NULL_SHA384; +%%% ECDHE PSK Cipher Suites RFC 5489 +suite(#{key_exchange := ecdhe_psk, + cipher := rc4_128, + mac := sha}) -> + ?TLS_ECDHE_PSK_WITH_RC4_128_SHA; +suite(#{key_exchange := ecdhe_psk, + cipher :='3des_ede_cbc', + mac := sha}) -> + ?TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA; +suite(#{key_exchange := ecdhe_psk, + cipher := aes_128_cbc, + mac := sha}) -> + ?TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA; +suite(#{key_exchange := ecdhe_psk, + cipher := aes_256_cbc, + mac := sha}) -> + ?TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA; +suite(#{key_exchange := ecdhe_psk, + cipher := aes_128_cbc, + mac := sha256}) -> + ?TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256; +suite(#{key_exchange := ecdhe_psk, + cipher := aes_256_cbc, + mac := sha384}) -> + ?TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384; +suite(#{key_exchange := ecdhe_psk, + cipher := null, + mac := sha256}) -> + ?TLS_ECDHE_PSK_WITH_NULL_SHA256; +suite(#{key_exchange := ecdhe_psk, + cipher := null, + mac := sha384}) -> + ?TLS_ECDHE_PSK_WITH_NULL_SHA384; +%%% ECDHE_PSK with AES-GCM and AES-CCM Cipher Suites, draft-ietf-tls-ecdhe-psk-aead-05 +suite(#{key_exchange := ecdhe_psk, + cipher := aes_128_gcm, + mac := null, + prf := sha256}) -> + ?TLS_ECDHE_PSK_WITH_AES_128_GCM_SHA256; +suite(#{key_exchange := ecdhe_psk, + cipher := aes_256_gcm, + mac := null, + prf := sha384}) -> + ?TLS_ECDHE_PSK_WITH_AES_256_GCM_SHA384; + %% suite(#{key_exchange := ecdhe_psk, + %% cipher := aes_128_ccm, + %% mac := null, + %% prf := sha256}) -> + %% ?TLS_ECDHE_PSK_WITH_AES_128_CCM_8_SHA256; + %% suite(#{key_exchange := ecdhe_psk, + %% cipher := aes_256_ccm, + %% mac := null, + %% prf := sha256}) -> + %% ?TLS_ECDHE_PSK_WITH_AES_128_CCM_SHA256; +%%% SRP Cipher Suites RFC 5054 +suite(#{key_exchange := srp_anon, + cipher := '3des_ede_cbc', + mac := sha}) -> + ?TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA; +suite(#{key_exchange := srp_rsa, + cipher := '3des_ede_cbc', + mac := sha}) -> + ?TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA; +suite(#{key_exchange := srp_dss, + cipher := '3des_ede_cbc', + mac := sha}) -> + ?TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA; +suite(#{key_exchange := srp_anon, + cipher := aes_128_cbc, + mac := sha}) -> + ?TLS_SRP_SHA_WITH_AES_128_CBC_SHA; +suite(#{key_exchange := srp_rsa, + cipher := aes_128_cbc, + mac := sha}) -> + ?TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA; +suite(#{key_exchange := srp_dss, + cipher := aes_128_cbc, + mac := sha}) -> + ?TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA; +suite(#{key_exchange := srp_anon, + cipher := aes_256_cbc, + mac := sha}) -> + ?TLS_SRP_SHA_WITH_AES_256_CBC_SHA; +suite(#{key_exchange := srp_rsa, + cipher := aes_256_cbc, + mac := sha}) -> + ?TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA; +suite(#{key_exchange := srp_dss, + cipher := aes_256_cbc, + mac := sha}) -> + ?TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA; +%%% RFC 4492 EC TLS suites +suite(#{key_exchange := ecdh_ecdsa, + cipher := null, + mac := sha}) -> + ?TLS_ECDH_ECDSA_WITH_NULL_SHA; +suite(#{key_exchange := ecdh_ecdsa, + cipher := rc4_128, + mac := sha}) -> + ?TLS_ECDH_ECDSA_WITH_RC4_128_SHA; +suite(#{key_exchange := ecdh_ecdsa, + cipher := '3des_ede_cbc', + mac := sha}) -> + ?TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA; +suite(#{key_exchange := ecdh_ecdsa, + cipher := aes_128_cbc, + mac := sha}) -> + ?TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA; +suite(#{key_exchange := ecdh_ecdsa, + cipher := aes_256_cbc, + mac := sha}) -> + ?TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA; +suite(#{key_exchange := ecdhe_ecdsa, + cipher := null, + mac := sha}) -> + ?TLS_ECDHE_ECDSA_WITH_NULL_SHA; +suite(#{key_exchange := ecdhe_ecdsa, + cipher := rc4_128, + mac := sha}) -> + ?TLS_ECDHE_ECDSA_WITH_RC4_128_SHA; +suite(#{key_exchange := ecdhe_ecdsa, + cipher := '3des_ede_cbc', + mac := sha}) -> + ?TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA; +suite(#{key_exchange := ecdhe_ecdsa, + cipher := aes_128_cbc, + mac := sha}) -> + ?TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA; +suite(#{key_exchange := ecdhe_ecdsa, + cipher := aes_256_cbc, + mac := sha}) -> + ?TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA; +suite(#{key_exchange := ecdh_rsa, + cipher := null, + mac := sha}) -> + ?TLS_ECDH_RSA_WITH_NULL_SHA; +suite(#{key_exchange := ecdh_rsa, + cipher := rc4_128, + mac := sha}) -> + ?TLS_ECDH_RSA_WITH_RC4_128_SHA; +suite(#{key_exchange := ecdh_rsa, + cipher := '3des_ede_cbc', mac := sha}) -> + ?TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA; +suite(#{key_exchange := ecdh_rsa, + cipher := aes_128_cbc, + mac := sha}) -> + ?TLS_ECDH_RSA_WITH_AES_128_CBC_SHA; +suite(#{key_exchange := ecdh_rsa, + cipher := aes_256_cbc, + mac := sha}) -> + ?TLS_ECDH_RSA_WITH_AES_256_CBC_SHA; +suite(#{key_exchange := ecdhe_rsa, + cipher := null, + mac := sha}) -> + ?TLS_ECDHE_RSA_WITH_NULL_SHA; +suite(#{key_exchange := ecdhe_rsa, + cipher := rc4_128, + mac := sha}) -> + ?TLS_ECDHE_RSA_WITH_RC4_128_SHA; +suite(#{key_exchange := ecdhe_rsa, + cipher := '3des_ede_cbc', + mac := sha}) -> + ?TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA; +suite(#{key_exchange := ecdhe_rsa, + cipher := aes_128_cbc, + mac := sha}) -> + ?TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA; +suite(#{key_exchange := ecdhe_rsa, + cipher := aes_256_cbc, + mac := sha}) -> + ?TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA; +suite(#{key_exchange := ecdh_anon, + cipher := null, + mac := sha}) -> + ?TLS_ECDH_anon_WITH_NULL_SHA; +suite(#{key_exchange := ecdh_anon, + cipher := rc4_128, + mac := sha}) -> + ?TLS_ECDH_anon_WITH_RC4_128_SHA; +suite(#{key_exchange := ecdh_anon, + cipher := '3des_ede_cbc', + mac := sha}) -> + ?TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA; +suite(#{key_exchange := ecdh_anon, + cipher := aes_128_cbc, + mac := sha}) -> + ?TLS_ECDH_anon_WITH_AES_128_CBC_SHA; +suite(#{key_exchange := ecdh_anon, + cipher := aes_256_cbc, + mac := sha}) -> + ?TLS_ECDH_anon_WITH_AES_256_CBC_SHA; +%%% RFC 5289 EC TLS suites +suite(#{key_exchange := ecdhe_ecdsa, + cipher := aes_128_cbc, + mac:= sha256, + prf := sha256}) -> + ?TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256; +suite(#{key_exchange := ecdhe_ecdsa, + cipher := aes_256_cbc, + mac := sha384, + prf := sha384}) -> + ?TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384; +suite(#{key_exchange := ecdh_ecdsa, + cipher := aes_128_cbc, + mac := sha256, + prf := sha256}) -> + ?TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256; +suite(#{key_exchange := ecdh_ecdsa, + cipher := aes_256_cbc, + mac := sha384, + prf := sha384}) -> + ?TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384; +suite(#{key_exchange := ecdhe_rsa, + cipher := aes_128_cbc, + mac := sha256, + prf := sha256}) -> + ?TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256; +suite(#{key_exchange := ecdhe_rsa, + cipher := aes_256_cbc, + mac := sha384, + prf := sha384}) -> + ?TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384; +suite(#{key_exchange := ecdh_rsa, + cipher := aes_128_cbc, + mac := sha256, + prf := sha256}) -> + ?TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256; +suite(#{key_exchange := ecdh_rsa, + cipher := aes_256_cbc, + mac := sha384, + prf := sha384}) -> + ?TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384; +%% RFC 5288 AES-GCM Cipher Suites +suite(#{key_exchange := rsa, + cipher := aes_128_gcm, + mac := aead, + prf := sha256}) -> + ?TLS_RSA_WITH_AES_128_GCM_SHA256; +suite(#{key_exchange := rsa, + cipher := aes_256_gcm, + mac := aead, + prf := sha384}) -> + ?TLS_RSA_WITH_AES_256_GCM_SHA384; +suite(#{key_exchange := dhe_rsa, + cipher := aes_128_gcm, + mac := aead, + prf := sha256}) -> + ?TLS_DHE_RSA_WITH_AES_128_GCM_SHA256; +suite(#{key_exchange := dhe_rsa, + cipher := aes_256_gcm, + mac := aead, + prf := sha384}) -> + ?TLS_DHE_RSA_WITH_AES_256_GCM_SHA384; +suite(#{key_exchange := dh_rsa, + cipher := aes_128_gcm, + mac := aead, + prf := sha256}) -> + ?TLS_DH_RSA_WITH_AES_128_GCM_SHA256; +suite(#{key_exchange := dh_rsa, + cipher := aes_256_gcm, + mac := aead, + prf := sha384}) -> + ?TLS_DH_RSA_WITH_AES_256_GCM_SHA384; +suite(#{key_exchange := dhe_dss, + cipher := aes_128_gcm, + mac := aead, + prf := sha256}) -> + ?TLS_DHE_DSS_WITH_AES_128_GCM_SHA256; +suite(#{key_exchange := dhe_dss, + cipher := aes_256_gcm, + mac := aead, + prf := sha384}) -> + ?TLS_DHE_DSS_WITH_AES_256_GCM_SHA384; +suite(#{key_exchange := dh_dss, + cipher := aes_128_gcm, + mac := aead, + prf := sha256}) -> + ?TLS_DH_DSS_WITH_AES_128_GCM_SHA256; +suite(#{key_exchange := dh_dss, + cipher := aes_256_gcm, + mac := aead, + prf := sha384}) -> + ?TLS_DH_DSS_WITH_AES_256_GCM_SHA384; +suite(#{key_exchange := dh_anon, + cipher := aes_128_gcm, + mac := aead, + prf := sha256}) -> + ?TLS_DH_anon_WITH_AES_128_GCM_SHA256; +suite(#{key_exchange := dh_anon, + cipher := aes_256_gcm, + mac := aead, + prf := sha384}) -> + ?TLS_DH_anon_WITH_AES_256_GCM_SHA384; +%% RFC 5289 ECC AES-GCM Cipher Suites +suite(#{key_exchange := ecdhe_ecdsa, + cipher := aes_128_gcm, + mac := aead, + prf := sha256}) -> + ?TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256; +suite(#{key_exchange := ecdhe_ecdsa, + cipher := aes_256_gcm, + mac := aead, + prf := sha384}) -> + ?TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384; +suite(#{key_exchange := ecdh_ecdsa, + cipher := aes_128_gcm, + mac := aead, + prf := sha256}) -> + ?TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256; +suite(#{key_exchange := ecdh_ecdsa, + cipher := aes_256_gcm, + mac := aead, + prf := sha384}) -> + ?TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384; +suite(#{key_exchange := ecdhe_rsa, + cipher := aes_128_gcm, + mac := aead, + prf := sha256}) -> + ?TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256; +suite(#{key_exchange := ecdhe_rsa, + cipher := aes_256_gcm, + mac := aead, + prf := sha384}) -> + ?TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384; +suite(#{key_exchange := ecdh_rsa, + cipher := aes_128_gcm, + mac := aead, + prf := sha256}) -> + ?TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256; +suite(#{key_exchange := ecdh_rsa, + cipher := aes_256_gcm, + mac := aead, + prf := sha384}) -> + ?TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384; +%% draft-agl-tls-chacha20poly1305-04 Chacha20/Poly1305 Suites +suite(#{key_exchange := ecdhe_rsa, + cipher := chacha20_poly1305, + mac := aead, + prf := sha256}) -> + ?TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256; +suite(#{key_exchange := ecdhe_ecdsa, + cipher := chacha20_poly1305, + mac := aead, + prf := sha256}) -> + ?TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256; +suite(#{key_exchange := dhe_rsa, + cipher := chacha20_poly1305, + mac := aead, + prf := sha256}) -> + ?TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256. + +%%-------------------------------------------------------------------- +-spec openssl_suite(openssl_cipher_suite()) -> cipher_suite(). +%% +%% Description: Return TLS cipher suite definition. +%%-------------------------------------------------------------------- +%% translate constants <-> openssl-strings +openssl_suite("DHE-RSA-AES256-SHA256") -> + ?TLS_DHE_RSA_WITH_AES_256_CBC_SHA256; +openssl_suite("DHE-DSS-AES256-SHA256") -> + ?TLS_DHE_DSS_WITH_AES_256_CBC_SHA256; +openssl_suite("AES256-SHA256") -> + ?TLS_RSA_WITH_AES_256_CBC_SHA256; +openssl_suite("DHE-RSA-AES128-SHA256") -> + ?TLS_DHE_RSA_WITH_AES_128_CBC_SHA256; +openssl_suite("DHE-DSS-AES128-SHA256") -> + ?TLS_DHE_DSS_WITH_AES_128_CBC_SHA256; +openssl_suite("AES128-SHA256") -> + ?TLS_RSA_WITH_AES_128_CBC_SHA256; +openssl_suite("DHE-RSA-AES256-SHA") -> + ?TLS_DHE_RSA_WITH_AES_256_CBC_SHA; +openssl_suite("DHE-DSS-AES256-SHA") -> + ?TLS_DHE_DSS_WITH_AES_256_CBC_SHA; +openssl_suite("AES256-SHA") -> + ?TLS_RSA_WITH_AES_256_CBC_SHA; +openssl_suite("EDH-RSA-DES-CBC3-SHA") -> + ?TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA; +openssl_suite("EDH-DSS-DES-CBC3-SHA") -> + ?TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA; +openssl_suite("DES-CBC3-SHA") -> + ?TLS_RSA_WITH_3DES_EDE_CBC_SHA; +openssl_suite("DHE-RSA-AES128-SHA") -> + ?TLS_DHE_RSA_WITH_AES_128_CBC_SHA; +openssl_suite("DHE-DSS-AES128-SHA") -> + ?TLS_DHE_DSS_WITH_AES_128_CBC_SHA; +openssl_suite("AES128-SHA") -> + ?TLS_RSA_WITH_AES_128_CBC_SHA; +openssl_suite("RC4-SHA") -> + ?TLS_RSA_WITH_RC4_128_SHA; +openssl_suite("RC4-MD5") -> + ?TLS_RSA_WITH_RC4_128_MD5; +openssl_suite("EDH-RSA-DES-CBC-SHA") -> + ?TLS_DHE_RSA_WITH_DES_CBC_SHA; +openssl_suite("DES-CBC-SHA") -> + ?TLS_RSA_WITH_DES_CBC_SHA; + +%%% SRP Cipher Suites RFC 5054 + +openssl_suite("SRP-DSS-AES-256-CBC-SHA") -> + ?TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA; +openssl_suite("SRP-RSA-AES-256-CBC-SHA") -> + ?TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA; +openssl_suite("SRP-DSS-3DES-EDE-CBC-SHA") -> + ?TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA; +openssl_suite("SRP-RSA-3DES-EDE-CBC-SHA") -> + ?TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA; +openssl_suite("SRP-DSS-AES-128-CBC-SHA") -> + ?TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA; +openssl_suite("SRP-RSA-AES-128-CBC-SHA") -> + ?TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA; + +%% RFC 4492 EC TLS suites +openssl_suite("ECDH-ECDSA-RC4-SHA") -> + ?TLS_ECDH_ECDSA_WITH_RC4_128_SHA; +openssl_suite("ECDH-ECDSA-DES-CBC3-SHA") -> + ?TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA; +openssl_suite("ECDH-ECDSA-AES128-SHA") -> + ?TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA; +openssl_suite("ECDH-ECDSA-AES256-SHA") -> + ?TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA; + +openssl_suite("ECDHE-ECDSA-RC4-SHA") -> + ?TLS_ECDHE_ECDSA_WITH_RC4_128_SHA; +openssl_suite("ECDHE-ECDSA-DES-CBC3-SHA") -> + ?TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA; +openssl_suite("ECDHE-ECDSA-AES128-SHA") -> + ?TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA; +openssl_suite("ECDHE-ECDSA-AES256-SHA") -> + ?TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA; + +openssl_suite("ECDHE-RSA-RC4-SHA") -> + ?TLS_ECDHE_RSA_WITH_RC4_128_SHA; +openssl_suite("ECDHE-RSA-DES-CBC3-SHA") -> + ?TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA; +openssl_suite("ECDHE-RSA-AES128-SHA") -> + ?TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA; +openssl_suite("ECDHE-RSA-AES256-SHA") -> + ?TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA; + +openssl_suite("ECDH-RSA-RC4-SHA") -> + ?TLS_ECDH_RSA_WITH_RC4_128_SHA; +openssl_suite("ECDH-RSA-DES-CBC3-SHA") -> + ?TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA; +openssl_suite("ECDH-RSA-AES128-SHA") -> + ?TLS_ECDH_RSA_WITH_AES_128_CBC_SHA; +openssl_suite("ECDH-RSA-AES256-SHA") -> + ?TLS_ECDH_RSA_WITH_AES_256_CBC_SHA; + +%% RFC 5289 EC TLS suites +openssl_suite("ECDHE-ECDSA-AES128-SHA256") -> + ?TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256; +openssl_suite("ECDHE-ECDSA-AES256-SHA384") -> + ?TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384; +openssl_suite("ECDH-ECDSA-AES128-SHA256") -> + ?TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256; +openssl_suite("ECDH-ECDSA-AES256-SHA384") -> + ?TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384; +openssl_suite("ECDHE-RSA-AES128-SHA256") -> + ?TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256; +openssl_suite("ECDHE-RSA-AES256-SHA384") -> + ?TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384; +openssl_suite("ECDH-RSA-AES128-SHA256") -> + ?TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256; +openssl_suite("ECDH-RSA-AES256-SHA384") -> + ?TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384; + +%% RFC 5288 AES-GCM Cipher Suites +openssl_suite("AES128-GCM-SHA256") -> + ?TLS_RSA_WITH_AES_128_GCM_SHA256; +openssl_suite("AES256-GCM-SHA384") -> + ?TLS_RSA_WITH_AES_256_GCM_SHA384; +openssl_suite("DHE-RSA-AES128-GCM-SHA256") -> + ?TLS_DHE_RSA_WITH_AES_128_GCM_SHA256; +openssl_suite("DHE-RSA-AES256-GCM-SHA384") -> + ?TLS_DHE_RSA_WITH_AES_256_GCM_SHA384; +openssl_suite("DH-RSA-AES128-GCM-SHA256") -> + ?TLS_DH_RSA_WITH_AES_128_GCM_SHA256; +openssl_suite("DH-RSA-AES256-GCM-SHA384") -> + ?TLS_DH_RSA_WITH_AES_256_GCM_SHA384; +openssl_suite("DHE-DSS-AES128-GCM-SHA256") -> + ?TLS_DHE_DSS_WITH_AES_128_GCM_SHA256; +openssl_suite("DHE-DSS-AES256-GCM-SHA384") -> + ?TLS_DHE_DSS_WITH_AES_256_GCM_SHA384; +openssl_suite("DH-DSS-AES128-GCM-SHA256") -> + ?TLS_DH_DSS_WITH_AES_128_GCM_SHA256; +openssl_suite("DH-DSS-AES256-GCM-SHA384") -> + ?TLS_DH_DSS_WITH_AES_256_GCM_SHA384; + +%% RFC 5289 ECC AES-GCM Cipher Suites +openssl_suite("ECDHE-ECDSA-AES128-GCM-SHA256") -> + ?TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256; +openssl_suite("ECDHE-ECDSA-AES256-GCM-SHA384") -> + ?TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384; +openssl_suite("ECDH-ECDSA-AES128-GCM-SHA256") -> + ?TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256; +openssl_suite("ECDH-ECDSA-AES256-GCM-SHA384") -> + ?TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384; +openssl_suite("ECDHE-RSA-AES128-GCM-SHA256") -> + ?TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256; +openssl_suite("ECDHE-RSA-AES256-GCM-SHA384") -> + ?TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384; +openssl_suite("ECDH-RSA-AES128-GCM-SHA256") -> + ?TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256; +openssl_suite("ECDH-RSA-AES256-GCM-SHA384") -> + ?TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384. + +%%-------------------------------------------------------------------- +-spec openssl_suite_name(cipher_suite()) -> openssl_cipher_suite() | erl_cipher_suite(). +%% +%% Description: Return openssl cipher suite name if possible +%%------------------------------------------------------------------- +openssl_suite_name(?TLS_DHE_RSA_WITH_AES_256_CBC_SHA) -> + "DHE-RSA-AES256-SHA"; +openssl_suite_name(?TLS_DHE_DSS_WITH_AES_256_CBC_SHA) -> + "DHE-DSS-AES256-SHA"; +openssl_suite_name(?TLS_RSA_WITH_AES_256_CBC_SHA) -> + "AES256-SHA"; +openssl_suite_name(?TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA) -> + "EDH-RSA-DES-CBC3-SHA"; +openssl_suite_name(?TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA) -> + "EDH-DSS-DES-CBC3-SHA"; +openssl_suite_name(?TLS_RSA_WITH_3DES_EDE_CBC_SHA) -> + "DES-CBC3-SHA"; +openssl_suite_name( ?TLS_DHE_RSA_WITH_AES_128_CBC_SHA) -> + "DHE-RSA-AES128-SHA"; +openssl_suite_name(?TLS_DHE_DSS_WITH_AES_128_CBC_SHA) -> + "DHE-DSS-AES128-SHA"; +openssl_suite_name(?TLS_RSA_WITH_AES_128_CBC_SHA) -> + "AES128-SHA"; +openssl_suite_name(?TLS_RSA_WITH_RC4_128_SHA) -> + "RC4-SHA"; +openssl_suite_name(?TLS_RSA_WITH_RC4_128_MD5) -> + "RC4-MD5"; +openssl_suite_name(?TLS_DHE_RSA_WITH_DES_CBC_SHA) -> + "EDH-RSA-DES-CBC-SHA"; +openssl_suite_name(?TLS_RSA_WITH_DES_CBC_SHA) -> + "DES-CBC-SHA"; +openssl_suite_name(?TLS_RSA_WITH_NULL_SHA256) -> + "NULL-SHA256"; +openssl_suite_name(?TLS_RSA_WITH_AES_128_CBC_SHA256) -> + "AES128-SHA256"; +openssl_suite_name(?TLS_RSA_WITH_AES_256_CBC_SHA256) -> + "AES256-SHA256"; +openssl_suite_name(?TLS_DH_DSS_WITH_AES_128_CBC_SHA256) -> + "DH-DSS-AES128-SHA256"; +openssl_suite_name(?TLS_DH_RSA_WITH_AES_128_CBC_SHA256) -> + "DH-RSA-AES128-SHA256"; +openssl_suite_name(?TLS_DHE_DSS_WITH_AES_128_CBC_SHA256) -> + "DHE-DSS-AES128-SHA256"; +openssl_suite_name(?TLS_DHE_RSA_WITH_AES_128_CBC_SHA256) -> + "DHE-RSA-AES128-SHA256"; +openssl_suite_name(?TLS_DH_DSS_WITH_AES_256_CBC_SHA256) -> + "DH-DSS-AES256-SHA256"; +openssl_suite_name(?TLS_DH_RSA_WITH_AES_256_CBC_SHA256) -> + "DH-RSA-AES256-SHA256"; +openssl_suite_name(?TLS_DHE_DSS_WITH_AES_256_CBC_SHA256) -> + "DHE-DSS-AES256-SHA256"; +openssl_suite_name(?TLS_DHE_RSA_WITH_AES_256_CBC_SHA256) -> + "DHE-RSA-AES256-SHA256"; + +%%% PSK Cipher Suites RFC 4279 + +openssl_suite_name(?TLS_PSK_WITH_AES_256_CBC_SHA) -> + "PSK-AES256-CBC-SHA"; +openssl_suite_name(?TLS_PSK_WITH_3DES_EDE_CBC_SHA) -> + "PSK-3DES-EDE-CBC-SHA"; +openssl_suite_name(?TLS_PSK_WITH_AES_128_CBC_SHA) -> + "PSK-AES128-CBC-SHA"; +openssl_suite_name(?TLS_PSK_WITH_RC4_128_SHA) -> + "PSK-RC4-SHA"; + +%%% SRP Cipher Suites RFC 5054 + +openssl_suite_name(?TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA) -> + "SRP-RSA-3DES-EDE-CBC-SHA"; +openssl_suite_name(?TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA) -> + "SRP-DSS-3DES-EDE-CBC-SHA"; +openssl_suite_name(?TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA) -> + "SRP-RSA-AES-128-CBC-SHA"; +openssl_suite_name(?TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA) -> + "SRP-DSS-AES-128-CBC-SHA"; +openssl_suite_name(?TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA) -> + "SRP-RSA-AES-256-CBC-SHA"; +openssl_suite_name(?TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA) -> + "SRP-DSS-AES-256-CBC-SHA"; + +%% RFC 4492 EC TLS suites +openssl_suite_name(?TLS_ECDH_ECDSA_WITH_RC4_128_SHA) -> + "ECDH-ECDSA-RC4-SHA"; +openssl_suite_name(?TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA) -> + "ECDH-ECDSA-DES-CBC3-SHA"; +openssl_suite_name(?TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA) -> + "ECDH-ECDSA-AES128-SHA"; +openssl_suite_name(?TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA) -> + "ECDH-ECDSA-AES256-SHA"; + +openssl_suite_name(?TLS_ECDHE_ECDSA_WITH_RC4_128_SHA) -> + "ECDHE-ECDSA-RC4-SHA"; +openssl_suite_name(?TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA) -> + "ECDHE-ECDSA-DES-CBC3-SHA"; +openssl_suite_name(?TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA) -> + "ECDHE-ECDSA-AES128-SHA"; +openssl_suite_name(?TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA) -> + "ECDHE-ECDSA-AES256-SHA"; + +openssl_suite_name(?TLS_ECDH_RSA_WITH_RC4_128_SHA) -> + "ECDH-RSA-RC4-SHA"; +openssl_suite_name(?TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA) -> + "ECDH-RSA-DES-CBC3-SHA"; +openssl_suite_name(?TLS_ECDH_RSA_WITH_AES_128_CBC_SHA) -> + "ECDH-RSA-AES128-SHA"; +openssl_suite_name(?TLS_ECDH_RSA_WITH_AES_256_CBC_SHA) -> + "ECDH-RSA-AES256-SHA"; + +openssl_suite_name(?TLS_ECDHE_RSA_WITH_RC4_128_SHA) -> + "ECDHE-RSA-RC4-SHA"; +openssl_suite_name(?TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA) -> + "ECDHE-RSA-DES-CBC3-SHA"; +openssl_suite_name(?TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA) -> + "ECDHE-RSA-AES128-SHA"; +openssl_suite_name(?TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA) -> + "ECDHE-RSA-AES256-SHA"; + +%% RFC 5289 EC TLS suites +openssl_suite_name(?TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256) -> + "ECDHE-ECDSA-AES128-SHA256"; +openssl_suite_name(?TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384) -> + "ECDHE-ECDSA-AES256-SHA384"; +openssl_suite_name(?TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256) -> + "ECDH-ECDSA-AES128-SHA256"; +openssl_suite_name(?TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384) -> + "ECDH-ECDSA-AES256-SHA384"; +openssl_suite_name(?TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256) -> + "ECDHE-RSA-AES128-SHA256"; +openssl_suite_name(?TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384) -> + "ECDHE-RSA-AES256-SHA384"; +openssl_suite_name(?TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256) -> + "ECDH-RSA-AES128-SHA256"; +openssl_suite_name(?TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384) -> + "ECDH-RSA-AES256-SHA384"; + +%% RFC 5288 AES-GCM Cipher Suites +openssl_suite_name(?TLS_RSA_WITH_AES_128_GCM_SHA256) -> + "AES128-GCM-SHA256"; +openssl_suite_name(?TLS_RSA_WITH_AES_256_GCM_SHA384) -> + "AES256-GCM-SHA384"; +openssl_suite_name(?TLS_DHE_RSA_WITH_AES_128_GCM_SHA256) -> + "DHE-RSA-AES128-GCM-SHA256"; +openssl_suite_name(?TLS_DHE_RSA_WITH_AES_256_GCM_SHA384) -> + "DHE-RSA-AES256-GCM-SHA384"; +openssl_suite_name(?TLS_DH_RSA_WITH_AES_128_GCM_SHA256) -> + "DH-RSA-AES128-GCM-SHA256"; +openssl_suite_name(?TLS_DH_RSA_WITH_AES_256_GCM_SHA384) -> + "DH-RSA-AES256-GCM-SHA384"; +openssl_suite_name(?TLS_DHE_DSS_WITH_AES_128_GCM_SHA256) -> + "DHE-DSS-AES128-GCM-SHA256"; +openssl_suite_name(?TLS_DHE_DSS_WITH_AES_256_GCM_SHA384) -> + "DHE-DSS-AES256-GCM-SHA384"; +openssl_suite_name(?TLS_DH_DSS_WITH_AES_128_GCM_SHA256) -> + "DH-DSS-AES128-GCM-SHA256"; +openssl_suite_name(?TLS_DH_DSS_WITH_AES_256_GCM_SHA384) -> + "DH-DSS-AES256-GCM-SHA384"; + +%% RFC 5289 ECC AES-GCM Cipher Suites +openssl_suite_name(?TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256) -> + "ECDHE-ECDSA-AES128-GCM-SHA256"; +openssl_suite_name(?TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384) -> + "ECDHE-ECDSA-AES256-GCM-SHA384"; +openssl_suite_name(?TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256) -> + "ECDH-ECDSA-AES128-GCM-SHA256"; +openssl_suite_name(?TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384) -> + "ECDH-ECDSA-AES256-GCM-SHA384"; +openssl_suite_name(?TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) -> + "ECDHE-RSA-AES128-GCM-SHA256"; +openssl_suite_name(?TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) -> + "ECDHE-RSA-AES256-GCM-SHA384"; +openssl_suite_name(?TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256) -> + "ECDH-RSA-AES128-GCM-SHA256"; +openssl_suite_name(?TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384) -> + "ECDH-RSA-AES256-GCM-SHA384"; + +%% No oppenssl name +openssl_suite_name(Cipher) -> + suite_definition(Cipher). diff --git a/lib/ssl/src/ssl_config.erl b/lib/ssl/src/ssl_config.erl index 545b8aa0f6..1e6dab9276 100644 --- a/lib/ssl/src/ssl_config.erl +++ b/lib/ssl/src/ssl_config.erl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2013. All Rights Reserved. +%% Copyright Ericsson AB 2007-2018. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. +%% 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 %% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% 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% %% @@ -31,24 +32,28 @@ init(SslOpts, Role) -> init_manager_name(SslOpts#ssl_options.erl_dist), - {ok, CertDbRef, CertDbHandle, FileRefHandle, PemCacheHandle, CacheHandle, OwnCert} + {ok, #{pem_cache := PemCache} = Config} = init_certificates(SslOpts, Role), PrivateKey = - init_private_key(PemCacheHandle, SslOpts#ssl_options.key, SslOpts#ssl_options.keyfile, + init_private_key(PemCache, SslOpts#ssl_options.key, SslOpts#ssl_options.keyfile, SslOpts#ssl_options.password, Role), - DHParams = init_diffie_hellman(PemCacheHandle, SslOpts#ssl_options.dh, SslOpts#ssl_options.dhfile, Role), - {ok, CertDbRef, CertDbHandle, FileRefHandle, CacheHandle, OwnCert, PrivateKey, DHParams}. + DHParams = init_diffie_hellman(PemCache, SslOpts#ssl_options.dh, SslOpts#ssl_options.dhfile, Role), + {ok, Config#{private_key => PrivateKey, dh_params => DHParams}}. init_manager_name(false) -> - put(ssl_manager, ssl_manager:manager_name(normal)); + put(ssl_manager, ssl_manager:name(normal)), + put(ssl_pem_cache, ssl_pem_cache:name(normal)); init_manager_name(true) -> - put(ssl_manager, ssl_manager:manager_name(dist)). + put(ssl_manager, ssl_manager:name(dist)), + put(ssl_pem_cache, ssl_pem_cache:name(dist)). init_certificates(#ssl_options{cacerts = CaCerts, cacertfile = CACertFile, - certfile = CertFile, - cert = Cert}, Role) -> - {ok, CertDbRef, CertDbHandle, FileRefHandle, PemCacheHandle, CacheHandle} = + certfile = CertFile, + cert = Cert, + crl_cache = CRLCache + }, Role) -> + {ok, Config} = try Certs = case CaCerts of undefined -> @@ -56,40 +61,45 @@ init_certificates(#ssl_options{cacerts = CaCerts, _ -> {der, CaCerts} end, - {ok, _, _, _, _, _} = ssl_manager:connection_init(Certs, Role) + {ok,_} = ssl_manager:connection_init(Certs, Role, CRLCache) catch _:Reason -> file_error(CACertFile, {cacertfile, Reason}) end, - init_certificates(Cert, CertDbRef, CertDbHandle, FileRefHandle, PemCacheHandle, - CacheHandle, CertFile, Role). + init_certificates(Cert, Config, CertFile, Role). -init_certificates(undefined, CertDbRef, CertDbHandle, FileRefHandle, PemCacheHandle, CacheHandle, <<>>, _) -> - {ok, CertDbRef, CertDbHandle, FileRefHandle, PemCacheHandle, CacheHandle, undefined}; +init_certificates(undefined, Config, <<>>, _) -> + {ok, Config#{own_certificate => undefined}}; -init_certificates(undefined, CertDbRef, CertDbHandle, FileRefHandle, PemCacheHandle, - CacheHandle, CertFile, client) -> +init_certificates(undefined, #{pem_cache := PemCache} = Config, CertFile, client) -> try %% Ignoring potential proxy-certificates see: %% http://dev.globus.org/wiki/Security/ProxyFileFormat - [OwnCert|_] = ssl_certificate:file_to_certificats(CertFile, PemCacheHandle), - {ok, CertDbRef, CertDbHandle, FileRefHandle, PemCacheHandle, CacheHandle, OwnCert} + [OwnCert|_] = ssl_certificate:file_to_certificats(CertFile, PemCache), + {ok, Config#{own_certificate => OwnCert}} catch _Error:_Reason -> - {ok, CertDbRef, CertDbHandle, FileRefHandle, PemCacheHandle, CacheHandle, undefined} - end; + {ok, Config#{own_certificate => undefined}} + end; -init_certificates(undefined, CertDbRef, CertDbHandle, FileRefHandle, - PemCacheHandle, CacheRef, CertFile, server) -> +init_certificates(undefined, #{pem_cache := PemCache} = Config, CertFile, server) -> try - [OwnCert|_] = ssl_certificate:file_to_certificats(CertFile, PemCacheHandle), - {ok, CertDbRef, CertDbHandle, FileRefHandle, PemCacheHandle, CacheRef, OwnCert} + [OwnCert|_] = ssl_certificate:file_to_certificats(CertFile, PemCache), + {ok, Config#{own_certificate => OwnCert}} catch _:Reason -> file_error(CertFile, {certfile, Reason}) end; -init_certificates(Cert, CertDbRef, CertDbHandle, FileRefHandle, PemCacheHandle, CacheRef, _, _) -> - {ok, CertDbRef, CertDbHandle, FileRefHandle, PemCacheHandle, CacheRef, Cert}. - +init_certificates(Cert, Config, _, _) -> + {ok, Config#{own_certificate => Cert}}. +init_private_key(_, #{algorithm := Alg} = Key, _, _Password, _Client) when Alg == ecdsa; + Alg == rsa; + Alg == dss -> + case maps:is_key(engine, Key) andalso maps:is_key(key_id, Key) of + true -> + Key; + false -> + throw({key, {invalid_key_id, Key}}) + end; init_private_key(_, undefined, <<>>, _Password, _Client) -> undefined; init_private_key(DbHandle, undefined, KeyFile, Password, _) -> @@ -122,7 +132,13 @@ private_key(#'PrivateKeyInfo'{privateKeyAlgorithm = #'PrivateKeyInfo_privateKeyAlgorithm'{algorithm = ?'id-dsa'}, privateKey = Key}) -> public_key:der_decode('DSAPrivateKey', iolist_to_binary(Key)); - +private_key(#'PrivateKeyInfo'{privateKeyAlgorithm = + #'PrivateKeyInfo_privateKeyAlgorithm'{algorithm = ?'id-ecPublicKey', + parameters = {asn1_OPENTYPE, Parameters}}, + privateKey = Key}) -> + ECKey = public_key:der_decode('ECPrivateKey', iolist_to_binary(Key)), + ECParameters = public_key:der_decode('EcpkParameters', Parameters), + ECKey#'ECPrivateKey'{parameters = ECParameters}; private_key(Key) -> Key. @@ -131,6 +147,8 @@ file_error(File, Throw) -> case Throw of {Opt,{badmatch, {error, {badmatch, Error}}}} -> throw({options, {Opt, binary_to_list(File), Error}}); + {Opt, {badmatch, Error}} -> + throw({options, {Opt, binary_to_list(File), Error}}); _ -> throw(Throw) end. diff --git a/lib/ssl/src/ssl_connection.erl b/lib/ssl/src/ssl_connection.erl index b6059eac58..e8c8120f50 100644 --- a/lib/ssl/src/ssl_connection.erl +++ b/lib/ssl/src/ssl_connection.erl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2013-2014. All Rights Reserved. +%% Copyright Ericsson AB 2013-2018. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. +%% 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 %% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% 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% %% @@ -36,29 +37,48 @@ -include_lib("public_key/include/public_key.hrl"). %% Setup --export([connect/8, ssl_accept/7, handshake/2, handshake/3, - socket_control/4, socket_control/5]). + +-export([connect/8, handshake/7, handshake/2, handshake/3, + handshake_continue/3, handshake_cancel/1, + socket_control/4, socket_control/5, start_or_recv_cancel_timer/2]). %% User Events --export([send/2, recv/3, close/1, shutdown/2, - new_user/2, get_opts/2, set_opts/2, info/1, session_info/1, - peer_certificate/1, renegotiation/1, negotiated_next_protocol/1, prf/5 +-export([send/2, recv/3, close/2, shutdown/2, + new_user/2, get_opts/2, set_opts/2, + peer_certificate/1, renegotiation/1, negotiated_protocol/1, prf/5, + connection_information/2 ]). --export([handle_session/6]). +%% Alert and close handling +-export([handle_own_alert/4, handle_alert/3, + handle_normal_shutdown/3, + handle_trusted_certs_db/1]). + +%% Data handling +-export([read_application_data/2, internal_renegotiation/2]). + +%% Help functions for tls|dtls_connection.erl +-export([handle_session/7, ssl_config/3, + prepare_connection/2, hibernate_after/3, map_extensions/1]). + +%% 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, error/4, hello/4, user_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]). +%% Erlang Distribution export +-export([get_sslsocket/1, dist_handshake_complete/2]). %%==================================================================== -%% Internal application API -%%==================================================================== +%% Setup +%%==================================================================== %%-------------------------------------------------------------------- -spec connect(tls_connection | dtls_connection, - host(), inet:port_number(), port(), + host(), inet:port_number(), + port() | {tuple(), port()}, %% TLS | DTLS {#ssl_options{}, #socket_options{}, %% Tracker only needed on server side undefined}, @@ -75,7 +95,7 @@ connect(Connection, Host, Port, Socket, Options, User, CbInfo, Timeout) -> {error, ssl_not_started} end. %%-------------------------------------------------------------------- --spec ssl_accept(tls_connection | dtls_connection, +-spec handshake(tls_connection | dtls_connection, inet:port_number(), port(), {#ssl_options{}, #socket_options{}, undefined | pid()}, pid(), tuple(), timeout()) -> @@ -84,7 +104,7 @@ connect(Connection, Host, Port, Socket, Options, User, CbInfo, Timeout) -> %% Description: Performs accept on an ssl listen socket. e.i. performs %% ssl handshake. %%-------------------------------------------------------------------- -ssl_accept(Connection, Port, Socket, Opts, User, CbInfo, Timeout) -> +handshake(Connection, Port, Socket, Opts, User, CbInfo, Timeout) -> try Connection:start_fsm(server, "localhost", Port, Socket, Opts, User, CbInfo, Timeout) catch @@ -93,34 +113,62 @@ ssl_accept(Connection, Port, Socket, Opts, User, CbInfo, Timeout) -> end. %%-------------------------------------------------------------------- --spec handshake(#sslsocket{}, timeout()) -> ok | {error, reason()}. +-spec handshake(#sslsocket{}, timeout()) -> {ok, #sslsocket{}} | + {ok, #sslsocket{}, map()}| {error, reason()}. %% %% Description: Starts ssl handshake. %%-------------------------------------------------------------------- -handshake(#sslsocket{pid = Pid}, Timeout) -> - case sync_send_all_state_event(Pid, {start, Timeout}) of +handshake(#sslsocket{pid = [Pid|_]} = Socket, Timeout) -> + case call(Pid, {start, Timeout}) of connected -> - ok; + {ok, Socket}; + {ok, Ext} -> + {ok, Socket, Ext}; Error -> Error end. %%-------------------------------------------------------------------- -spec handshake(#sslsocket{}, {#ssl_options{},#socket_options{}}, - timeout()) -> ok | {error, reason()}. + timeout()) -> {ok, #sslsocket{}} | {error, reason()}. %% %% 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 +handshake(#sslsocket{pid = [Pid|_]} = Socket, SslOptions, Timeout) -> + case call(Pid, {start, SslOptions, Timeout}) of connected -> - ok; + {ok, Socket}; Error -> Error end. +%%-------------------------------------------------------------------- +-spec handshake_continue(#sslsocket{}, [ssl_option()], + timeout()) -> {ok, #sslsocket{}}| {error, reason()}. +%% +%% Description: Continues handshake with new options +%%-------------------------------------------------------------------- +handshake_continue(#sslsocket{pid = [Pid|_]} = Socket, SslOptions, Timeout) -> + case call(Pid, {handshake_continue, SslOptions, Timeout}) of + connected -> + {ok, Socket}; + Error -> + Error + end. +%%-------------------------------------------------------------------- +-spec handshake_cancel(#sslsocket{}) -> ok | {error, reason()}. +%% +%% Description: Cancels connection +%%-------------------------------------------------------------------- +handshake_cancel(#sslsocket{pid = [Pid|_]}) -> + case call(Pid, cancel) of + closed -> + ok; + Error -> + Error + end. %-------------------------------------------------------------------- --spec socket_control(tls_connection | dtls_connection, port(), pid(), atom()) -> +-spec socket_control(tls_connection | dtls_connection, port(), [pid()], atom()) -> {ok, #sslsocket{}} | {error, reason()}. %% %% Description: Set the ssl process to own the accept socket @@ -129,24 +177,44 @@ socket_control(Connection, Socket, Pid, Transport) -> socket_control(Connection, Socket, Pid, Transport, undefined). %-------------------------------------------------------------------- --spec socket_control(tls_connection | dtls_connection, port(), pid(), atom(), pid()| undefined) -> +-spec socket_control(tls_connection | dtls_connection, port(), [pid()], atom(), pid()| atom()) -> {ok, #sslsocket{}} | {error, reason()}. %%-------------------------------------------------------------------- -socket_control(Connection, Socket, Pid, Transport, ListenTracker) -> +socket_control(Connection, Socket, Pids, Transport, udp_listener) -> + %% dtls listener process must have the socket control + {ok, Connection:socket(Pids, Transport, Socket, Connection, undefined)}; + +socket_control(tls_connection = Connection, Socket, [Pid|_] = Pids, Transport, ListenTracker) -> + case Transport:controlling_process(Socket, Pid) of + ok -> + {ok, Connection:socket(Pids, Transport, Socket, Connection, ListenTracker)}; + {error, Reason} -> + {error, Reason} + end; +socket_control(dtls_connection = Connection, {_, Socket}, [Pid|_] = Pids, Transport, ListenTracker) -> case Transport:controlling_process(Socket, Pid) of ok -> - {ok, ssl_socket:socket(Pid, Transport, Socket, Connection, ListenTracker)}; + {ok, Connection:socket(Pids, Transport, Socket, Connection, ListenTracker)}; {error, Reason} -> {error, Reason} end. +start_or_recv_cancel_timer(infinity, _RecvFrom) -> + undefined; +start_or_recv_cancel_timer(Timeout, RecvFrom) -> + erlang:send_after(Timeout, self(), {cancel_start_or_recv, RecvFrom}). + +%%==================================================================== +%% User events +%%==================================================================== + %%-------------------------------------------------------------------- -spec send(pid(), iodata()) -> ok | {error, reason()}. %% %% 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)}). @@ -158,28 +226,37 @@ 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(), boolean()) -> {ok, list()} | {error, reason()}. +%% +%% Description: Get the SNI hostname +%%-------------------------------------------------------------------- +connection_information(Pid, IncludeSecrityInfo) when is_pid(Pid) -> + call(Pid, {connection_information, IncludeSecrityInfo}). %%-------------------------------------------------------------------- --spec close(pid()) -> ok | {error, reason()}. +-spec close(pid(), {close, Timeout::integer() | + {NewController::pid(), Timeout::integer()}}) -> + ok | {ok, port()} | {error, reason()}. %% %% Description: Close an ssl connection %%-------------------------------------------------------------------- -close(ConnectionPid) -> - case sync_send_all_state_event(ConnectionPid, close) of +close(ConnectionPid, How) -> + case call(ConnectionPid, How) of {error, closed} -> ok; Other -> Other end. - %%-------------------------------------------------------------------- -spec shutdown(pid(), atom()) -> ok | {error, reason()}. %% %% 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()}. @@ -188,15 +265,15 @@ 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_next_protocol(pid()) -> {ok, binary()} | {error, reason()}. +-spec negotiated_protocol(pid()) -> {ok, binary()} | {error, reason()}. %% %% Description: Returns the negotiated protocol %%-------------------------------------------------------------------- -negotiated_next_protocol(ConnectionPid) -> - sync_send_all_state_event(ConnectionPid, negotiated_next_protocol). +negotiated_protocol(ConnectionPid) -> + call(ConnectionPid, negotiated_protocol). %%-------------------------------------------------------------------- -spec get_opts(pid(), list()) -> {ok, list()} | {error, reason()}. @@ -204,30 +281,14 @@ negotiated_next_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}). - -%%-------------------------------------------------------------------- --spec info(pid()) -> {ok, {atom(), tuple()}} | {error, reason()}. -%% -%% Description: Returns ssl protocol and cipher used for the connection -%%-------------------------------------------------------------------- -info(ConnectionPid) -> - sync_send_all_state_event(ConnectionPid, info). - -%%-------------------------------------------------------------------- --spec session_info(pid()) -> {ok, list()} | {error, reason()}. -%% -%% Description: Returns info about the ssl session -%%-------------------------------------------------------------------- -session_info(ConnectionPid) -> - sync_send_all_state_event(ConnectionPid, session_info). + call(ConnectionPid, {set_opts, Options}). %%-------------------------------------------------------------------- -spec peer_certificate(pid()) -> {ok, binary()| undefined} | {error, reason()}. @@ -235,7 +296,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()}. @@ -243,42 +304,293 @@ peer_certificate(ConnectionPid) -> %% Description: Starts a renegotiation of the ssl session. %%-------------------------------------------------------------------- renegotiation(ConnectionPid) -> - sync_send_all_state_event(ConnectionPid, renegotiate). + call(ConnectionPid, renegotiate). + +%%-------------------------------------------------------------------- +-spec internal_renegotiation(pid(), ssl_record:connection_states()) -> + ok. +%% +%% Description: Starts a renegotiation of the ssl session. +%%-------------------------------------------------------------------- +internal_renegotiation(ConnectionPid, #{current_write := WriteState}) -> + gen_statem:cast(ConnectionPid, {internal_renegotiate, WriteState}). + +get_sslsocket(ConnectionPid) -> + call(ConnectionPid, get_sslsocket). + +dist_handshake_complete(ConnectionPid, DHandle) -> + gen_statem:cast(ConnectionPid, {dist_handshake_complete, DHandle}). %%-------------------------------------------------------------------- -spec prf(pid(), binary() | 'master_secret', binary(), - binary() | ssl:prf_random(), non_neg_integer()) -> + [binary() | ssl:prf_random()], non_neg_integer()) -> {ok, binary()} | {error, reason()} | {'EXIT', term()}. %% %% 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}). + +%%==================================================================== +%% Alert and close handling +%%==================================================================== +handle_own_alert(Alert, _, StateName, + #state{static_env = #static_env{role = Role, + protocol_cb = Connection}, + ssl_options = SslOpts} = State) -> + try %% Try to tell the other side + send_alert(Alert, StateName, State) + catch _:_ -> %% Can crash if we are in a uninitialized state + ignore + end, + try %% Try to tell the local user + log_alert(SslOpts#ssl_options.log_alert, Role, Connection:protocol_name(), StateName, Alert#alert{role = Role}), + handle_normal_shutdown(Alert,StateName, State) + catch _:_ -> + ok + end, + {stop, {shutdown, own_alert}, State}. + +handle_normal_shutdown(Alert, _, #state{static_env = #static_env{role = Role, + socket = Socket, + transport_cb = Transport, + protocol_cb = Connection, + tracker = Tracker}, + start_or_recv_from = StartFrom, + renegotiation = {false, first}} = State) -> + Pids = Connection:pids(State), + alert_user(Pids, Transport, Tracker,Socket, StartFrom, Alert, Role, Connection); + +handle_normal_shutdown(Alert, StateName, #state{static_env = #static_env{role = Role, + socket = Socket, + transport_cb = Transport, + protocol_cb = Connection, + tracker = Tracker}, + socket_options = Opts, + user_application = {_Mon, Pid}, + start_or_recv_from = RecvFrom} = State) -> + Pids = Connection:pids(State), + alert_user(Pids, Transport, Tracker, Socket, StateName, Opts, Pid, RecvFrom, Alert, Role, Connection). + +handle_alert(#alert{level = ?FATAL} = Alert, StateName, + #state{static_env = #static_env{role = Role, + socket = Socket, + host = Host, + port = Port, + tracker = Tracker, + transport_cb = Transport, + protocol_cb = Connection}, + ssl_options = SslOpts, + start_or_recv_from = From, + session = Session, user_application = {_Mon, Pid}, + socket_options = Opts} = State) -> + invalidate_session(Role, Host, Port, Session), + log_alert(SslOpts#ssl_options.log_alert, Role, Connection:protocol_name(), + StateName, Alert#alert{role = opposite_role(Role)}), + Pids = Connection:pids(State), + alert_user(Pids, Transport, Tracker, Socket, StateName, Opts, Pid, From, Alert, Role, Connection), + {stop, {shutdown, normal}, State}; + +handle_alert(#alert{level = ?WARNING, description = ?CLOSE_NOTIFY} = Alert, + downgrade= StateName, State) -> + {next_state, StateName, State, [{next_event, internal, Alert}]}; +handle_alert(#alert{level = ?WARNING, description = ?CLOSE_NOTIFY} = Alert, + StateName, State) -> + handle_normal_shutdown(Alert, StateName, State), + {stop,{shutdown, peer_close}, State}; +handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert, StateName, + #state{static_env = #static_env{role = Role, + protocol_cb = Connection}, + ssl_options = SslOpts, + renegotiation = {true, internal}} = State) -> + log_alert(SslOpts#ssl_options.log_alert, Role, + Connection:protocol_name(), StateName, Alert#alert{role = opposite_role(Role)}), + handle_normal_shutdown(Alert, StateName, State), + {stop,{shutdown, peer_close}, State}; + +handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert, connection = StateName, + #state{static_env = #static_env{role = Role, + protocol_cb = Connection}, + ssl_options = SslOpts, + renegotiation = {true, From} + } = State0) -> + log_alert(SslOpts#ssl_options.log_alert, Role, + Connection:protocol_name(), StateName, Alert#alert{role = opposite_role(Role)}), + gen_statem:reply(From, {error, renegotiation_rejected}), + State = Connection:reinit_handshake_data(State0), + Connection:next_event(connection, no_record, State#state{renegotiation = undefined}); + +handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert, StateName, + #state{static_env = #static_env{role = Role, + protocol_cb = Connection}, + ssl_options = SslOpts, + renegotiation = {true, From} + } = State0) -> + log_alert(SslOpts#ssl_options.log_alert, Role, + Connection:protocol_name(), StateName, Alert#alert{role = opposite_role(Role)}), + gen_statem:reply(From, {error, renegotiation_rejected}), + %% Go back to connection! + State = Connection:reinit(State0#state{renegotiation = undefined}), + Connection:next_event(connection, no_record, State); + +%% Gracefully log and ignore all other warning alerts +handle_alert(#alert{level = ?WARNING} = Alert, StateName, + #state{static_env = #static_env{role = Role, + protocol_cb = Connection}, + ssl_options = SslOpts} = State) -> + log_alert(SslOpts#ssl_options.log_alert, Role, + Connection:protocol_name(), StateName, Alert#alert{role = opposite_role(Role)}), + Connection:next_event(StateName, no_record, State). +%%==================================================================== +%% Data handling +%%==================================================================== +passive_receive(State0 = #state{user_data_buffer = Buffer}, StateName, Connection) -> + case Buffer of + <<>> -> + {Record, State} = Connection:next_record(State0), + Connection:next_event(StateName, Record, State); + _ -> + case read_application_data(<<>>, State0) of + {stop, _, _} = ShutdownError -> + ShutdownError; + {Record, State} -> + Connection:next_event(StateName, Record, State) + end + end. +read_application_data( + Data, + #state{ + user_data_buffer = Buffer0, + erl_dist_handle = DHandle} = State) -> + %% + Buffer = bincat(Buffer0, Data), + case DHandle of + undefined -> + #state{ + socket_options = SocketOpts, + bytes_to_read = BytesToRead, + start_or_recv_from = RecvFrom, + timer = Timer} = State, + read_application_data( + Buffer, State, SocketOpts, RecvFrom, Timer, BytesToRead); + _ -> + try read_application_dist_data(Buffer, State, DHandle) + catch error:_ -> + {stop,disconnect, + State#state{ + user_data_buffer = Buffer, + bytes_to_read = undefined}} + end + end. + +read_application_dist_data(Buffer, State, DHandle) -> + case Buffer of + <<Size:32,Data:Size/binary>> -> + erlang:dist_ctrl_put_data(DHandle, Data), + {no_record, + State#state{ + user_data_buffer = <<>>, + bytes_to_read = undefined}}; + <<Size:32,Data:Size/binary,Rest/binary>> -> + erlang:dist_ctrl_put_data(DHandle, Data), + read_application_dist_data(Rest, State, DHandle); + _ -> + {no_record, + State#state{ + user_data_buffer = Buffer, + bytes_to_read = undefined}} + end. + +read_application_data( + Buffer0, State, SocketOpts0, RecvFrom, Timer, BytesToRead) -> + %% + case get_data(SocketOpts0, BytesToRead, Buffer0) of + {ok, ClientData, Buffer} -> % Send data + #state{ + static_env = + #static_env{ + socket = Socket, + protocol_cb = Connection, + transport_cb = Transport, + tracker = Tracker}, + user_application = {_Mon, Pid}} = State, + SocketOpts = + deliver_app_data( + Connection:pids(State), + Transport, Socket, SocketOpts0, + ClientData, Pid, RecvFrom, Tracker, Connection), + cancel_timer(Timer), + if + SocketOpts#socket_options.active =:= false; + Buffer =:= <<>> -> + %% Passive mode, wait for active once or recv + %% Active and empty, get more data + {no_record, + State#state{ + user_data_buffer = Buffer, + start_or_recv_from = undefined, + timer = undefined, + bytes_to_read = undefined, + socket_options = SocketOpts + }}; + true -> %% We have more data + read_application_data( + Buffer, State, SocketOpts, + undefined, undefined, undefined) + end; + {more, Buffer} -> % no reply, we need more data + {no_record, State#state{user_data_buffer = Buffer}}; + {passive, Buffer} -> + {no_record, State#state{user_data_buffer = Buffer}}; + {error,_Reason} -> %% Invalid packet in packet mode + #state{ + static_env = + #static_env{ + socket = Socket, + protocol_cb = Connection, + transport_cb = Transport, + tracker = Tracker}, + user_application = {_Mon, Pid}} = State, + deliver_packet_error( + Connection:pids(State), Transport, Socket, SocketOpts0, + Buffer0, Pid, RecvFrom, Tracker, Connection), + {stop, {shutdown, normal}, State} + end. + +%%==================================================================== +%% Help functions for tls|dtls_connection.erl +%%==================================================================== +%%-------------------------------------------------------------------- +-spec handle_session(#server_hello{}, ssl_record:ssl_version(), + binary(), ssl_record:connection_states(), _,_, #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- handle_session(#server_hello{cipher_suite = CipherSuite, compression_method = Compression}, - Version, NewId, ConnectionStates, NextProtocol, + Version, NewId, ConnectionStates, ProtoExt, Protocol0, #state{session = #session{session_id = OldId}, - negotiated_version = ReqVersion} = State0) -> - {KeyAlgorithm, _, _, _} = - ssl_cipher:suite_definition(CipherSuite), + negotiated_version = ReqVersion, + negotiated_protocol = CurrentProtocol} = State0) -> + #{key_exchange := KeyAlgorithm} = + ssl_cipher_format:suite_definition(CipherSuite), PremasterSecret = make_premaster_secret(ReqVersion, KeyAlgorithm), - - NewNextProtocol = case NextProtocol of - undefined -> - State0#state.next_protocol; - _ -> - NextProtocol - end, - + + {ExpectNPN, Protocol} = case Protocol0 of + undefined -> + {false, CurrentProtocol}; + _ -> + {ProtoExt =:= npn, Protocol0} + end, + State = State0#state{key_algorithm = KeyAlgorithm, - negotiated_version = Version, + negotiated_version = Version, connection_states = ConnectionStates, premaster_secret = PremasterSecret, - expecting_next_protocol_negotiation = NextProtocol =/= undefined, - next_protocol = NewNextProtocol}, + expecting_next_protocol_negotiation = ExpectNPN, + negotiated_protocol = Protocol}, case ssl_session:is_new(OldId, NewId) of true -> @@ -288,244 +600,368 @@ 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) -> + ssl_config(Opts, Role, State, new). + +ssl_config(Opts, Role, #state{static_env = InitStatEnv0} =State0, Type) -> + {ok, #{cert_db_ref := Ref, + cert_db_handle := CertDbHandle, + fileref_db_handle := FileRefHandle, + session_cache := CacheHandle, + crl_db_info := CRLDbHandle, + private_key := Key, + dh_params := DHParams, + own_certificate := OwnCert}} = + ssl_config:init(Opts, Role), + TimeStamp = erlang:monotonic_time(), + Session = State0#state.session, + + State = State0#state{session = Session#session{own_certificate = OwnCert, + time_stamp = TimeStamp}, + static_env = InitStatEnv0#static_env{ + file_ref_db = FileRefHandle, + cert_db_ref = Ref, + cert_db = CertDbHandle, + crl_db = CRLDbHandle, + session_cache = CacheHandle + }, + private_key = Key, + diffie_hellman_params = DHParams, + ssl_options = Opts}, + case Type of + new -> + Handshake = ssl_handshake:init_handshake_history(), + State#state{tls_handshake_history = Handshake}; + continue -> + State + end. -hello(#hello_request{}, #state{role = client} = State0, Connection) -> - {Record, State} = Connection:next_record(State0), - Connection:next_state(hello, hello, Record, State); -hello({common_client_hello, Type, ServerHelloExt, NegotiatedHashSign}, - State, Connection) -> - do_server_hello(Type, ServerHelloExt, - %% Note NegotiatedHashSign is only negotiated for real if - %% if TLS version is at least TLS-1.2 - State#state{hashsign_algorithm = NegotiatedHashSign}, Connection); +%%==================================================================== +%% gen_statem general state functions with connection cb argument +%%==================================================================== +%%-------------------------------------------------------------------- +-spec init(gen_statem:event_type(), + {start, timeout()} | {start, {list(), list()}, timeout()}| term(), + #state{}, tls_connection | dtls_connection) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- -hello(timeout, State, _) -> - {next_state, hello, State, hibernate}; +init({call, From}, {start, Timeout}, State0, Connection) -> + Timer = start_or_recv_cancel_timer(Timeout, From), + Connection:next_event(hello, no_record, State0#state{start_or_recv_from = From, timer = Timer}); +init({call, From}, {start, {Opts, EmOpts}, Timeout}, + #state{static_env = #static_env{role = Role}, + ssl_options = OrigSSLOptions, + socket_options = SockOpts} = State0, Connection) -> + try + SslOpts = ssl:handle_options(Opts, OrigSSLOptions), + State = ssl_config(SslOpts, Role, State0), + init({call, From}, {start, Timeout}, + State#state{ssl_options = SslOpts, + socket_options = new_emulated(EmOpts, SockOpts)}, Connection) + catch throw:Error -> + {stop_and_reply, {shutdown, normal}, {reply, From, {error, Error}}, State0} + end; +init({call, From}, {new_user, _} = Msg, State, Connection) -> + handle_call(Msg, From, ?FUNCTION_NAME, State, Connection); +init({call, From}, _Msg, _State, _Connection) -> + {keep_state_and_data, [{reply, From, {error, notsup_on_transport_accept_socket}}]}; +init(_Type, _Event, _State, _Connection) -> + {keep_state_and_data, [postpone]}. + +%%-------------------------------------------------------------------- +-spec error(gen_statem:event_type(), + {start, timeout()} | term(), #state{}, + tls_connection | dtls_connection) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +error({call, From}, {close, _}, State, _Connection) -> + {stop_and_reply, {shutdown, normal}, {reply, From, ok}, State}; +error({call, From}, _Msg, State, _Connection) -> + {next_state, ?FUNCTION_NAME, State, [{reply, From, {error, closed}}]}. -hello(Msg, State, Connection) -> - Connection:handle_unexpected_message(Msg, hello, State). +%%-------------------------------------------------------------------- +-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, ?FUNCTION_NAME, State, Connection); +hello(internal, {common_client_hello, Type, ServerHelloExt}, State, Connection) -> + do_server_hello(Type, ServerHelloExt, State, Connection); +hello(info, Msg, State, _) -> + handle_info(Msg, ?FUNCTION_NAME, State); +hello(Type, Msg, State, Connection) -> + handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection). + +user_hello({call, From}, cancel, #state{negotiated_version = Version} = State, _) -> + gen_statem:reply(From, ok), + handle_own_alert(?ALERT_REC(?FATAL, ?USER_CANCELED, user_canceled), + Version, ?FUNCTION_NAME, State); +user_hello({call, From}, {handshake_continue, NewOptions, Timeout}, + #state{hello = Hello, + static_env = #static_env{role = Role}, + start_or_recv_from = RecvFrom, + ssl_options = Options0} = State0, _Connection) -> + Timer = start_or_recv_cancel_timer(Timeout, RecvFrom), + Options = ssl:handle_options(NewOptions, Options0#ssl_options{handshake = full}), + State = ssl_config(Options, Role, State0, continue), + {next_state, hello, State#state{start_or_recv_from = From, + timer = Timer}, + [{next_event, internal, Hello}]}; +user_hello(_, _, _, _) -> + {keep_state_and_data, [postpone]}. %%-------------------------------------------------------------------- --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(#finished{verify_data = Data} = Finished, - #state{role = server, +abbreviated({call, From}, Msg, State, Connection) -> + handle_call(Msg, From, ?FUNCTION_NAME, State, Connection); +abbreviated(internal, #finished{verify_data = Data} = Finished, + #state{static_env = #static_env{role = server}, negotiated_version = Version, expecting_finished = true, tls_handshake_history = Handshake, session = #session{master_secret = MasterSecret}, connection_states = ConnectionStates0} = - State, Connection) -> - case ssl_handshake:verify_connection(Version, Finished, client, + State0, Connection) -> + case ssl_handshake:verify_connection(ssl:tls_version(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) + handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0) end; - -abbreviated(#finished{verify_data = Data} = Finished, - #state{role = client, tls_handshake_history = Handshake0, +abbreviated(internal, #finished{verify_data = Data} = Finished, + #state{static_env = #static_env{role = client}, + tls_handshake_history = Handshake0, session = #session{master_secret = MasterSecret}, negotiated_version = Version, connection_states = ConnectionStates0} = State0, Connection) -> - case ssl_handshake:verify_connection(Version, Finished, server, + case ssl_handshake:verify_connection(ssl:tls_version(Version), Finished, server, get_pending_prf(ConnectionStates0, write), MasterSecret, Handshake0) of verified -> ConnectionStates1 = ssl_record:set_server_verify_data(current_read, Data, ConnectionStates0), - State = + {State1, Actions} = finalize_handshake(State0#state{connection_states = ConnectionStates1}, - abbreviated, Connection), - Connection:next_state_connection(abbreviated, - ack_connection(State#state{expecting_finished = false})); - #alert{} = Alert -> - Connection:handle_own_alert(Alert, Version, abbreviated, State0) + ?FUNCTION_NAME, Connection), + {Record, State} = prepare_connection(State1#state{expecting_finished = false}, Connection), + Connection:next_event(connection, Record, State, Actions); + #alert{} = Alert -> + handle_own_alert(Alert, Version, ?FUNCTION_NAME, 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}, - #state{role = server, expecting_next_protocol_negotiation = true} = State0, +abbreviated(internal, #next_protocol{selected_protocol = SelectedProtocol}, + #state{static_env = #static_env{role = server}, + expecting_next_protocol_negotiation = true} = State, Connection) -> - {Record, State} = Connection:next_record(State0#state{next_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). - + Connection:next_event(?FUNCTION_NAME, no_record, + State#state{negotiated_protocol = SelectedProtocol, + expecting_next_protocol_negotiation = false}); +abbreviated(internal, + #change_cipher_spec{type = <<1>>}, + #state{connection_states = ConnectionStates0} = State, Connection) -> + ConnectionStates1 = + ssl_record:activate_pending_connection_state(ConnectionStates0, read, Connection), + Connection:next_event(?FUNCTION_NAME, no_record, State#state{connection_states = + ConnectionStates1, + expecting_finished = true}); +abbreviated(info, Msg, State, _) -> + handle_info(Msg, ?FUNCTION_NAME, State); +abbreviated(Type, Msg, State, Connection) -> + handle_common_event(Type, Msg, ?FUNCTION_NAME, 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 = []}, - #state{role = server, negotiated_version = Version, +certify({call, From}, Msg, State, Connection) -> + handle_call(Msg, From, ?FUNCTION_NAME, State, Connection); +certify(info, Msg, State, _) -> + handle_info(Msg, ?FUNCTION_NAME, State); +certify(internal, #certificate{asn1_certificates = []}, + #state{static_env = #static_env{role = server}, + negotiated_version = Version, ssl_options = #ssl_options{verify = verify_peer, fail_if_no_peer_cert = true}} = - State, Connection) -> + State, _) -> Alert = ?ALERT_REC(?FATAL,?HANDSHAKE_FAILURE), - Connection:handle_own_alert(Alert, Version, certify, State); - -certify(#certificate{asn1_certificates = []}, - #state{role = server, + handle_own_alert(Alert, Version, ?FUNCTION_NAME, State); +certify(internal, #certificate{asn1_certificates = []}, + #state{static_env = #static_env{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); - -certify(#certificate{} = Cert, - #state{negotiated_version = Version, - role = Role, - cert_db = CertDbHandle, - cert_db_ref = CertDbRef, + Connection:next_event(?FUNCTION_NAME, no_record, State0#state{client_certificate_requested = false}); +certify(internal, #certificate{}, + #state{static_env = #static_env{role = server}, + negotiated_version = Version, + ssl_options = #ssl_options{verify = verify_none}} = + State, _) -> + Alert = ?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE, unrequested_certificate), + handle_own_alert(Alert, Version, ?FUNCTION_NAME, State); +certify(internal, #certificate{} = Cert, + #state{static_env = #static_env{ + role = Role, + host = Host, + cert_db = CertDbHandle, + cert_db_ref = CertDbRef, + crl_db = CRLDbInfo}, + negotiated_version = Version, ssl_options = Opts} = State, Connection) -> - case ssl_handshake:certify(Cert, CertDbHandle, CertDbRef, Opts#ssl_options.depth, - Opts#ssl_options.verify, - Opts#ssl_options.verify_fun, - Opts#ssl_options.partial_chain, - Role) of + case ssl_handshake:certify(Cert, CertDbHandle, CertDbRef, + Opts, CRLDbInfo, Role, Host) of {PeerCert, PublicKeyInfo} -> handle_peer_cert(Role, PeerCert, PublicKeyInfo, State#state{client_certificate_requested = false}, Connection); #alert{} = Alert -> - Connection:handle_own_alert(Alert, Version, certify, State) + handle_own_alert(Alert, Version, ?FUNCTION_NAME, State) end; - -certify(#server_key_exchange{exchange_keys = Keys}, - #state{role = client, negotiated_version = Version, +certify(internal, #server_key_exchange{exchange_keys = Keys}, + #state{static_env = #static_env{role = client}, + negotiated_version = Version, key_algorithm = Alg, public_key_info = PubKeyInfo, + session = Session, connection_states = ConnectionStates} = State, Connection) when Alg == dhe_dss; Alg == dhe_rsa; Alg == ecdhe_rsa; Alg == ecdhe_ecdsa; Alg == dh_anon; Alg == ecdh_anon; - Alg == psk; Alg == dhe_psk; Alg == rsa_psk; + Alg == psk; Alg == dhe_psk; Alg == ecdhe_psk; Alg == rsa_psk; Alg == srp_dss; Alg == srp_rsa; Alg == srp_anon -> - Params = ssl_handshake:decode_server_key(Keys, Alg, Version), - HashSign = negotiated_hashsign(Params#server_key_params.hashsign, Alg, Version), + Params = ssl_handshake:decode_server_key(Keys, Alg, ssl:tls_version(Version)), + + %% Use negotiated value if TLS-1.2 otherwhise return default + HashSign = negotiated_hashsign(Params#server_key_params.hashsign, Alg, PubKeyInfo, ssl:tls_version(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, ssl:tls_version(Version), PubKeyInfo) of true -> calculate_secret(Params#server_key_params.params, - State#state{hashsign_algorithm = HashSign}, Connection); + State#state{hashsign_algorithm = HashSign, + session = session_handle_params(Params#server_key_params.params, Session)}, + Connection); false -> - Connection:handle_own_alert(?ALERT_REC(?FATAL, ?DECRYPT_ERROR), - Version, certify, State) + handle_own_alert(?ALERT_REC(?FATAL, ?DECRYPT_ERROR), + Version, ?FUNCTION_NAME, 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}, - #state{session = #session{own_certificate = Cert}, - negotiated_version = Version} = State0, Connection) -> - HashSign = ssl_handshake:select_hashsign(HashSigns, Cert, Version), - {Record, State} = Connection:next_record(State0#state{client_certificate_requested = true}), - Connection:next_state(certify, certify, Record, - State#state{cert_hashsign_algorithm = HashSign}); - +certify(internal, #certificate_request{}, + #state{static_env = #static_env{role = client}, + negotiated_version = Version, + key_algorithm = Alg} = State, _) + when Alg == dh_anon; Alg == ecdh_anon; + Alg == psk; Alg == dhe_psk; Alg == ecdhe_psk; Alg == rsa_psk; + Alg == srp_dss; Alg == srp_rsa; Alg == srp_anon -> + handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE), + Version, ?FUNCTION_NAME, State); +certify(internal, #certificate_request{}, + #state{static_env = #static_env{role = client}, + session = #session{own_certificate = undefined}} = State, Connection) -> + %% The 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. + Connection:next_event(?FUNCTION_NAME, no_record, State#state{client_certificate_requested = true}); +certify(internal, #certificate_request{} = CertRequest, + #state{static_env = #static_env{role = client}, + session = #session{own_certificate = Cert}, + ssl_options = #ssl_options{signature_algs = SupportedHashSigns}, + negotiated_version = Version} = State, Connection) -> + case ssl_handshake:select_hashsign(CertRequest, Cert, SupportedHashSigns, ssl:tls_version(Version)) of + #alert {} = Alert -> + handle_own_alert(Alert, Version, ?FUNCTION_NAME, State); + NegotiatedHashSign -> + Connection:next_event(?FUNCTION_NAME, no_record, + State#state{client_certificate_requested = true, + cert_hashsign_algorithm = NegotiatedHashSign}) + end; %% PSK and RSA_PSK might bypass the Server-Key-Exchange -certify(#server_hello_done{}, - #state{session = #session{master_secret = undefined}, +certify(internal, #server_hello_done{}, + #state{static_env = #static_env{role = client}, + session = #session{master_secret = undefined}, negotiated_version = Version, psk_identity = PSKIdentity, ssl_options = #ssl_options{user_lookup_fun = PSKLookup}, premaster_secret = undefined, - role = client, key_algorithm = Alg} = State0, Connection) when Alg == psk -> case ssl_handshake:premaster_secret({Alg, PSKIdentity}, PSKLookup) of #alert{} = Alert -> - Connection:handle_own_alert(Alert, Version, certify, State0); + handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0); PremasterSecret -> State = master_secret(PremasterSecret, State0#state{premaster_secret = PremasterSecret}), client_certify_and_key_exchange(State, Connection) end; - -certify(#server_hello_done{}, - #state{session = #session{master_secret = undefined}, +certify(internal, #server_hello_done{}, + #state{static_env = #static_env{role = client}, + 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; + handle_own_alert(Alert, Version, ?FUNCTION_NAME, 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{}, - #state{session = #session{master_secret = MasterSecret} = Session, +certify(internal, #server_hello_done{}, + #state{static_env = #static_env{role = client}, + session = #session{master_secret = MasterSecret} = Session, connection_states = ConnectionStates0, negotiated_version = Version, - premaster_secret = undefined, - role = client} = State0, Connection) -> - case ssl_handshake:master_secret(record_cb(Connection), Version, Session, + premaster_secret = undefined} = State0, Connection) -> + case ssl_handshake:master_secret(ssl:tls_version(Version), Session, ConnectionStates0, client) of {MasterSecret, ConnectionStates} -> State = State0#state{connection_states = ConnectionStates}, client_certify_and_key_exchange(State, Connection); #alert{} = Alert -> - Connection:handle_own_alert(Alert, Version, certify, State0) + handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0) end; - %% Master secret is calculated from premaster_secret -certify(#server_hello_done{}, - #state{session = Session0, +certify(internal, #server_hello_done{}, + #state{static_env = #static_env{role = client}, + session = Session0, connection_states = ConnectionStates0, negotiated_version = Version, - premaster_secret = PremasterSecret, - role = client} = State0, Connection) -> - case ssl_handshake:master_secret(record_cb(Connection), Version, PremasterSecret, + premaster_secret = PremasterSecret} = State0, Connection) -> + case ssl_handshake:master_secret(ssl:tls_version(Version), PremasterSecret, ConnectionStates0, client) of {MasterSecret, ConnectionStates} -> Session = Session0#session{master_secret = MasterSecret}, @@ -533,283 +969,293 @@ certify(#server_hello_done{}, session = Session}, client_certify_and_key_exchange(State, Connection); #alert{} = Alert -> - Connection:handle_own_alert(Alert, Version, certify, State0) + handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0) end; - -certify(#client_key_exchange{} = Msg, - #state{role = server, +certify(internal = Type, #client_key_exchange{} = Msg, + #state{static_env = #static_env{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); - -certify(#client_key_exchange{exchange_keys = Keys}, + handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection); +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), + certify_client_key_exchange(ssl_handshake:decode_client_key(Keys, KeyAlg, ssl:tls_version(Version)), State, Connection) catch #alert{} = Alert -> - Connection:handle_own_alert(Alert, Version, certify, State) + handle_own_alert(Alert, Version, ?FUNCTION_NAME, 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, ?FUNCTION_NAME, 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(#certificate_verify{signature = Signature, hashsign_algorithm = CertHashSign}, - #state{role = server, - public_key_info = {Algo, _, _} =PublicKeyInfo, +cipher({call, From}, Msg, State, Connection) -> + handle_call(Msg, From, ?FUNCTION_NAME, State, Connection); +cipher(info, Msg, State, _) -> + handle_info(Msg, ?FUNCTION_NAME, State); +cipher(internal, #certificate_verify{signature = Signature, + hashsign_algorithm = CertHashSign}, + #state{static_env = #static_env{role = server}, + key_algorithm = KexAlg, + public_key_info = PublicKeyInfo, negotiated_version = Version, session = #session{master_secret = MasterSecret}, tls_handshake_history = Handshake - } = State0, Connection) -> - - HashSign = ssl_handshake:select_hashsign_algs(CertHashSign, Algo, Version), + } = State, Connection) -> + + TLSVersion = ssl:tls_version(Version), + %% Use negotiated value if TLS-1.2 otherwhise return default + HashSign = negotiated_hashsign(CertHashSign, KexAlg, PublicKeyInfo, TLSVersion), case ssl_handshake:certificate_verify(Signature, PublicKeyInfo, - Version, HashSign, MasterSecret, Handshake) of + TLSVersion, HashSign, MasterSecret, Handshake) of valid -> - {Record, State} = Connection:next_record(State0), - Connection:next_state(cipher, cipher, Record, + Connection:next_event(?FUNCTION_NAME, no_record, State#state{cert_hashsign_algorithm = HashSign}); #alert{} = Alert -> - Connection:handle_own_alert(Alert, Version, cipher, State0) + handle_own_alert(Alert, Version, ?FUNCTION_NAME, State) end; - %% client must send a next protocol message if we are expecting it -cipher(#finished{}, #state{role = server, expecting_next_protocol_negotiation = true, - next_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, - #state{negotiated_version = Version, - host = Host, - port = Port, - role = Role, - expecting_finished = true, +cipher(internal, #finished{}, + #state{static_env = #static_env{role = server}, + expecting_next_protocol_negotiation = true, + negotiated_protocol = undefined, negotiated_version = Version} = State0, + _Connection) -> + handle_own_alert(?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE), Version, ?FUNCTION_NAME, State0); +cipher(internal, #finished{verify_data = Data} = Finished, + #state{static_env = #static_env{role = Role, + host = Host, + port = Port}, + negotiated_version = Version, + expecting_finished = true, session = #session{master_secret = MasterSecret} = Session0, + ssl_options = SslOpts, connection_states = ConnectionStates0, tls_handshake_history = Handshake0} = State, Connection) -> - case ssl_handshake:verify_connection(Version, Finished, + case ssl_handshake:verify_connection(ssl:tls_version(Version), Finished, opposite_role(Role), get_current_prf(ConnectionStates0, read), MasterSecret, Handshake0) of verified -> - Session = register_session(Role, Host, Port, Session0), - cipher_role(Role, Data, Session, State#state{expecting_finished = false}, Connection); + Session = register_session(Role, host_id(Role, Host, SslOpts), Port, Session0), + cipher_role(Role, Data, Session, + State#state{expecting_finished = false}, Connection); #alert{} = Alert -> - Connection:handle_own_alert(Alert, Version, cipher, State) + handle_own_alert(Alert, Version, ?FUNCTION_NAME, 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}, - #state{role = server, expecting_next_protocol_negotiation = true, +cipher(internal, #next_protocol{selected_protocol = SelectedProtocol}, + #state{static_env = #static_env{role = server}, + expecting_next_protocol_negotiation = true, expecting_finished = true} = State0, Connection) -> - {Record, State} = Connection:next_record(State0#state{next_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(?FUNCTION_NAME, Record, + State#state{expecting_next_protocol_negotiation = false}); +cipher(internal, #change_cipher_spec{type = <<1>>}, #state{connection_states = ConnectionStates0} = + State, Connection) -> + ConnectionStates = + ssl_record:activate_pending_connection_state(ConnectionStates0, read, Connection), + Connection:next_event(?FUNCTION_NAME, no_record, State#state{connection_states = + ConnectionStates, + expecting_finished = true}); +cipher(Type, Msg, State, Connection) -> + handle_common_event(Type, Msg, ?FUNCTION_NAME, 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, RecvFrom}, {recv, N, Timeout}, + #state{static_env = #static_env{protocol_cb = Connection}, + socket_options = + #socket_options{active = false}} = State0, Connection) -> + Timer = start_or_recv_cancel_timer(Timeout, RecvFrom), + passive_receive(State0#state{bytes_to_read = N, + start_or_recv_from = RecvFrom, + timer = Timer}, ?FUNCTION_NAME, Connection); + +connection({call, From}, renegotiate, #state{static_env = #static_env{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(?FUNCTION_NAME, State, [{reply, From, {ok, Cert}}]); +connection({call, From}, {connection_information, true}, State, _) -> + Info = connection_info(State) ++ security_info(State), + hibernate_after(?FUNCTION_NAME, State, [{reply, From, {ok, Info}}]); +connection({call, From}, {connection_information, false}, State, _) -> + Info = connection_info(State), + hibernate_after(?FUNCTION_NAME, State, [{reply, From, {ok, Info}}]); +connection({call, From}, negotiated_protocol, + #state{negotiated_protocol = undefined} = State, _) -> + hibernate_after(?FUNCTION_NAME, State, [{reply, From, {error, protocol_not_negotiated}}]); +connection({call, From}, negotiated_protocol, + #state{negotiated_protocol = SelectedProtocol} = State, _) -> + hibernate_after(?FUNCTION_NAME, State, + [{reply, From, {ok, SelectedProtocol}}]); +connection({call, From}, Msg, State, Connection) -> + handle_call(Msg, From, ?FUNCTION_NAME, State, Connection); +connection(cast, {internal_renegotiate, WriteState}, #state{static_env = #static_env{protocol_cb = Connection}, + connection_states = ConnectionStates} + = State, Connection) -> + Connection:renegotiate(State#state{renegotiation = {true, internal}, + connection_states = ConnectionStates#{current_write => WriteState}}, []); +connection(cast, {dist_handshake_complete, DHandle}, + #state{ssl_options = #ssl_options{erl_dist = true}, + socket_options = SockOpts} = State0, Connection) -> + process_flag(priority, normal), + State1 = + State0#state{ + socket_options = SockOpts#socket_options{active = true}, + erl_dist_handle = DHandle, + bytes_to_read = undefined}, + {Record, State} = read_application_data(<<>>, State1), + Connection:next_event(connection, Record, State); +connection(info, Msg, State, _) -> + handle_info(Msg, ?FUNCTION_NAME, State); +connection(internal, {recv, _}, State, Connection) -> + passive_receive(State, ?FUNCTION_NAME, Connection); +connection(Type, Msg, State, Connection) -> + handle_common_event(Type, Msg, ?FUNCTION_NAME, 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(Type, Event, State, Connection) -> + handle_common_event(Type, Event, ?FUNCTION_NAME, 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)} - end; -handle_sync_event({application_data, Data}, From, StateName, - #state{send_queue = Queue} = State) -> +handle_common_event(internal, {handshake, {#hello_request{} = Handshake, _}}, connection = StateName, + #state{static_env = #static_env{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{static_env = #static_env{role = client}}, _) + when StateName =/= connection -> + {keep_state_and_data}; +handle_common_event(internal, {handshake, {Handshake, Raw}}, StateName, + #state{tls_handshake_history = Hs0} = State0, + Connection) -> + + PossibleSNI = Connection:select_sni_extension(Handshake), + %% 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 = handle_sni_extension(PossibleSNI, State0), + HsHist = ssl_handshake:update_handshake_history(Hs0, iolist_to_binary(Raw)), + {next_state, StateName, State#state{tls_handshake_history = HsHist}, + [{next_event, internal, Handshake}]}; +handle_common_event(internal, {protocol_record, TLSorDTLSRecord}, StateName, State, Connection) -> + Connection:handle_protocol_record(TLSorDTLSRecord, StateName, State); +handle_common_event(timeout, hibernate, _, _, _) -> + {keep_state_and_data, [hibernate]}; +handle_common_event(internal, #change_cipher_spec{type = <<1>>}, StateName, + #state{negotiated_version = Version} = State, _) -> + handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE), Version, + StateName, State); +handle_common_event(_Type, Msg, StateName, #state{negotiated_version = Version} = State, + _) -> + Alert = ?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE, {unexpected_msg, Msg}), + handle_own_alert(Alert, Version, StateName, 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} + {keep_state_and_data, [postpone]}; +handle_call({close, _} = Close, From, StateName, State, _Connection) -> + %% Run terminate before returning so that the reuseaddr + %% inet-option works properly + Result = terminate(Close, StateName, State), + {stop_and_reply, + {shutdown, normal}, + {reply, From, Result}, State#state{terminated = true}}; +handle_call({shutdown, read_write = How}, From, StateName, + #state{static_env = #static_env{transport_cb = Transport, + socket = Socket}} = State, _) -> + try send_alert(?ALERT_REC(?WARNING, ?CLOSE_NOTIFY), + StateName, State) of + _ -> + case Transport:shutdown(Socket, How) of + ok -> + {next_state, StateName, State#state{terminated = true}, [{reply, From, ok}]}; + Error -> + {stop, StateName, State#state{terminated = true}, [{reply, From, Error}]} + end + catch + throw:Return -> + Return 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, _, StateName, #state{protocol_cb = Connection} = State) -> - %% Run terminate before returning - %% so that the reuseaddr inet-option will work - %% as intended. - (catch Connection:terminate(user_close, StateName, State)), - {stop, normal, ok, State#state{terminated = true}}; -handle_sync_event({shutdown, How0}, _, StateName, - #state{transport_cb = Transport, - negotiated_version = Version, - connection_states = ConnectionStates, - socket = Socket} = State) -> - case How0 of - How when How == write; How == both -> - Alert = ?ALERT_REC(?WARNING, ?CLOSE_NOTIFY), - {BinMsg, _} = - ssl_alert:encode(Alert, Version, ConnectionStates), - Transport:send(Socket, BinMsg); - _ -> - ok - end, - +handle_call({shutdown, How0}, From, StateName, + #state{static_env = #static_env{transport_cb = Transport, + socket = Socket}} = State, _) -> case Transport:shutdown(Socket, How0) of ok -> - {reply, ok, StateName, State, get_timeout(State)}; + {next_state, StateName, State, [{reply, From, ok}]}; Error -> - {stop, normal, Error, State} + {stop, StateName, State, [{reply, From, Error}]} 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, - #state{socket = Socket, - transport_cb = Transport, - socket_options = SockOpts} = State) -> - OptsReply = get_socket_opts(Transport, Socket, OptTags, SockOpts, []), - {reply, OptsReply, StateName, State, get_timeout(State)}; -handle_sync_event(negotiated_next_protocol, _From, StateName, #state{next_protocol = undefined} = State) -> - {reply, {error, next_protocol_not_negotiated}, StateName, State, get_timeout(State)}; -handle_sync_event(negotiated_next_protocol, _From, StateName, #state{next_protocol = NextProtocol} = State) -> - {reply, {ok, NextProtocol}, StateName, State, get_timeout(State)}; -handle_sync_event({set_opts, Opts0}, _From, StateName0, - #state{socket_options = Opts1, - protocol_cb = Connection, - socket = Socket, - transport_cb = Transport, - user_data_buffer = Buffer} = 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) -> - ConnectionState = + {next_state, StateName, State#state{user_application = {NewMon,User}}, + [{reply, From, ok}]}; +handle_call({get_opts, OptTags}, From, _, + #state{static_env = #static_env{socket = Socket, + transport_cb = Transport}, + socket_options = SockOpts}, Connection) -> + OptsReply = get_socket_opts(Connection, Transport, Socket, OptTags, SockOpts, []), + {keep_state_and_data, [{reply, From, OptsReply}]}; +handle_call({set_opts, Opts0}, From, StateName, + #state{static_env = #static_env{socket = Socket, + transport_cb = Transport}, + socket_options = Opts1 + } = State0, Connection) -> + {Reply, Opts} = set_socket_opts(Connection, Transport, Socket, Opts0, Opts1, []), + 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(get_sslsocket, From, _StateName, State, Connection) -> + SslSocket = Connection:socket(State), + {keep_state_and_data, [{reply, From, SslSocket}]}; + +handle_call({prf, Secret, Label, Seed, WantedLength}, From, _, + #state{connection_states = ConnectionStates, + negotiated_version = Version}, _) -> + #{security_parameters := SecParams} = ssl_record:current_connection_state(ConnectionStates, read), - SecParams = ConnectionState#connection_state.security_parameters, #security_parameters{master_secret = MasterSecret, client_random = ClientRandom, - server_random = ServerRandom} = SecParams, + server_random = ServerRandom, + prf_algorithm = PRFAlgorithm} = SecParams, Reply = try SecretToUse = case Secret of _ when is_binary(Secret) -> Secret; @@ -820,125 +1266,135 @@ handle_sync_event({prf, Secret, Label, Seed, WantedLength}, _, StateName, (client_random, Acc) -> [ClientRandom|Acc]; (server_random, Acc) -> [ServerRandom|Acc] end, [], Seed)), - ssl_handshake:prf(Version, SecretToUse, Label, SeedToUse, WantedLength) + ssl_handshake:prf(ssl:tls_version(Version), PRFAlgorithm, SecretToUse, Label, SeedToUse, WantedLength) catch exit:_ -> {error, badarg}; error:Reason -> {error, Reason} end, - {reply, Reply, StateName, State, get_timeout(State)}; -handle_sync_event(info, _, StateName, - #state{negotiated_version = Version, - session = #session{cipher_suite = Suite}} = State) -> - - AtomVersion = tls_record:protocol_version(Version), - {reply, {ok, {AtomVersion, ssl:suite_definition(Suite)}}, - 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: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)}. + {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, - start_or_recv_from = StartFrom, role = Role, - 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), - {stop, normal, State}; - -handle_info({ErrorTag, Socket, Reason}, StateName, #state{socket = Socket, - protocol_cb = Connection, - error_tag = ErrorTag} = State) -> + #state{static_env = #static_env{role = Role, + socket = Socket, + transport_cb = Transport, + error_tag = ErrorTag, + tracker = Tracker, + protocol_cb = Connection}, + start_or_recv_from = StartFrom + } = State) when StateName =/= connection -> + Pids = Connection:pids(State), + alert_user(Pids, Transport, Tracker,Socket, + StartFrom, ?ALERT_REC(?FATAL, ?CLOSE_NOTIFY), Role, Connection), + {stop, {shutdown, normal}, State}; + +handle_info({ErrorTag, Socket, Reason}, StateName, #state{static_env = #static_env{socket = Socket, + error_tag = ErrorTag}} = State) -> Report = io_lib:format("SSL: Socket error: ~p ~n", [Reason]), - error_logger:info_report(Report), - Connection:handle_normal_shutdown(?ALERT_REC(?FATAL, ?CLOSE_NOTIFY), StateName, State), - {stop, normal, State}; - -handle_info({'DOWN', MonitorRef, _, _, _}, _, - State = #state{user_application={MonitorRef,_Pid}}) -> - {stop, normal, State}; - + error_logger:error_report(Report), + handle_normal_shutdown(?ALERT_REC(?FATAL, ?CLOSE_NOTIFY), StateName, State), + {stop, {shutdown,normal}, State}; + +handle_info({'DOWN', MonitorRef, _, _, Reason}, _, + #state{user_application = {MonitorRef, _Pid}, + ssl_options = #ssl_options{erl_dist = true}}) -> + {stop, {shutdown, Reason}}; +handle_info({'DOWN', MonitorRef, _, _, _}, _, + #state{user_application = {MonitorRef, _Pid}}) -> + {stop, {shutdown, normal}}; +handle_info({'EXIT', Pid, _Reason}, StateName, + #state{user_application = {_MonitorRef, Pid}} = State) -> + %% It seems the user application has linked to us + %% - ignore that and let the monitor handle this + {next_state, StateName, State}; %%% So that terminate will be run when supervisor issues shutdown handle_info({'EXIT', _Sup, shutdown}, _StateName, State) -> {stop, shutdown, State}; -handle_info({'EXIT', Socket, normal}, _StateName, #state{socket = Socket} = State) -> +handle_info({'EXIT', Socket, normal}, _StateName, #state{static_env = #static_env{socket = Socket}} = State) -> %% Handle as transport close" - {stop, {shutdown, transport_closed}, State}; + {stop,{shutdown, transport_closed}, State}; +handle_info({'EXIT', Socket, Reason}, _StateName, #state{static_env = #static_env{socket = Socket}} = State) -> + {stop,{shutdown, Reason}, 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}}; - -handle_info({cancel_start_or_recv, RecvFrom}, StateName, #state{start_or_recv_from = RecvFrom} = State) -> - gen_fsm:reply(RecvFrom, {error, timeout}), + {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) 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) -> +handle_info(Msg, StateName, #state{static_env = #static_env{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}. +%%==================================================================== +%% general 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. + %% when ssl:close/1 returns unless it is a downgrade where + %% we want to guarantee that close alert is received before + %% returning. In both cases terminate has been run manually + %% before run by gen_statem which will end up here ok; -terminate({shutdown, transport_closed}, StateName, #state{send_queue = SendQueue, - renegotiation = Renegotiate} = State) -> - handle_unrecv_data(StateName, State), +terminate({shutdown, transport_closed} = Reason, + _StateName, #state{static_env = #static_env{protocol_cb = Connection, + socket = Socket, + transport_cb = Transport}} = State) -> handle_trusted_certs_db(State), - notify_senders(SendQueue), - notify_renegotiater(Renegotiate); - -terminate({shutdown, own_alert}, _StateName, #state{send_queue = SendQueue, - renegotiation = Renegotiate} = State) -> - handle_trusted_certs_db(State), - notify_senders(SendQueue), - notify_renegotiater(Renegotiate); -terminate(Reason, connection, #state{negotiated_version = Version, - protocol_cb = Connection, - connection_states = ConnectionStates, - transport_cb = Transport, socket = Socket, - send_queue = SendQueue, renegotiation = Renegotiate} = State) -> + Connection:close(Reason, Socket, Transport, undefined, undefined); +terminate({shutdown, own_alert}, _StateName, #state{ + static_env = #static_env{protocol_cb = Connection, + socket = Socket, + transport_cb = Transport}} = State) -> handle_trusted_certs_db(State), - notify_senders(SendQueue), - notify_renegotiater(Renegotiate), - BinAlert = terminate_alert(Reason, Version, ConnectionStates), - Transport:send(Socket, BinAlert), - case Connection of - tls_connection -> - tls_connection:workaround_transport_delivery_problems(Socket, Transport); + case application:get_env(ssl, alert_timeout) of + {ok, Timeout} when is_integer(Timeout) -> + Connection:close({timeout, Timeout}, Socket, Transport, undefined, undefined); _ -> - ok + Connection:close({timeout, ?DEFAULT_TIMEOUT}, Socket, Transport, undefined, undefined) end; -terminate(_Reason, _StateName, #state{transport_cb = Transport, - socket = Socket, send_queue = SendQueue, - renegotiation = Renegotiate} = State) -> +terminate({shutdown, downgrade = Reason}, downgrade, #state{static_env = #static_env{protocol_cb = Connection, + transport_cb = Transport, + socket = Socket} + } = State) -> + handle_trusted_certs_db(State), + Connection:close(Reason, Socket, Transport, undefined, undefined); +terminate(Reason, connection, #state{static_env = #static_env{ + protocol_cb = Connection, + transport_cb = Transport, + socket = Socket}, + connection_states = ConnectionStates, + ssl_options = #ssl_options{padding_check = Check} + } = State) -> + handle_trusted_certs_db(State), + Alert = terminate_alert(Reason), + %% Send the termination ALERT if possible + catch (ok = Connection:send_alert_in_connection(Alert, State)), + Connection:close({timeout, ?DEFAULT_TIMEOUT}, Socket, Transport, ConnectionStates, Check); +terminate(Reason, _StateName, #state{static_env = #static_env{transport_cb = Transport, + protocol_cb = Connection, + socket = Socket} + } = State) -> handle_trusted_certs_db(State), - notify_senders(SendQueue), - notify_renegotiater(Renegotiate), - Transport:close(Socket). + 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, @@ -947,38 +1403,58 @@ 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, OwnCert, Key, DHParams} = - ssl_config:init(Opts, Role), - Handshake = ssl_handshake:init_handshake_history(), - TimeStamp = calendar:datetime_to_gregorian_seconds({date(), 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, - session_cache = CacheHandle, - private_key = Key, - diffie_hellman_params = DHParams, - ssl_options = Opts}. +send_alert(Alert, connection, #state{static_env = #static_env{protocol_cb = Connection}} = State) -> + Connection:send_alert_in_connection(Alert, State); +send_alert(Alert, _, #state{static_env = #static_env{protocol_cb = Connection}} = State) -> + Connection:send_alert(Alert, State). + +connection_info(#state{static_env = #static_env{protocol_cb = Connection}, + sni_hostname = SNIHostname, + session = #session{session_id = SessionId, + cipher_suite = CipherSuite, ecc = ECCCurve}, + negotiated_version = {_,_} = Version, + ssl_options = Opts}) -> + RecordCB = record_cb(Connection), + CipherSuiteDef = #{key_exchange := KexAlg} = ssl_cipher_format:suite_definition(CipherSuite), + IsNamedCurveSuite = lists:member(KexAlg, + [ecdh_ecdsa, ecdhe_ecdsa, ecdh_rsa, ecdhe_rsa, ecdh_anon]), + CurveInfo = case ECCCurve of + {namedCurve, Curve} when IsNamedCurveSuite -> + [{ecc, {named_curve, pubkey_cert_records:namedCurves(Curve)}}]; + _ -> + [] + end, + [{protocol, RecordCB:protocol_version(Version)}, + {session_id, SessionId}, + {cipher_suite, ssl_cipher_format:erl_suite_definition(CipherSuiteDef)}, + {selected_cipher_suite, CipherSuiteDef}, + {sni_hostname, SNIHostname} | CurveInfo] ++ ssl_options_list(Opts). + +security_info(#state{connection_states = ConnectionStates}) -> + #{security_parameters := + #security_parameters{client_random = ClientRand, + server_random = ServerRand, + master_secret = MasterSecret}} = + ssl_record:current_connection_state(ConnectionStates, read), + [{client_random, ClientRand}, {server_random, ServerRand}, {master_secret, MasterSecret}]. do_server_hello(Type, #hello_extensions{next_protocol_negotiation = NextProtocols} = ServerHelloExt, @@ -988,7 +1464,7 @@ do_server_hello(Type, #hello_extensions{next_protocol_negotiation = NextProtocol = State0, Connection) when is_atom(Type) -> ServerHello = - ssl_handshake:server_hello(SessId, Version, ConnectionStates0, ServerHelloExt), + ssl_handshake:server_hello(SessId, ssl:tls_version(Version), ConnectionStates0, ServerHelloExt), State = server_hello(ServerHello, State0#state{expecting_next_protocol_negotiation = NextProtocols =/= undefined}, Connection), @@ -1006,39 +1482,37 @@ new_server_hello(#server_hello{cipher_suite = CipherSuite, negotiated_version = Version} = State0, Connection) -> try server_certify_and_key_exchange(State0, Connection) of #state{} = State1 -> - State2 = server_hello_done(State1, Connection), + {State, Actions} = server_hello_done(State1, Connection), Session = Session0#session{session_id = SessionId, 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, no_record, State#state{session = Session}, Actions) catch #alert{} = Alert -> - Connection:handle_own_alert(Alert, Version, hello, State0) + handle_own_alert(Alert, Version, hello, State0) end. resumed_server_hello(#state{session = Session, connection_states = ConnectionStates0, negotiated_version = Version} = State0, Connection) -> - case ssl_handshake:master_secret(record_cb(Connection), Version, Session, + case ssl_handshake:master_secret(ssl:tls_version(Version), Session, ConnectionStates0, server) of {_, ConnectionStates1} -> State1 = State0#state{connection_states = ConnectionStates1, session = Session}, - State2 = + {State, Actions} = finalize_handshake(State1, abbreviated, Connection), - {Record, State} = Connection:next_record(State2), - Connection:next_state(hello, abbreviated, Record, State); + Connection:next_event(abbreviated, no_record, State, Actions); #alert{} = Alert -> - Connection:handle_own_alert(Alert, Version, hello, State0) + handle_own_alert(Alert, Version, hello, State0) end. server_hello(ServerHello, State0, Connection) -> CipherSuite = ServerHello#server_hello.cipher_suite, - {KeyAlgorithm, _, _, _} = ssl_cipher:suite_definition(CipherSuite), - State = Connection:send_handshake(ServerHello, State0), + #{key_exchange := KeyAlgorithm} = ssl_cipher_format:suite_definition(CipherSuite), + State = Connection:queue_handshake(ServerHello, State0), State#state{key_algorithm = KeyAlgorithm}. server_hello_done(State, Connection) -> @@ -1051,21 +1525,19 @@ handle_peer_cert(Role, PeerCert, PublicKeyInfo, State1 = State0#state{session = Session#session{peer_certificate = PeerCert}, public_key_info = PublicKeyInfo}, - {KeyAlg,_,_,_} = ssl_cipher:suite_definition(CipherSuite), - State2 = handle_peer_cert_key(Role, PeerCert, PublicKeyInfo, KeyAlg, State1), - - {Record, State} = Connection:next_record(State2), - Connection:next_state(certify, certify, Record, State). + #{key_exchange := KeyAlgorithm} = ssl_cipher_format:suite_definition(CipherSuite), + State = handle_peer_cert_key(Role, PeerCert, PublicKeyInfo, KeyAlgorithm, State1), + Connection:next_event(certify, no_record, State). handle_peer_cert_key(client, _, {?'id-ecPublicKey', #'ECPoint'{point = _ECPoint} = PublicKey, PublicKeyParams}, - KeyAlg, State) when KeyAlg == ecdh_rsa; - KeyAlg == ecdh_ecdsa -> + KeyAlg, #state{session = Session} = State) when KeyAlg == ecdh_rsa; + KeyAlg == ecdh_ecdsa -> ECDHKey = public_key:generate_key(PublicKeyParams), PremasterSecret = ssl_handshake:premaster_secret(PublicKey, ECDHKey), - master_secret(PremasterSecret, State#state{diffie_hellman_keys = ECDHKey}); - + master_secret(PremasterSecret, State#state{diffie_hellman_keys = ECDHKey, + session = Session#session{ecc = PublicKeyParams}}); %% We do currently not support cipher suites that use fixed DH. %% If we want to implement that the following clause can be used %% to extract DH parameters form cert. @@ -1078,18 +1550,19 @@ handle_peer_cert_key(client, _, handle_peer_cert_key(_, _, _, _, State) -> State. -certify_client(#state{client_certificate_requested = true, role = client, - cert_db = CertDbHandle, - cert_db_ref = CertDbRef, +certify_client(#state{static_env = #static_env{role = client, + cert_db = CertDbHandle, + cert_db_ref = CertDbRef}, + client_certificate_requested = true, 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. -verify_client_cert(#state{client_certificate_requested = true, role = client, +verify_client_cert(#state{static_env = #static_env{role = client}, + client_certificate_requested = true, negotiated_version = Version, private_key = PrivateKey, session = #session{master_secret = MasterSecret, @@ -1098,9 +1571,9 @@ verify_client_cert(#state{client_certificate_requested = true, role = client, tls_handshake_history = Handshake0} = State, Connection) -> case ssl_handshake:client_certificate_verify(OwnCert, MasterSecret, - Version, HashSign, PrivateKey, Handshake0) of + ssl:tls_version(Version), HashSign, PrivateKey, Handshake0) of #certificate_verify{} = Verified -> - Connection:send_handshake(Verified, State); + Connection:queue_handshake(Verified, State); ignore -> State; #alert{} = Alert -> @@ -1113,15 +1586,14 @@ client_certify_and_key_exchange(#state{negotiated_version = Version} = State0, Connection) -> try do_client_certify_and_key_exchange(State0, Connection) of State1 = #state{} -> - State2 = finalize_handshake(State1, certify, Connection), - State3 = State2#state{ - %% Reinitialize - client_certificate_requested = false}, - {Record, State} = Connection:next_record(State3), - Connection:next_state(certify, cipher, Record, State) + {State2, Actions} = finalize_handshake(State1, certify, Connection), + State = State2#state{ + %% Reinitialize + client_certificate_requested = false}, + Connection:next_event(cipher, no_record, State, Actions) catch throw:#alert{} = Alert -> - Connection:handle_own_alert(Alert, Version, certify, State0) + handle_own_alert(Alert, Version, certify, State0) end. do_client_certify_and_key_exchange(State0, Connection) -> @@ -1135,10 +1607,26 @@ server_certify_and_key_exchange(State0, Connection) -> request_client_cert(State2, Connection). certify_client_key_exchange(#encrypted_premaster_secret{premaster_secret= EncPMS}, - #state{private_key = Key} = State, Connection) -> - PremasterSecret = ssl_handshake:premaster_secret(EncPMS, Key), + #state{private_key = Key, client_hello_version = {Major, Minor} = Version} = State, Connection) -> + FakeSecret = make_premaster_secret(Version, rsa), + %% Countermeasure for Bleichenbacher attack always provide some kind of premaster secret + %% and fail handshake later.RFC 5246 section 7.4.7.1. + PremasterSecret = + try ssl_handshake:premaster_secret(EncPMS, Key) of + Secret when erlang:byte_size(Secret) == ?NUM_OF_PREMASTERSECRET_BYTES -> + case Secret of + <<?BYTE(Major), ?BYTE(Minor), Rest/binary>> -> %% Correct + <<?BYTE(Major), ?BYTE(Minor), Rest/binary>>; + <<?BYTE(_), ?BYTE(_), Rest/binary>> -> %% Version mismatch + <<?BYTE(Major), ?BYTE(Minor), Rest/binary>> + end; + _ -> %% erlang:byte_size(Secret) =/= ?NUM_OF_PREMASTERSECRET_BYTES + FakeSecret + catch + #alert{description = ?DECRYPT_ERROR} -> + FakeSecret + end, calculate_master_secret(PremasterSecret, State, Connection, certify, cipher); - certify_client_key_exchange(#client_diffie_hellman_public{dh_public = ClientPublicDhKey}, #state{diffie_hellman_params = #'DHParameter'{} = Params, diffie_hellman_keys = {_, ServerDhPrivateKey}} = State, @@ -1150,26 +1638,36 @@ certify_client_key_exchange(#client_ec_diffie_hellman_public{dh_public = ClientP #state{diffie_hellman_keys = ECDHKey} = State, Connection) -> PremasterSecret = ssl_handshake:premaster_secret(#'ECPoint'{point = ClientPublicEcDhPoint}, ECDHKey), 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_ecdhe_psk_identity{} = ClientKey, + #state{diffie_hellman_keys = ServerEcDhPrivateKey, + ssl_options = + #ssl_options{user_lookup_fun = PSKLookup}} = State, + Connection) -> + PremasterSecret = + ssl_handshake:premaster_secret(ClientKey, ServerEcDhPrivateKey, PSKLookup), + calculate_master_secret(PremasterSecret, State, 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); - certify_client_key_exchange(#client_srp_public{} = ClientKey, #state{srp_params = Params, srp_keys = Key @@ -1177,23 +1675,26 @@ 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 == ecdhe_psk; + Algo == srp_anon -> State; - -certify_server(#state{cert_db = CertDbHandle, - cert_db_ref = CertDbRef, +certify_server(#state{static_env = #static_env{cert_db = CertDbHandle, + cert_db_ref = CertDbRef}, 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. -key_exchange(#state{role = server, key_algorithm = rsa} = State,_) -> +key_exchange(#state{static_env = #static_env{role = server}, key_algorithm = rsa} = State,_) -> State; -key_exchange(#state{role = server, key_algorithm = Algo, +key_exchange(#state{static_env = #static_env{role = server}, key_algorithm = Algo, hashsign_algorithm = HashSignAlgo, diffie_hellman_params = #'DHParameter'{} = Params, private_key = PrivateKey, @@ -1204,65 +1705,66 @@ key_exchange(#state{role = server, key_algorithm = Algo, Algo == dhe_rsa; Algo == dh_anon -> DHKeys = public_key:generate_key(Params), - ConnectionState = + #{security_parameters := SecParams} = ssl_record:pending_connection_state(ConnectionStates0, read), - SecParams = ConnectionState#connection_state.security_parameters, #security_parameters{client_random = ClientRandom, server_random = ServerRandom} = SecParams, - Msg = ssl_handshake:key_exchange(server, Version, {dh, DHKeys, Params, + Msg = ssl_handshake:key_exchange(server, ssl:tls_version(Version), {dh, DHKeys, Params, 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, _) +key_exchange(#state{static_env = #static_env{role = server}, + private_key = #'ECPrivateKey'{parameters = ECCurve} = Key, + key_algorithm = Algo, + session = Session} = State, _) when Algo == ecdh_ecdsa; Algo == ecdh_rsa -> - State#state{diffie_hellman_keys = Key}; -key_exchange(#state{role = server, key_algorithm = Algo, + State#state{diffie_hellman_keys = Key, + session = Session#session{ecc = ECCurve}}; +key_exchange(#state{static_env = #static_env{role = server}, key_algorithm = Algo, hashsign_algorithm = HashSignAlgo, private_key = PrivateKey, + session = #session{ecc = ECCCurve}, connection_states = ConnectionStates0, negotiated_version = Version } = State0, Connection) when Algo == ecdhe_ecdsa; Algo == ecdhe_rsa; Algo == ecdh_anon -> - ECDHKeys = public_key:generate_key(select_curve(State0)), - ConnectionState = + ECDHKeys = public_key:generate_key(ECCCurve), + #{security_parameters := SecParams} = ssl_record:pending_connection_state(ConnectionStates0, read), - SecParams = ConnectionState#connection_state.security_parameters, #security_parameters{client_random = ClientRandom, server_random = ServerRandom} = SecParams, - Msg = ssl_handshake:key_exchange(server, Version, {ecdh, ECDHKeys, - HashSignAlgo, ClientRandom, - ServerRandom, - PrivateKey}), - State = Connection:send_handshake(Msg, State0), + Msg = ssl_handshake:key_exchange(server, ssl:tls_version(Version), + {ecdh, ECDHKeys, + HashSignAlgo, ClientRandom, + ServerRandom, + PrivateKey}), + State = Connection:queue_handshake(Msg, State0), State#state{diffie_hellman_keys = ECDHKeys}; - -key_exchange(#state{role = server, key_algorithm = psk, +key_exchange(#state{static_env = #static_env{role = server}, key_algorithm = psk, ssl_options = #ssl_options{psk_identity = undefined}} = State, _) -> State; -key_exchange(#state{role = server, key_algorithm = psk, +key_exchange(#state{static_env = #static_env{role = server}, key_algorithm = psk, ssl_options = #ssl_options{psk_identity = PskIdentityHint}, hashsign_algorithm = HashSignAlgo, private_key = PrivateKey, connection_states = ConnectionStates0, negotiated_version = Version } = State0, Connection) -> - ConnectionState = + #{security_parameters := SecParams} = ssl_record:pending_connection_state(ConnectionStates0, read), - SecParams = ConnectionState#connection_state.security_parameters, #security_parameters{client_random = ClientRandom, server_random = ServerRandom} = SecParams, - Msg = ssl_handshake:key_exchange(server, Version, {psk, PskIdentityHint, - HashSignAlgo, ClientRandom, - ServerRandom, + Msg = ssl_handshake:key_exchange(server, ssl:tls_version(Version), + {psk, PskIdentityHint, + HashSignAlgo, ClientRandom, + ServerRandom, PrivateKey}), - Connection:send_handshake(Msg, State0); - -key_exchange(#state{role = server, key_algorithm = dhe_psk, + Connection:queue_handshake(Msg, State0); +key_exchange(#state{static_env = #static_env{role = server}, key_algorithm = dhe_psk, ssl_options = #ssl_options{psk_identity = PskIdentityHint}, hashsign_algorithm = HashSignAlgo, diffie_hellman_params = #'DHParameter'{} = Params, @@ -1271,40 +1773,60 @@ key_exchange(#state{role = server, key_algorithm = dhe_psk, negotiated_version = Version } = State0, Connection) -> DHKeys = public_key:generate_key(Params), - ConnectionState = + #{security_parameters := SecParams} = ssl_record:pending_connection_state(ConnectionStates0, read), - 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, ssl:tls_version(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, +key_exchange(#state{static_env = #static_env{role = server}, key_algorithm = ecdhe_psk, + ssl_options = #ssl_options{psk_identity = PskIdentityHint}, + hashsign_algorithm = HashSignAlgo, + private_key = PrivateKey, + session = #session{ecc = ECCCurve}, + connection_states = ConnectionStates0, + negotiated_version = Version + } = State0, Connection) -> + ECDHKeys = public_key:generate_key(ECCCurve), + #{security_parameters := SecParams} = + ssl_record:pending_connection_state(ConnectionStates0, read), + #security_parameters{client_random = ClientRandom, + server_random = ServerRandom} = SecParams, + Msg = ssl_handshake:key_exchange(server, ssl:tls_version(Version), + {ecdhe_psk, + PskIdentityHint, ECDHKeys, + HashSignAlgo, ClientRandom, + ServerRandom, + PrivateKey}), + State = Connection:queue_handshake(Msg, State0), + State#state{diffie_hellman_keys = ECDHKeys}; +key_exchange(#state{static_env = #static_env{role = server}, key_algorithm = rsa_psk, ssl_options = #ssl_options{psk_identity = undefined}} = State, _) -> State; -key_exchange(#state{role = server, key_algorithm = rsa_psk, +key_exchange(#state{static_env = #static_env{role = server}, key_algorithm = rsa_psk, ssl_options = #ssl_options{psk_identity = PskIdentityHint}, hashsign_algorithm = HashSignAlgo, private_key = PrivateKey, connection_states = ConnectionStates0, negotiated_version = Version } = State0, Connection) -> - ConnectionState = + #{security_parameters := SecParams} = ssl_record:pending_connection_state(ConnectionStates0, read), - SecParams = ConnectionState#connection_state.security_parameters, #security_parameters{client_random = ClientRandom, server_random = ServerRandom} = SecParams, - Msg = ssl_handshake:key_exchange(server, Version, {psk, PskIdentityHint, - HashSignAlgo, ClientRandom, - ServerRandom, - PrivateKey}), - Connection:send_handshake(Msg, State0); - -key_exchange(#state{role = server, key_algorithm = Algo, + Msg = ssl_handshake:key_exchange(server, ssl:tls_version(Version), + {psk, PskIdentityHint, + HashSignAlgo, ClientRandom, + ServerRandom, + PrivateKey}), + Connection:queue_handshake(Msg, State0); +key_exchange(#state{static_env = #static_env{role = server}, key_algorithm = Algo, ssl_options = #ssl_options{user_lookup_fun = LookupFun}, hashsign_algorithm = HashSignAlgo, session = #session{srp_username = Username}, @@ -1322,28 +1844,26 @@ key_exchange(#state{role = server, key_algorithm = Algo, Keys0 = {_,_} -> Keys0 end, - ConnectionState = + #{security_parameters := SecParams} = ssl_record:pending_connection_state(ConnectionStates0, read), - SecParams = ConnectionState#connection_state.security_parameters, #security_parameters{client_random = ClientRandom, server_random = ServerRandom} = SecParams, - Msg = ssl_handshake:key_exchange(server, Version, {srp, Keys, SrpParams, - HashSignAlgo, ClientRandom, - ServerRandom, - PrivateKey}), - State = Connection:send_handshake(Msg, State0), + Msg = ssl_handshake:key_exchange(server, ssl:tls_version(Version), + {srp, Keys, SrpParams, + HashSignAlgo, ClientRandom, + ServerRandom, + PrivateKey}), + State = Connection:queue_handshake(Msg, State0), State#state{srp_params = SrpParams, srp_keys = Keys}; - -key_exchange(#state{role = client, +key_exchange(#state{static_env = #static_env{role = client}, key_algorithm = rsa, public_key_info = PublicKeyInfo, negotiated_version = Version, premaster_secret = PremasterSecret} = State0, Connection) -> - Msg = rsa_key_exchange(Version, PremasterSecret, PublicKeyInfo), - Connection:send_handshake(Msg, State0); - -key_exchange(#state{role = client, + Msg = rsa_key_exchange(ssl:tls_version(Version), PremasterSecret, PublicKeyInfo), + Connection:queue_handshake(Msg, State0); +key_exchange(#state{static_env = #static_env{role = client}, key_algorithm = Algorithm, negotiated_version = Version, diffie_hellman_keys = {DhPubKey, _} @@ -1351,46 +1871,57 @@ key_exchange(#state{role = client, when Algorithm == dhe_dss; Algorithm == dhe_rsa; Algorithm == dh_anon -> - Msg = ssl_handshake:key_exchange(client, Version, {dh, DhPubKey}), - Connection:send_handshake(Msg, State0); + Msg = ssl_handshake:key_exchange(client, ssl:tls_version(Version), {dh, DhPubKey}), + Connection:queue_handshake(Msg, State0); -key_exchange(#state{role = client, +key_exchange(#state{static_env = #static_env{role = client}, key_algorithm = Algorithm, negotiated_version = Version, - diffie_hellman_keys = Keys} = State0, Connection) + session = Session, + diffie_hellman_keys = #'ECPrivateKey'{parameters = ECCurve} = Key} = State0, Connection) when Algorithm == ecdhe_ecdsa; Algorithm == ecdhe_rsa; Algorithm == ecdh_ecdsa; Algorithm == ecdh_rsa; Algorithm == ecdh_anon -> - Msg = ssl_handshake:key_exchange(client, Version, {ecdh, Keys}), - Connection:send_handshake(Msg, State0); - -key_exchange(#state{role = client, + Msg = ssl_handshake:key_exchange(client, ssl:tls_version(Version), {ecdh, Key}), + Connection:queue_handshake(Msg, State0#state{session = Session#session{ecc = ECCurve}}); +key_exchange(#state{static_env = #static_env{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); - -key_exchange(#state{role = client, + Msg = ssl_handshake:key_exchange(client, ssl:tls_version(Version), + {psk, SslOpts#ssl_options.psk_identity}), + Connection:queue_handshake(Msg, State0); +key_exchange(#state{static_env = #static_env{role = client}, ssl_options = SslOpts, key_algorithm = dhe_psk, 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); -key_exchange(#state{role = client, + Msg = ssl_handshake:key_exchange(client, ssl:tls_version(Version), + {dhe_psk, + SslOpts#ssl_options.psk_identity, DhPubKey}), + Connection:queue_handshake(Msg, State0); + +key_exchange(#state{static_env = #static_env{role = client}, + ssl_options = SslOpts, + key_algorithm = ecdhe_psk, + negotiated_version = Version, + diffie_hellman_keys = ECDHKeys} = State0, Connection) -> + Msg = ssl_handshake:key_exchange(client, ssl:tls_version(Version), + {ecdhe_psk, + SslOpts#ssl_options.psk_identity, ECDHKeys}), + Connection:queue_handshake(Msg, State0); + +key_exchange(#state{static_env = #static_env{role = client}, ssl_options = SslOpts, key_algorithm = rsa_psk, public_key_info = PublicKeyInfo, negotiated_version = Version, premaster_secret = PremasterSecret} = State0, Connection) -> - Msg = rsa_psk_key_exchange(Version, SslOpts#ssl_options.psk_identity, + Msg = rsa_psk_key_exchange(ssl:tls_version(Version), SslOpts#ssl_options.psk_identity, PremasterSecret, PublicKeyInfo), - Connection:send_handshake(Msg, State0); - -key_exchange(#state{role = client, + Connection:queue_handshake(Msg, State0); +key_exchange(#state{static_env = #static_env{role = client}, key_algorithm = Algorithm, negotiated_version = Version, srp_keys = {ClientPubKey, _}} @@ -1398,8 +1929,8 @@ key_exchange(#state{role = client, when Algorithm == srp_dss; Algorithm == srp_rsa; Algorithm == srp_anon -> - Msg = ssl_handshake:key_exchange(client, Version, {srp, ClientPubKey}), - Connection:send_handshake(Msg, State0). + Msg = ssl_handshake:key_exchange(client, ssl:tls_version(Version), {srp, ClientPubKey}), + Connection:queue_handshake(Msg, State0). rsa_key_exchange(Version, PremasterSecret, PublicKeyInfo = {Algorithm, _, _}) when Algorithm == ?rsaEncryption; @@ -1411,13 +1942,14 @@ rsa_key_exchange(Version, PremasterSecret, PublicKeyInfo = {Algorithm, _, _}) Algorithm == ?sha384WithRSAEncryption; Algorithm == ?sha512WithRSAEncryption -> - ssl_handshake:key_exchange(client, Version, + ssl_handshake:key_exchange(client, ssl:tls_version(Version), {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; @@ -1427,42 +1959,53 @@ rsa_psk_key_exchange(Version, PskIdentity, PremasterSecret, PublicKeyInfo = {Alg Algorithm == ?sha384WithRSAEncryption; Algorithm == ?sha512WithRSAEncryption -> - ssl_handshake:key_exchange(client, Version, + ssl_handshake:key_exchange(client, ssl:tls_version(Version), {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}, - connection_states = ConnectionStates0, - cert_db = CertDbHandle, - cert_db_ref = CertDbRef, +request_client_cert(#state{key_algorithm = Alg} = State, _) + when Alg == dh_anon; Alg == ecdh_anon; + Alg == psk; Alg == dhe_psk; Alg == ecdhe_psk; Alg == rsa_psk; + Alg == srp_dss; Alg == srp_rsa; Alg == srp_anon -> + State; + +request_client_cert(#state{static_env = #static_env{cert_db = CertDbHandle, + cert_db_ref = CertDbRef}, + ssl_options = #ssl_options{verify = verify_peer, + signature_algs = SupportedHashSigns}, + connection_states = ConnectionStates0, negotiated_version = Version} = State0, Connection) -> - #connection_state{security_parameters = - #security_parameters{cipher_suite = CipherSuite}} = + #{security_parameters := + #security_parameters{cipher_suite = CipherSuite}} = ssl_record:pending_connection_state(ConnectionStates0, read), - Msg = ssl_handshake:certificate_request(CipherSuite, CertDbHandle, CertDbRef, Version), - State = Connection:send_handshake(Msg, State0), + TLSVersion = ssl:tls_version(Version), + HashSigns = ssl_handshake:available_signature_algs(SupportedHashSigns, + TLSVersion), + Msg = ssl_handshake:certificate_request(CipherSuite, CertDbHandle, CertDbRef, + HashSigns, TLSVersion), + 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) -> - case ssl_handshake:master_secret(record_cb(Connection), Version, PremasterSecret, +calculate_master_secret(PremasterSecret, + #state{negotiated_version = Version, + connection_states = ConnectionStates0, + session = Session0} = State0, Connection, + _Current, Next) -> + case ssl_handshake:master_secret(ssl:tls_version(Version), PremasterSecret, ConnectionStates0, server) of {MasterSecret, ConnectionStates} -> Session = Session0#session{master_secret = MasterSecret}, - State1 = State0#state{connection_states = ConnectionStates, + State = State0#state{connection_states = ConnectionStates, session = Session}, - {Record, State} = Connection:next_record(State1), - Connection:next_state(Current, Next, Record, State); + Connection:next_event(Next, no_record, State); #alert{} = Alert -> - Connection:handle_own_alert(Alert, Version, certify, State0) + handle_own_alert(Alert, Version, certify, State0) end. finalize_handshake(State0, StateName, Connection) -> @@ -1471,31 +2014,32 @@ finalize_handshake(State0, StateName, Connection) -> ConnectionStates = ssl_record:activate_pending_connection_state(ConnectionStates0, - write), + write, Connection), State2 = State1#state{connection_states = ConnectionStates}, State = next_protocol(State2, Connection), finished(State, StateName, Connection). -next_protocol(#state{role = server} = State, _) -> +next_protocol(#state{static_env = #static_env{role = server}} = State, _) -> State; -next_protocol(#state{next_protocol = undefined} = State, _) -> +next_protocol(#state{negotiated_protocol = undefined} = State, _) -> State; next_protocol(#state{expecting_next_protocol_negotiation = false} = State, _) -> State; -next_protocol(#state{next_protocol = NextProtocol} = State0, Connection) -> +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, +finished(#state{static_env = #static_env{role = Role}, + negotiated_version = Version, session = Session, connection_states = ConnectionStates0, tls_handshake_history = Handshake0} = State0, StateName, Connection) -> MasterSecret = Session#session.master_secret, - Finished = ssl_handshake:finished(Version, Role, + Finished = ssl_handshake:finished(ssl:tls_version(Version), Role, get_current_prf(ConnectionStates0, write), MasterSecret, Handshake0), ConnectionStates = save_verify_data(Role, Finished, ConnectionStates0, StateName), @@ -1511,49 +2055,69 @@ 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) -> + State=#state{session=Session}, 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, + session = Session#session{ecc = ECCurve}}, + Connection, certify, certify); calculate_secret(#server_psk_params{ - hint = IdentityHint}, - State0, Connection) -> + hint = IdentityHint}, + State, 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, no_record, State#state{psk_identity = IdentityHint}); 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), calculate_master_secret(PremasterSecret, State#state{diffie_hellman_keys = Keys}, Connection, certify, certify); +calculate_secret(#server_ecdhe_psk_params{ + dh_params = #server_ecdh_params{curve = ECCurve}} = ServerKey, + #state{ssl_options = #ssl_options{user_lookup_fun = PSKLookup}} = + State=#state{session=Session}, Connection) -> + ECDHKeys = public_key:generate_key(ECCurve), + + PremasterSecret = ssl_handshake:premaster_secret(ServerKey, ECDHKeys, PSKLookup), + calculate_master_secret(PremasterSecret, + State#state{diffie_hellman_keys = ECDHKeys, + session = Session#session{ecc = ECCurve}}, + 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; -master_secret(PremasterSecret, #state{session = Session, - negotiated_version = Version, role = Role, +master_secret(PremasterSecret, #state{static_env = #static_env{role = Role}, + session = Session, + negotiated_version = Version, connection_states = ConnectionStates0} = State) -> - case ssl_handshake:master_secret(tls_record, Version, PremasterSecret, + case ssl_handshake:master_secret(ssl:tls_version(Version), PremasterSecret, ConnectionStates0, Role) of {MasterSecret, ConnectionStates} -> State#state{ @@ -1569,22 +2133,24 @@ generate_srp_server_keys(_SrpParams, 10) -> generate_srp_server_keys(SrpParams = #srp_user{generator = Generator, prime = Prime, verifier = Verifier}, N) -> - case crypto:generate_key(srp, {host, [Verifier, Generator, Prime, '6a']}) of - error -> - generate_srp_server_keys(SrpParams, N+1); + try crypto:generate_key(srp, {host, [Verifier, Generator, Prime, '6a']}) of Keys -> Keys + catch + error:_ -> + generate_srp_server_keys(SrpParams, N+1) end. generate_srp_client_keys(_Generator, _Prime, 10) -> ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER); generate_srp_client_keys(Generator, Prime, N) -> - case crypto:generate_key(srp, {user, [Generator, Prime, '6a']}) of - error -> - generate_srp_client_keys(Generator, Prime, N+1); + try crypto:generate_key(srp, {user, [Generator, Prime, '6a']}) of Keys -> Keys + catch + error:_ -> + generate_srp_client_keys(Generator, Prime, N+1) end. handle_srp_identity(Username, {Fun, UserState}) -> @@ -1602,31 +2168,29 @@ 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, Actions} = finalize_handshake(State0#state{connection_states = ConnectionStates1, session = Session}, cipher, Connection), - Connection:next_state_connection(cipher, ack_connection(State#state{session = Session})). - -select_curve(#state{client_ecc = {[Curve|_], _}}) -> - {namedCurve, Curve}; -select_curve(_) -> - {namedCurve, ?secp256r1}. + {Record, State} = prepare_connection(State1, Connection), + Connection:next_event(connection, Record, State, Actions). is_anonymous(Algo) when Algo == dh_anon; Algo == ecdh_anon; Algo == psk; Algo == dhe_psk; + Algo == ecdhe_psk; Algo == rsa_psk; Algo == srp_anon -> true; @@ -1634,11 +2198,11 @@ is_anonymous(_) -> false. get_current_prf(CStates, Direction) -> - CS = ssl_record:current_connection_state(CStates, Direction), - CS#connection_state.security_parameters#security_parameters.prf_algorithm. + #{security_parameters := SecParams} = ssl_record:current_connection_state(CStates, Direction), + SecParams#security_parameters.prf_algorithm. get_pending_prf(CStates, Direction) -> - CS = ssl_record:pending_connection_state(CStates, Direction), - CS#connection_state.security_parameters#security_parameters.prf_algorithm. + #{security_parameters := SecParams} = ssl_record:pending_connection_state(CStates, Direction), + SecParams#security_parameters.prf_algorithm. opposite_role(client) -> server; @@ -1650,8 +2214,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}; @@ -1661,42 +2225,39 @@ sync_send_all_state_event(FsmPid, Event) -> {error, closed} end. -get_socket_opts(_,_,[], _, Acc) -> +get_socket_opts(_, _,_,[], _, Acc) -> {ok, Acc}; -get_socket_opts(Transport, Socket, [mode | Tags], SockOpts, Acc) -> - get_socket_opts(Transport, Socket, Tags, SockOpts, +get_socket_opts(Connection, Transport, Socket, [mode | Tags], SockOpts, Acc) -> + get_socket_opts(Connection, Transport, Socket, Tags, SockOpts, [{mode, SockOpts#socket_options.mode} | Acc]); -get_socket_opts(Transport, Socket, [packet | Tags], SockOpts, Acc) -> +get_socket_opts(Connection, Transport, Socket, [packet | Tags], SockOpts, Acc) -> case SockOpts#socket_options.packet of {Type, headers} -> - get_socket_opts(Transport, Socket, Tags, SockOpts, [{packet, Type} | Acc]); + get_socket_opts(Connection, Transport, Socket, Tags, SockOpts, [{packet, Type} | Acc]); Type -> - get_socket_opts(Transport, Socket, Tags, SockOpts, [{packet, Type} | Acc]) + get_socket_opts(Connection, Transport, Socket, Tags, SockOpts, [{packet, Type} | Acc]) end; -get_socket_opts(Transport, Socket, [header | Tags], SockOpts, Acc) -> - get_socket_opts(Transport, Socket, Tags, SockOpts, +get_socket_opts(Connection, Transport, Socket, [header | Tags], SockOpts, Acc) -> + get_socket_opts(Connection, Transport, Socket, Tags, SockOpts, [{header, SockOpts#socket_options.header} | Acc]); -get_socket_opts(Transport, Socket, [active | Tags], SockOpts, Acc) -> - get_socket_opts(Transport, Socket, Tags, SockOpts, +get_socket_opts(Connection, Transport, Socket, [active | Tags], SockOpts, Acc) -> + get_socket_opts(Connection, Transport, Socket, Tags, SockOpts, [{active, SockOpts#socket_options.active} | Acc]); -get_socket_opts(Transport, Socket, [Tag | Tags], SockOpts, Acc) -> - try ssl_socket:getopts(Transport, Socket, [Tag]) of - {ok, [Opt]} -> - get_socket_opts(Transport, Socket, Tags, SockOpts, [Opt | Acc]); - {error, Error} -> - {error, {options, {socket_options, Tag, Error}}} - catch - %% So that inet behavior does not crash our process - _:Error -> {error, {options, {socket_options, Tag, Error}}} +get_socket_opts(Connection, Transport, Socket, [Tag | Tags], SockOpts, Acc) -> + case Connection:getopts(Transport, Socket, [Tag]) of + {ok, [Opt]} -> + get_socket_opts(Connection, Transport, Socket, Tags, SockOpts, [Opt | Acc]); + {error, Reason} -> + {error, {options, {socket_options, Tag, Reason}}} end; -get_socket_opts(_, _,Opts, _,_) -> +get_socket_opts(_,_, _,Opts, _,_) -> {error, {options, {socket_options, Opts, function_clause}}}. -set_socket_opts(_,_, [], SockOpts, []) -> +set_socket_opts(_,_,_, [], SockOpts, []) -> {ok, SockOpts}; -set_socket_opts(Transport, Socket, [], SockOpts, Other) -> +set_socket_opts(ConnectionCb, Transport, Socket, [], SockOpts, Other) -> %% Set non emulated options - try ssl_socket:setopts(Transport, Socket, Other) of + try ConnectionCb:setopts(Transport, Socket, Other) of ok -> {ok, SockOpts}; {error, InetError} -> @@ -1707,94 +2268,99 @@ 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, Opts, +set_socket_opts(ConnectionCb, Transport,Socket, [{mode, Mode}| Opts], SockOpts, Other) + when Mode == list; Mode == binary -> + set_socket_opts(ConnectionCb, Transport, Socket, Opts, SockOpts#socket_options{mode = Mode}, Other); -set_socket_opts(_, _, [{mode, _} = Opt| _], SockOpts, _) -> +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, Opts, +set_socket_opts(ConnectionCb, 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(ConnectionCb, Transport, Socket, Opts, SockOpts#socket_options{packet = Packet}, Other); -set_socket_opts(_, _, [{packet, _} = Opt| _], SockOpts, _) -> +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, Opts, +set_socket_opts(ConnectionCb, Transport, Socket, [{header, Header}| Opts], SockOpts, Other) + when is_integer(Header) -> + set_socket_opts(ConnectionCb, Transport, Socket, Opts, SockOpts#socket_options{header = Header}, Other); -set_socket_opts(_, _, [{header, _} = Opt| _], SockOpts, _) -> +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, Opts, +set_socket_opts(ConnectionCb, Transport, Socket, [{active, Active}| Opts], SockOpts, Other) + when Active == once; + Active == true; + Active == false -> + set_socket_opts(ConnectionCb, Transport, Socket, Opts, SockOpts#socket_options{active = Active}, Other); -set_socket_opts(_, _, [{active, _} = Opt| _], SockOpts, _) -> +set_socket_opts(_,_, _, [{active, _} = Opt| _], SockOpts, _) -> {{error, {options, {socket_options, Opt}} }, SockOpts}; -set_socket_opts(Transport, Socket, [Opt | Opts], SockOpts, Other) -> - set_socket_opts(Transport, Socket, Opts, SockOpts, [Opt | Other]). - -start_or_recv_cancel_timer(infinity, _RecvFrom) -> - undefined; -start_or_recv_cancel_timer(Timeout, RecvFrom) -> - erlang:send_after(Timeout, self(), {cancel_start_or_recv, RecvFrom}). - -get_timeout(#state{ssl_options=#ssl_options{hibernate_after = undefined}}) -> - infinity; -get_timeout(#state{ssl_options=#ssl_options{hibernate_after = HibernateAfter}}) -> - HibernateAfter. - -terminate_alert(Reason, Version, ConnectionStates) when Reason == normal; - Reason == user_close -> - {BinAlert, _} = ssl_alert:encode(?ALERT_REC(?WARNING, ?CLOSE_NOTIFY), - Version, ConnectionStates), - BinAlert; -terminate_alert({shutdown, _}, Version, ConnectionStates) -> - {BinAlert, _} = ssl_alert:encode(?ALERT_REC(?WARNING, ?CLOSE_NOTIFY), - Version, ConnectionStates), - BinAlert; - -terminate_alert(_, Version, ConnectionStates) -> - {BinAlert, _} = ssl_alert:encode(?ALERT_REC(?FATAL, ?INTERNAL_ERROR), - Version, ConnectionStates), - BinAlert. - -handle_unrecv_data(StateName, #state{socket = Socket, transport_cb = Transport, - protocol_cb = Connection} = State) -> - ssl_socket:setopts(Transport, Socket, [{active, false}]), - case Transport:recv(Socket, 0, 0) of - {error, closed} -> - ok; - {ok, Data} -> - Connection:handle_close_alert(Data, StateName, State) - end. - -handle_trusted_certs_db(#state{ssl_options = #ssl_options{cacertfile = <<>>, cacerts = []}}) -> +set_socket_opts(ConnectionCb, Transport, Socket, [Opt | Opts], SockOpts, Other) -> + set_socket_opts(ConnectionCb, Transport, Socket, Opts, SockOpts, [Opt | Other]). + + + +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}. + +map_extensions(#hello_extensions{renegotiation_info = RenegotiationInfo, + signature_algs = SigAlg, + alpn = Alpn, + next_protocol_negotiation = Next, + srp = SRP, + ec_point_formats = ECPointFmt, + elliptic_curves = ECCCurves, + sni = SNI}) -> + #{renegotiation_info => ssl_handshake:extension_value(RenegotiationInfo), + signature_algs => ssl_handshake:extension_value(SigAlg), + alpn => ssl_handshake:extension_value(Alpn), + srp => ssl_handshake:extension_value(SRP), + next_protocol => ssl_handshake:extension_value(Next), + ec_point_formats => ssl_handshake:extension_value(ECPointFmt), + elliptic_curves => ssl_handshake:extension_value(ECCCurves), + sni => ssl_handshake:extension_value(SNI)}. + +terminate_alert(normal) -> + ?ALERT_REC(?WARNING, ?CLOSE_NOTIFY); +terminate_alert({Reason, _}) when Reason == close; + Reason == shutdown -> + ?ALERT_REC(?WARNING, ?CLOSE_NOTIFY); +terminate_alert(_) -> + ?ALERT_REC(?FATAL, ?INTERNAL_ERROR). + +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, - cert_db = CertDb, - ssl_options = #ssl_options{cacertfile = <<>>}}) -> +handle_trusted_certs_db(#state{static_env = #static_env{cert_db_ref = Ref, + cert_db = CertDb}, + ssl_options = #ssl_options{cacertfile = <<>>}}) when CertDb =/= undefined -> %% Certs provided as DER directly can not be shared %% 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 +handle_trusted_certs_db(#state{static_env = #static_env{file_ref_db = undefined}}) -> + %% 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, +handle_trusted_certs_db(#state{static_env = #static_env{cert_db_ref = Ref, + file_ref_db = RefDb}, ssl_options = #ssl_options{cacertfile = File}}) -> case ssl_pkix_db:ref_count(Ref, RefDb, -1) of 0 -> @@ -1803,29 +2369,29 @@ 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. - -ack_connection(#state{renegotiation = {true, Initiater}} = State) - when Initiater == internal; - Initiater == peer -> +prepare_connection(#state{renegotiation = Renegotiate, + start_or_recv_from = RecvFrom} = State0, Connection) + when Renegotiate =/= {false, first}, + RecvFrom =/= undefined -> + State = Connection:reinit(State0), + {no_record, ack_connection(State)}; +prepare_connection(State0, Connection) -> + State = Connection:reinit(State0), + {no_record, ack_connection(State)}. + +ack_connection(#state{renegotiation = {true, Initiater}} = State) when Initiater == peer; + Initiater == internal -> 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. @@ -1835,6 +2401,11 @@ cancel_timer(Timer) -> erlang:cancel_timer(Timer), ok. +session_handle_params(#server_ecdh_params{curve = ECCurve}, Session) -> + Session#session{ecc = ECCurve}; +session_handle_params(_, Session) -> + Session. + register_session(client, Host, Port, #session{is_resumable = new} = Session0) -> Session = Session0#session{is_resumable = true}, ssl_manager:register_session(Host, Port, Session), @@ -1846,47 +2417,344 @@ 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) -> +host_id(client, _Host, #ssl_options{server_name_indication = Hostname}) when is_list(Hostname) -> + Hostname; +host_id(_, Host, _) -> + Host. + +handle_new_session(NewId, CipherSuite, Compression, + #state{static_env = #static_env{protocol_cb = Connection}, + session = Session0 + } = 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). - -handle_resumed_session(SessId, #state{connection_states = ConnectionStates0, - negotiated_version = Version, - host = Host, port = Port, - protocol_cb = Connection, - session_cache = Cache, - session_cache_cb = CacheCb} = State0) -> + Connection:next_event(certify, no_record, State0#state{session = Session}). + +handle_resumed_session(SessId, #state{static_env = #static_env{host = Host, + port = Port, + protocol_cb = Connection, + session_cache = Cache, + session_cache_cb = CacheCb}, + connection_states = ConnectionStates0, + negotiated_version = Version + } = State) -> Session = CacheCb:lookup(Cache, {{Host, Port}, SessId}), - case ssl_handshake:master_secret(tls_record, Version, Session, + case ssl_handshake:master_secret(ssl:tls_version(Version), Session, ConnectionStates0, client) of {_, ConnectionStates} -> - {Record, State} = - Connection:next_record(State0#state{ - connection_states = ConnectionStates, - session = Session}), - Connection:next_state(hello, abbreviated, Record, State); + Connection:next_event(abbreviated, no_record, State#state{ + connection_states = ConnectionStates, + session = Session}); #alert{} = Alert -> - Connection:handle_own_alert(Alert, Version, hello, State0) + handle_own_alert(Alert, Version, hello, State) 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. -negotiated_hashsign(undefined, Alg, Version) -> +negotiated_hashsign(undefined, KexAlg, PubKeyInfo, Version) -> %% Not negotiated choose default - case is_anonymous(Alg) of + case is_anonymous(KexAlg) of true -> {null, anon}; false -> - ssl_handshake:select_hashsign_algs(Alg, Version) + {PubAlg, _, _} = PubKeyInfo, + ssl_handshake:select_hashsign_algs(undefined, PubAlg, Version) end; -negotiated_hashsign(HashSign = {_, _}, _, _) -> +negotiated_hashsign(HashSign = {_, _}, _, _, _) -> HashSign. +ssl_options_list(SslOptions) -> + Fileds = record_info(fields, ssl_options), + Values = tl(tuple_to_list(SslOptions)), + ssl_options_list(Fileds, Values, []). + +ssl_options_list([],[], Acc) -> + lists:reverse(Acc); +%% Skip internal options, only return user options +ssl_options_list([protocol | Keys], [_ | Values], Acc) -> + ssl_options_list(Keys, Values, Acc); +ssl_options_list([erl_dist | Keys], [_ | Values], Acc) -> + ssl_options_list(Keys, Values, Acc); +ssl_options_list([renegotiate_at | Keys], [_ | Values], Acc) -> + ssl_options_list(Keys, Values, Acc); +ssl_options_list([ciphers = Key | Keys], [Value | Values], Acc) -> + ssl_options_list(Keys, Values, + [{Key, lists:map( + fun(Suite) -> + ssl_cipher_format:suite_definition(Suite) + end, Value)} + | 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{static_env = #static_env{protocol_cb = Connection}, + user_data_buffer = <<>>} = State0) -> + case Connection:next_event(StateName0, no_record, State0) 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; +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{static_env = #static_env{protocol_cb = Connection}} = State0) -> + case read_application_data(<<>>, State0) of + {stop, _, _} = Stop -> + Stop; + {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. + + +%% Picks ClientData +get_data(_, _, <<>>) -> + {more, <<>>}; +%% Recv timed out save buffer data until next recv +get_data(#socket_options{active=false}, undefined, Buffer) -> + {passive, Buffer}; +get_data(#socket_options{active=Active, packet=Raw}, BytesToRead, Buffer) + when Raw =:= raw; Raw =:= 0 -> %% Raw Mode + if + Active =/= false orelse BytesToRead =:= 0 -> + %% Active true or once, or passive mode recv(0) + {ok, Buffer, <<>>}; + byte_size(Buffer) >= BytesToRead -> + %% Passive Mode, recv(Bytes) + <<Data:BytesToRead/binary, Rest/binary>> = Buffer, + {ok, Data, Rest}; + true -> + %% Passive Mode not enough data + {more, Buffer} + end; +get_data(#socket_options{packet=Type, packet_size=Size}, _, Buffer) -> + PacketOpts = [{packet_size, Size}], + case decode_packet(Type, Buffer, PacketOpts) of + {more, _} -> + {more, Buffer}; + Decoded -> + Decoded + end. + +decode_packet({http, headers}, Buffer, PacketOpts) -> + decode_packet(httph, Buffer, PacketOpts); +decode_packet({http_bin, headers}, Buffer, PacketOpts) -> + decode_packet(httph_bin, Buffer, PacketOpts); +decode_packet(Type, Buffer, PacketOpts) -> + erlang:decode_packet(Type, Buffer, PacketOpts). + +%% Just like with gen_tcp sockets, an ssl socket that has been configured with +%% {packet, http} (or {packet, http_bin}) will automatically switch to expect +%% HTTP headers after it sees a HTTP Request or HTTP Response line. We +%% represent the current state as follows: +%% #socket_options.packet =:= http: Expect a HTTP Request/Response line +%% #socket_options.packet =:= {http, headers}: Expect HTTP Headers +%% Note that if the user has explicitly configured the socket to expect +%% HTTP headers using the {packet, httph} option, we don't do any automatic +%% switching of states. +deliver_app_data( + CPids, Transport, Socket, + #socket_options{active=Active, packet=Type} = SOpts, + Data, Pid, From, Tracker, Connection) -> + %% + send_or_reply( + Active, Pid, From, + format_reply( + CPids, Transport, Socket, SOpts, Data, Tracker, Connection)), + SO = + case Data of + {P, _, _, _} + when ((P =:= http_request) or (P =:= http_response)), + ((Type =:= http) or (Type =:= http_bin)) -> + SOpts#socket_options{packet={Type, headers}}; + http_eoh when tuple_size(Type) =:= 2 -> + %% End of headers - expect another Request/Response line + {Type1, headers} = Type, + SOpts#socket_options{packet=Type1}; + _ -> + SOpts + end, + case Active of + once -> + SO#socket_options{active=false}; + _ -> + SO + end. + +format_reply(_, _, _,#socket_options{active = false, mode = Mode, packet = Packet, + header = Header}, Data, _, _) -> + {ok, do_format_reply(Mode, Packet, Header, Data)}; +format_reply(CPids, Transport, Socket, #socket_options{active = _, mode = Mode, packet = Packet, + header = Header}, Data, Tracker, Connection) -> + {ssl, Connection:socket(CPids, Transport, Socket, Connection, Tracker), + do_format_reply(Mode, Packet, Header, Data)}. + +deliver_packet_error(CPids, Transport, Socket, + SO= #socket_options{active = Active}, Data, Pid, From, Tracker, Connection) -> + send_or_reply(Active, Pid, From, format_packet_error(CPids, + Transport, Socket, SO, Data, Tracker, Connection)). + +format_packet_error(_, _, _,#socket_options{active = false, mode = Mode}, Data, _, _) -> + {error, {invalid_packet, do_format_reply(Mode, raw, 0, Data)}}; +format_packet_error(CPids, Transport, Socket, #socket_options{active = _, mode = Mode}, + Data, Tracker, Connection) -> + {ssl_error, Connection:socket(CPids, Transport, Socket, Connection, Tracker), + {invalid_packet, do_format_reply(Mode, raw, 0, Data)}}. + +do_format_reply(binary, _, N, Data) when N > 0 -> % Header mode + header(N, Data); +do_format_reply(binary, _, _, Data) -> + Data; +do_format_reply(list, Packet, _, Data) + when Packet == http; Packet == {http, headers}; + Packet == http_bin; Packet == {http_bin, headers}; + Packet == httph; Packet == httph_bin -> + Data; +do_format_reply(list, _,_, Data) -> + binary_to_list(Data). + +header(0, <<>>) -> + <<>>; +header(_, <<>>) -> + []; +header(0, Binary) -> + Binary; +header(N, Binary) -> + <<?BYTE(ByteN), NewBinary/binary>> = Binary, + [ByteN | header(N-1, NewBinary)]. + +send_or_reply(false, _Pid, From, Data) when From =/= undefined -> + gen_statem:reply(From, Data); +%% Can happen when handling own alert or tcp error/close and there is +%% no outstanding gen_fsm sync events +send_or_reply(false, no_pid, _, _) -> + ok; +send_or_reply(_, Pid, _From, Data) -> + send_user(Pid, Data). + +send_user(Pid, Msg) -> + Pid ! Msg, + ok. + +alert_user(Pids, Transport, Tracker, Socket, connection, Opts, Pid, From, Alert, Role, Connection) -> + alert_user(Pids, Transport, Tracker, Socket, Opts#socket_options.active, Pid, From, Alert, Role, Connection); +alert_user(Pids, Transport, Tracker, Socket,_, _, _, From, Alert, Role, Connection) -> + alert_user(Pids, Transport, Tracker, Socket, From, Alert, Role, Connection). + +alert_user(Pids, Transport, Tracker, Socket, From, Alert, Role, Connection) -> + alert_user(Pids, Transport, Tracker, Socket, false, no_pid, From, Alert, Role, Connection). + +alert_user(_, _, _, _, false = Active, Pid, From, Alert, Role, _) when From =/= undefined -> + %% If there is an outstanding ssl_accept | recv + %% From will be defined and send_or_reply will + %% send the appropriate error message. + ReasonCode = ssl_alert:reason_code(Alert, Role), + send_or_reply(Active, Pid, From, {error, ReasonCode}); +alert_user(Pids, Transport, Tracker, Socket, Active, Pid, From, Alert, Role, Connection) -> + case ssl_alert:reason_code(Alert, Role) of + closed -> + send_or_reply(Active, Pid, From, + {ssl_closed, Connection:socket(Pids, + Transport, Socket, Connection, Tracker)}); + ReasonCode -> + send_or_reply(Active, Pid, From, + {ssl_error, Connection:socket(Pids, + Transport, Socket, Connection, Tracker), ReasonCode}) + end. + +log_alert(true, Role, ProtocolName, StateName, #alert{role = Role} = Alert) -> + Txt = ssl_alert:own_alert_txt(Alert), + error_logger:info_report(io_lib:format("~s ~p: In state ~p ~s\n", [ProtocolName, Role, StateName, Txt])); +log_alert(true, Role, ProtocolName, StateName, Alert) -> + Txt = ssl_alert:alert_txt(Alert), + error_logger:info_report(io_lib:format("~s ~p: In state ~p ~s\n", [ProtocolName, Role, StateName, Txt])); +log_alert(false, _, _, _, _) -> + ok. + +invalidate_session(client, Host, Port, Session) -> + ssl_manager:invalidate_session(Host, Port, Session); +invalidate_session(server, _, Port, Session) -> + ssl_manager:invalidate_session(Port, Session). + +handle_sni_extension(undefined, State) -> + State; +handle_sni_extension(#sni{hostname = Hostname}, #state{static_env = #static_env{role = Role} = InitStatEnv0} = State0) -> + NewOptions = update_ssl_options_from_sni(State0#state.ssl_options, Hostname), + case NewOptions of + undefined -> + State0; + _ -> + {ok, #{cert_db_ref := Ref, + cert_db_handle := CertDbHandle, + fileref_db_handle := FileRefHandle, + session_cache := CacheHandle, + crl_db_info := CRLDbHandle, + private_key := Key, + dh_params := DHParams, + own_certificate := OwnCert}} = + ssl_config:init(NewOptions, Role), + State0#state{ + session = State0#state.session#session{own_certificate = OwnCert}, + static_env = InitStatEnv0#static_env{ + file_ref_db = FileRefHandle, + cert_db_ref = Ref, + cert_db = CertDbHandle, + crl_db = CRLDbHandle, + session_cache = CacheHandle + }, + private_key = Key, + diffie_hellman_params = DHParams, + ssl_options = NewOptions, + sni_hostname = Hostname + } + end. + +update_ssl_options_from_sni(OrigSSLOptions, SNIHostname) -> + SSLOption = + case OrigSSLOptions#ssl_options.sni_fun of + undefined -> + proplists:get_value(SNIHostname, + OrigSSLOptions#ssl_options.sni_hosts); + SNIFun -> + SNIFun(SNIHostname) + end, + case SSLOption of + undefined -> + undefined; + _ -> + ssl:handle_options(SSLOption, OrigSSLOptions) + end. + +new_emulated([], EmOpts) -> + EmOpts; +new_emulated(NewEmOpts, _) -> + NewEmOpts. + +-compile({inline, [bincat/2]}). +bincat(<<>>, B) -> + B; +bincat(A, <<>>) -> + A; +bincat(A, B) -> + <<A/binary, B/binary>>. diff --git a/lib/ssl/src/ssl_connection.hrl b/lib/ssl/src/ssl_connection.hrl index b9a1ef3a84..dc8aa7619b 100644 --- a/lib/ssl/src/ssl_connection.hrl +++ b/lib/ssl/src/ssl_connection.hrl @@ -1,19 +1,19 @@ - %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2013-2014. All Rights Reserved. +%% Copyright Ericsson AB 2013-2018. 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 %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. +%% http://www.apache.org/licenses/LICENSE-2.0 %% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% 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% %% @@ -33,55 +33,78 @@ -include("ssl_cipher.hrl"). -include_lib("public_key/include/public_key.hrl"). +-record(static_env, { + role :: client | server, + transport_cb :: atom(), % callback module + protocol_cb :: tls_connection | dtls_connection, + data_tag :: atom(), % ex tcp. + close_tag :: atom(), % ex tcp_closed + error_tag :: atom(), % ex tcp_error + host :: string() | inet:ip_address(), + port :: integer(), + socket :: port() | tuple(), %% TODO: dtls socket + cert_db :: reference() | 'undefined', + session_cache :: db_handle(), + session_cache_cb :: atom(), + crl_db :: term(), + file_ref_db :: db_handle(), + cert_db_ref :: certdb_ref() | 'undefined', + tracker :: pid() | 'undefined' %% Tracker process for listen socket + }). -record(state, { - role :: client | server, - user_application :: {Monitor::reference(), User::pid()}, - transport_cb :: atom(), % callback module - protocol_cb :: tls_connection | dtls_connection, - data_tag :: atom(), % ex tcp. - close_tag :: atom(), % ex tcp_closed - error_tag :: atom(), % ex tcp_error - host :: string() | inet:ip_address(), - port :: integer(), - socket :: port(), - ssl_options :: #ssl_options{}, - 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(), - session :: #session{} | secret_printout(), - session_cache :: db_handle(), - session_cache_cb :: atom(), - negotiated_version :: ssl_record:ssl_version(), - 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(), - 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() , - file_ref_db :: db_handle(), - cert_db_ref :: certdb_ref(), - bytes_to_read :: undefined | integer(), %% bytes to read in passive mode - user_data_buffer :: undefined | binary() | secret_printout(), + static_env :: #static_env{}, + %% Change seldome + user_application :: {Monitor::reference(), User::pid()}, + ssl_options :: #ssl_options{}, + socket_options :: #socket_options{}, + session :: #session{} | secret_printout(), + allow_renegotiate = true ::boolean(), + terminated = false ::boolean() | closed, + negotiated_version :: ssl_record:ssl_version() | 'undefined', + bytes_to_read :: undefined | integer(), %% bytes to read in passive mode + downgrade, + + %% Changed often + connection_states :: ssl_record:connection_states() | secret_printout(), + protocol_buffers :: term() | secret_printout() , %% #protocol_buffers{} from tls_record.hrl or dtls_recor.hr + user_data_buffer :: undefined | binary() | secret_printout(), + + %% Used only in HS + unprocessed_handshake_events = 0 :: integer(), + tls_handshake_history :: ssl_handshake:ssl_handshake_history() | secret_printout() + | 'undefined', + client_hello_version :: ssl_record:ssl_version() | 'undefined', + client_certificate_requested = false :: boolean(), + key_algorithm :: ssl_cipher_format:key_algo(), + hashsign_algorithm = {undefined, undefined}, + cert_hashsign_algorithm = {undefined, undefined}, + 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() | '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', renegotiation :: undefined | {boolean(), From::term() | internal | peer}, - start_or_recv_from :: term(), - timer :: undefined | reference(), % start_or_recive_timer - send_queue :: queue:queue(), - terminated = false ::boolean(), - allow_renegotiate = true ::boolean(), - expecting_next_protocol_negotiation = false ::boolean(), - expecting_finished = false ::boolean(), - next_protocol = undefined :: undefined | binary(), - client_ecc, % {Curves, PointFmt} - tracker :: pid() %% Tracker process for listen socket - }). + start_or_recv_from :: term(), + timer :: undefined | reference(), % start_or_recive_timer + hello, %%:: #client_hello{} | #server_hello{}, + expecting_next_protocol_negotiation = false ::boolean(), + expecting_finished = false ::boolean(), + next_protocol = undefined :: undefined | binary(), + negotiated_protocol, + sni_hostname = undefined, + flight_buffer = [] :: list() | map(), %% Buffer of TLS/DTLS records, used during the TLS handshake + %% to when possible pack more than one 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. In DTLS we need to track DTLS handshake seqnr + flight_state = reliable, %% reliable | {retransmit, integer()}| {waiting, ref(), integer()} - last two is used in DTLS over udp. + erl_dist_handle = undefined :: erlang:dist_handle() | undefined, + protocol_specific = #{} :: map() + }). + -define(DEFAULT_DIFFIE_HELLMAN_PARAMS, #'DHParameter'{prime = ?DEFAULT_DIFFIE_HELLMAN_PRIME, diff --git a/lib/ssl/src/ssl_connection_sup.erl b/lib/ssl/src/ssl_connection_sup.erl new file mode 100644 index 0000000000..934dd39df5 --- /dev/null +++ b/lib/ssl/src/ssl_connection_sup.erl @@ -0,0 +1,101 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1998-2018. 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_connection_sup). + +-behaviour(supervisor). + +%% API +-export([start_link/0]). + +%% Supervisor callback +-export([init/1]). + +%%%========================================================================= +%%% API +%%%========================================================================= + +-spec start_link() -> {ok, pid()} | ignore | {error, term()}. + +start_link() -> + supervisor:start_link({local, ?MODULE}, ?MODULE, []). + +%%%========================================================================= +%%% Supervisor callback +%%%========================================================================= + +init([]) -> + + TLSConnetionManager = tls_connection_manager_child_spec(), + %% Handles emulated options so that they inherited by the accept + %% socket, even when setopts is performed on the listen socket + ListenOptionsTracker = listen_options_tracker_child_spec(), + + DTLSConnetionManager = dtls_connection_manager_child_spec(), + DTLSListeners = dtls_listeners_spec(), + + {ok, {{one_for_one, 10, 3600}, [TLSConnetionManager, + ListenOptionsTracker, + DTLSConnetionManager, + DTLSListeners + ]}}. + + +%%-------------------------------------------------------------------- +%%% Internal functions +%%-------------------------------------------------------------------- + +tls_connection_manager_child_spec() -> + Name = tls_connection, + StartFunc = {tls_connection_sup, start_link, []}, + Restart = permanent, + Shutdown = 4000, + Modules = [tls_connection_sup], + Type = supervisor, + {Name, StartFunc, Restart, Shutdown, Type, Modules}. + +dtls_connection_manager_child_spec() -> + Name = dtls_connection, + StartFunc = {dtls_connection_sup, start_link, []}, + Restart = permanent, + Shutdown = 4000, + Modules = [dtls_connection_sup], + Type = supervisor, + {Name, StartFunc, Restart, Shutdown, Type, Modules}. + +listen_options_tracker_child_spec() -> + Name = tls_socket, + StartFunc = {ssl_listen_tracker_sup, start_link, []}, + Restart = permanent, + Shutdown = 4000, + Modules = [tls_socket], + Type = supervisor, + {Name, StartFunc, Restart, Shutdown, Type, Modules}. + +dtls_listeners_spec() -> + Name = dtls_listener, + StartFunc = {dtls_listener_sup, start_link, []}, + Restart = permanent, + Shutdown = 4000, + Modules = [], + Type = supervisor, + {Name, StartFunc, Restart, Shutdown, Type, Modules}. diff --git a/lib/ssl/src/ssl_crl.erl b/lib/ssl/src/ssl_crl.erl new file mode 100644 index 0000000000..888a75bfd6 --- /dev/null +++ b/lib/ssl/src/ssl_crl.erl @@ -0,0 +1,115 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2015-2017. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% 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% + +%---------------------------------------------------------------------- +%% Purpose: CRL handling +%%---------------------------------------------------------------------- + +-module(ssl_crl). + +-include("ssl_alert.hrl"). +-include("ssl_internal.hrl"). +-include_lib("public_key/include/public_key.hrl"). + +-export([trusted_cert_and_path/3]). + +trusted_cert_and_path(CRL, {SerialNumber, Issuer},{_, {Db, DbRef}} = DbHandle) -> + case ssl_pkix_db:lookup_trusted_cert(Db, DbRef, SerialNumber, Issuer) of + undefined -> + trusted_cert_and_path(CRL, issuer_not_found, DbHandle); + {ok, {_, OtpCert}} -> + {ok, Root, Chain} = ssl_certificate:certificate_chain(OtpCert, Db, DbRef), + {ok, Root, lists:reverse(Chain)} + end; +trusted_cert_and_path(CRL, issuer_not_found, {CertPath, {Db, DbRef}}) -> + case find_issuer(CRL, {certpath, + [{Der, public_key:pkix_decode_cert(Der,otp)} || Der <- CertPath]}) of + {ok, OtpCert} -> + {ok, Root, Chain} = ssl_certificate:certificate_chain(OtpCert, Db, DbRef), + {ok, Root, lists:reverse(Chain)}; + {error, issuer_not_found} -> + trusted_cert_and_path(CRL, issuer_not_found, {Db, DbRef}) + end; +trusted_cert_and_path(CRL, issuer_not_found, {Db, DbRef} = DbInfo) -> + case find_issuer(CRL, DbInfo) of + {ok, OtpCert} -> + {ok, Root, Chain} = ssl_certificate:certificate_chain(OtpCert, Db, DbRef), + {ok, Root, lists:reverse(Chain)}; + {error, issuer_not_found} -> + {error, unknown_ca} + end. + +find_issuer(CRL, {certpath = Db, DbRef}) -> + Issuer = public_key:pkix_normalize_name(public_key:pkix_crl_issuer(CRL)), + IsIssuerFun = + fun({_Der,ErlCertCandidate}, Acc) -> + verify_crl_issuer(CRL, ErlCertCandidate, Issuer, Acc); + (_, Acc) -> + Acc + end, + find_issuer(IsIssuerFun, Db, DbRef); +find_issuer(CRL, {Db, DbRef}) -> + Issuer = public_key:pkix_normalize_name(public_key:pkix_crl_issuer(CRL)), + IsIssuerFun = + fun({_Key, {_Der,ErlCertCandidate}}, Acc) -> + verify_crl_issuer(CRL, ErlCertCandidate, Issuer, Acc); + (_, Acc) -> + Acc + end, + find_issuer(IsIssuerFun, Db, DbRef). + +find_issuer(IsIssuerFun, certpath, Certs) -> + try lists:foldl(IsIssuerFun, issuer_not_found, Certs) of + issuer_not_found -> + {error, issuer_not_found} + catch + {ok, _} = Result -> + Result + end; +find_issuer(IsIssuerFun, extracted, CertsData) -> + Certs = [Entry || {decoded, Entry} <- CertsData], + try lists:foldl(IsIssuerFun, issuer_not_found, Certs) of + issuer_not_found -> + {error, issuer_not_found} + catch + {ok, _} = Result -> + Result + end; +find_issuer(IsIssuerFun, Db, _) -> + try ssl_pkix_db:foldl(IsIssuerFun, issuer_not_found, Db) of + issuer_not_found -> + {error, issuer_not_found} + catch + {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 + Issuer -> + case public_key:pkix_crl_verify(CRL, ErlCertCandidate) of + true -> + throw({ok, ErlCertCandidate}); + false -> + NotIssuer + end; + _ -> + NotIssuer + end. diff --git a/lib/ssl/src/ssl_crl_cache.erl b/lib/ssl/src/ssl_crl_cache.erl new file mode 100644 index 0000000000..9c1af86eeb --- /dev/null +++ b/lib/ssl/src/ssl_crl_cache.erl @@ -0,0 +1,181 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2015-2018. 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% + +%---------------------------------------------------------------------- +%% Purpose: Simple default CRL cache +%%---------------------------------------------------------------------- + +-module(ssl_crl_cache). + +-include("ssl_internal.hrl"). +-include_lib("public_key/include/public_key.hrl"). + +-behaviour(ssl_crl_cache_api). + +-export([lookup/3, select/2, fresh_crl/2]). +-export([insert/1, insert/2, delete/1]). + +%%==================================================================== +%% Cache callback API +%%==================================================================== + +lookup(#'DistributionPoint'{distributionPoint = {fullName, Names}}, + _Issuer, + CRLDbInfo) -> + get_crls(Names, CRLDbInfo); +lookup(_,_,_) -> + not_available. + +select(Issuer, {{_Cache, Mapping},_}) -> + case ssl_pkix_db:lookup(Issuer, Mapping) of + undefined -> + []; + CRLs -> + CRLs + end. + +fresh_crl(#'DistributionPoint'{distributionPoint = {fullName, Names}}, CRL) -> + case get_crls(Names, undefined) of + not_available -> + CRL; + [NewCRL] -> + NewCRL + end. + +%%==================================================================== +%% API +%%==================================================================== + +insert(CRLs) -> + insert(?NO_DIST_POINT, CRLs). + +insert(URI, {file, File}) when is_list(URI) -> + case file:read_file(File) of + {ok, PemBin} -> + PemEntries = public_key:pem_decode(PemBin), + CRLs = [ CRL || {'CertificateList', CRL, not_encrypted} + <- PemEntries], + do_insert(URI, CRLs); + Error -> + Error + end; +insert(URI, {der, CRLs}) -> + do_insert(URI, CRLs). + +delete({file, File}) -> + case file:read_file(File) of + {ok, PemBin} -> + PemEntries = public_key:pem_decode(PemBin), + CRLs = [ CRL || {'CertificateList', CRL, not_encrypted} + <- PemEntries], + ssl_manager:delete_crls({?NO_DIST_POINT, CRLs}); + Error -> + Error + end; +delete({der, CRLs}) -> + ssl_manager:delete_crls({?NO_DIST_POINT, CRLs}); + +delete(URI) -> + case uri_string:normalize(URI, [return_map]) of + #{scheme := "http", path := Path} -> + ssl_manager:delete_crls(string:trim(Path, leading, "/")); + _ -> + {error, {only_http_distribution_points_supported, URI}} + end. + +%%-------------------------------------------------------------------- +%%% Internal functions +%%-------------------------------------------------------------------- +do_insert(URI, CRLs) -> + case uri_string:normalize(URI, [return_map]) of + #{scheme := "http", path := Path} -> + ssl_manager:insert_crls(string:trim(Path, leading, "/"), CRLs); + _ -> + {error, {only_http_distribution_points_supported, URI}} + end. + +get_crls([], _) -> + not_available; +get_crls([{uniformResourceIdentifier, "http"++_ = URL} | Rest], + CRLDbInfo) -> + case cache_lookup(URL, CRLDbInfo) of + [] -> + handle_http(URL, Rest, CRLDbInfo); + CRLs -> + CRLs + end; +get_crls([ _| Rest], CRLDbInfo) -> + %% unsupported CRL location + get_crls(Rest, CRLDbInfo). + +http_lookup(URL, Rest, CRLDbInfo, Timeout) -> + case application:ensure_started(inets) of + ok -> + http_get(URL, Rest, CRLDbInfo, Timeout); + _ -> + get_crls(Rest, CRLDbInfo) + end. + +http_get(URL, Rest, CRLDbInfo, Timeout) -> + case httpc:request(get, {URL, [{"connection", "close"}]}, + [{timeout, Timeout}], [{body_format, binary}]) of + {ok, {_Status, _Headers, Body}} -> + case Body of + <<"-----BEGIN", _/binary>> -> + Pem = public_key:pem_decode(Body), + lists:filtermap(fun({'CertificateList', + CRL, not_encrypted}) -> + {true, CRL}; + (_) -> + false + end, Pem); + _ -> + try public_key:der_decode('CertificateList', Body) of + _ -> + [Body] + catch + _:_ -> + get_crls(Rest, CRLDbInfo) + end + end; + {error, _Reason} -> + get_crls(Rest, CRLDbInfo) + end. + +cache_lookup(_, undefined) -> + []; +cache_lookup(URL, {{Cache, _}, _}) -> + #{path := Path} = uri_string:normalize(URL, [return_map]), + case ssl_pkix_db:lookup(string:trim(Path, leading, "/"), Cache) of + undefined -> + []; + CRLs -> + CRLs + end. + +handle_http(URI, Rest, {_, [{http, Timeout}]} = CRLDbInfo) -> + CRLs = http_lookup(URI, Rest, CRLDbInfo, Timeout), + %% Uncomment to improve performance, but need to + %% implement cache limit and or cleaning to prevent + %% DoS attack possibilities + %%insert(URI, {der, CRLs}), + CRLs; +handle_http(_, Rest, CRLDbInfo) -> + get_crls(Rest, CRLDbInfo). + diff --git a/lib/ssl/src/ssl_crl_cache_api.erl b/lib/ssl/src/ssl_crl_cache_api.erl new file mode 100644 index 0000000000..d5380583e7 --- /dev/null +++ b/lib/ssl/src/ssl_crl_cache_api.erl @@ -0,0 +1,32 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2015-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_cache_api). + +-include_lib("public_key/include/public_key.hrl"). + +-type db_handle() :: term(). +-type issuer_name() :: {rdnSequence, [#'AttributeTypeAndValue'{}]}. + +-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_admin_sup.erl b/lib/ssl/src/ssl_dist_admin_sup.erl new file mode 100644 index 0000000000..f60806c4cb --- /dev/null +++ b/lib/ssl/src/ssl_dist_admin_sup.erl @@ -0,0 +1,74 @@ +%% +%% %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_dist_admin_sup). + +-behaviour(supervisor). + +%% API +-export([start_link/0]). + +%% Supervisor callback +-export([init/1]). + +%%%========================================================================= +%%% API +%%%========================================================================= + +-spec start_link() -> {ok, pid()} | ignore | {error, term()}. + +start_link() -> + supervisor:start_link({local, ?MODULE}, ?MODULE, []). + +%%%========================================================================= +%%% Supervisor callback +%%%========================================================================= + +init([]) -> + PEMCache = pem_cache_child_spec(), + SessionCertManager = session_and_cert_manager_child_spec(), + {ok, {{rest_for_one, 10, 3600}, [PEMCache, SessionCertManager]}}. + + +%%-------------------------------------------------------------------- +%%% Internal functions +%%-------------------------------------------------------------------- + +pem_cache_child_spec() -> + Name = ssl_pem_cache_dist, + StartFunc = {ssl_pem_cache, start_link_dist, [[]]}, + Restart = permanent, + Shutdown = 4000, + Modules = [ssl_pem_cache], + Type = worker, + {Name, StartFunc, Restart, Shutdown, Type, Modules}. + +session_and_cert_manager_child_spec() -> + Opts = ssl_admin_sup:manager_opts(), + Name = ssl_dist_manager, + StartFunc = {ssl_manager, start_link_dist, [Opts]}, + Restart = permanent, + Shutdown = 4000, + Modules = [ssl_manager], + Type = worker, + {Name, StartFunc, Restart, Shutdown, Type, Modules}. + diff --git a/lib/ssl/src/ssl_dist_connection_sup.erl b/lib/ssl/src/ssl_dist_connection_sup.erl new file mode 100644 index 0000000000..e5842c866e --- /dev/null +++ b/lib/ssl/src/ssl_dist_connection_sup.erl @@ -0,0 +1,79 @@ +%% +%% %CopyrightBegin% +%% +%% 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. +%% 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_dist_connection_sup). + +-behaviour(supervisor). + +%% API +-export([start_link/0]). + +%% Supervisor callback +-export([init/1]). + +%%%========================================================================= +%%% API +%%%========================================================================= + +-spec start_link() -> {ok, pid()} | ignore | {error, term()}. + +start_link() -> + supervisor:start_link({local, ?MODULE}, ?MODULE, []). + +%%%========================================================================= +%%% Supervisor callback +%%%========================================================================= + +init([]) -> + + TLSConnetionManager = tls_connection_manager_child_spec(), + %% Handles emulated options so that they inherited by the accept + %% socket, even when setopts is performed on the listen socket + ListenOptionsTracker = listen_options_tracker_child_spec(), + + {ok, {{one_for_one, 10, 3600}, [TLSConnetionManager, + ListenOptionsTracker + ]}}. + + +%%-------------------------------------------------------------------- +%%% Internal functions +%%-------------------------------------------------------------------- + +tls_connection_manager_child_spec() -> + Name = dist_tls_connection, + StartFunc = {tls_connection_sup, start_link_dist, []}, + Restart = permanent, + Shutdown = 4000, + Modules = [tls_connection_sup], + Type = supervisor, + {Name, StartFunc, Restart, Shutdown, Type, Modules}. + +listen_options_tracker_child_spec() -> + Name = dist_tls_socket, + StartFunc = {ssl_listen_tracker_sup, start_link_dist, []}, + Restart = permanent, + Shutdown = 4000, + Modules = [tls_socket], + Type = supervisor, + {Name, StartFunc, Restart, Shutdown, Type, Modules}. + diff --git a/lib/ssl/src/ssl_dist_sup.erl b/lib/ssl/src/ssl_dist_sup.erl index 58efeaf892..bea67935d8 100644 --- a/lib/ssl/src/ssl_dist_sup.erl +++ b/lib/ssl/src/ssl_dist_sup.erl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2011-2014. All Rights Reserved. +%% Copyright Ericsson AB 2011-2017. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. +%% 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 %% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% 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% %% @@ -29,6 +30,9 @@ %% Supervisor callback -export([init/1]). +%% Debug +-export([consult/1]). + %%%========================================================================= %%% API %%%========================================================================= @@ -36,58 +40,94 @@ -spec start_link() -> {ok, pid()} | ignore | {error, term()}. start_link() -> - supervisor:start_link({local, ?MODULE}, ?MODULE, []). + case init:get_argument(ssl_dist_optfile) of + {ok, [File]} -> + DistOpts = consult(File), + TabOpts = [set, protected, named_table], + Tab = ets:new(ssl_dist_opts, TabOpts), + true = ets:insert(Tab, DistOpts), + supervisor:start_link({local, ?MODULE}, ?MODULE, []); + {ok, BadArg} -> + error({bad_ssl_dist_optfile, BadArg}); + error -> + supervisor:start_link({local, ?MODULE}, ?MODULE, []) + end. %%%========================================================================= %%% Supervisor callback %%%========================================================================= init([]) -> - SessionCertManager = session_and_cert_manager_child_spec(), - ConnetionManager = connection_manager_child_spec(), - ListenOptionsTracker = listen_options_tracker_child_spec(), - ProxyServer = proxy_server_child_spec(), - - {ok, {{one_for_all, 10, 3600}, [SessionCertManager, ConnetionManager, - ListenOptionsTracker, - ProxyServer]}}. + AdminSup = ssl_admin_child_spec(), + ConnectionSup = ssl_connection_sup(), + {ok, {{one_for_all, 10, 3600}, [AdminSup, ConnectionSup]}}. %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- -session_and_cert_manager_child_spec() -> - Opts = ssl_sup:manager_opts(), - Name = ssl_manager_dist, - StartFunc = {ssl_manager, start_link_dist, [Opts]}, +ssl_admin_child_spec() -> + Name = ssl_dist_admin_sup, + StartFunc = {ssl_dist_admin_sup, start_link , []}, Restart = permanent, Shutdown = 4000, - Modules = [ssl_manager], - Type = worker, + Modules = [ssl_admin_sup], + Type = supervisor, {Name, StartFunc, Restart, Shutdown, Type, Modules}. -connection_manager_child_spec() -> - Name = ssl_connection_dist, - StartFunc = {tls_connection_sup, start_link_dist, []}, - Restart = permanent, +ssl_connection_sup() -> + Name = ssl_dist_connection_sup, + StartFunc = {ssl_dist_connection_sup, start_link, []}, + Restart = permanent, Shutdown = 4000, - Modules = [tls_connection_sup], + Modules = [ssl_connection_sup], Type = supervisor, {Name, StartFunc, Restart, Shutdown, Type, Modules}. -proxy_server_child_spec() -> - Name = ssl_tls_dist_proxy, - StartFunc = {ssl_tls_dist_proxy, start_link, []}, - Restart = permanent, - Shutdown = 4000, - Modules = [ssl_tls_dist_proxy], - Type = worker, - {Name, StartFunc, Restart, Shutdown, Type, Modules}. +consult(File) -> + case erl_prim_loader:get_file(File) of + {ok, Binary, _FullName} -> + Encoding = + case epp:read_encoding_from_binary(Binary) of + none -> latin1; + Enc -> Enc + end, + case unicode:characters_to_list(Binary, Encoding) of + {error, _String, Rest} -> + error( + {bad_ssl_dist_optfile, {encoding_error, Rest}}); + {incomplete, _String, Rest} -> + error( + {bad_ssl_dist_optfile, {encoding_incomplete, Rest}}); + String when is_list(String) -> + consult_string(String) + end; + error -> + error({bad_ssl_dist_optfile, File}) + end. -listen_options_tracker_child_spec() -> - Name = ssl_socket_dist, - StartFunc = {ssl_listen_tracker_sup, start_link_dist, []}, - Restart = permanent, - Shutdown = 4000, - Modules = [ssl_socket], - Type = supervisor, - {Name, StartFunc, Restart, Shutdown, Type, Modules}. +consult_string(String) -> + case erl_scan:string(String) of + {error, Info, Location} -> + error({bad_ssl_dist_optfile, {scan_error, Info, Location}}); + {ok, Tokens, _EndLocation} -> + consult_tokens(Tokens) + end. + +consult_tokens(Tokens) -> + case erl_parse:parse_exprs(Tokens) of + {error, Info} -> + error({bad_ssl_dist_optfile, {parse_error, Info}}); + {ok, [Expr]} -> + consult_expr(Expr); + {ok, Other} -> + error({bad_ssl_dist_optfile, {parse_error, Other}}) + end. + +consult_expr(Expr) -> + {value, Value, Bs} = erl_eval:expr(Expr, erl_eval:new_bindings()), + case erl_eval:bindings(Bs) of + [] -> + Value; + Other -> + error({bad_ssl_dist_optfile, {bindings, Other}}) + end. diff --git a/lib/ssl/src/ssl_handshake.erl b/lib/ssl/src/ssl_handshake.erl index 29b64f7a81..14df1d2e02 100644 --- a/lib/ssl/src/ssl_handshake.erl +++ b/lib/ssl/src/ssl_handshake.erl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2013-2015. All Rights Reserved. +%% Copyright Ericsson AB 2013-2018. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. +%% 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 %% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% 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% @@ -43,46 +44,44 @@ #client_key_exchange{} | #finished{} | #certificate_verify{} | #hello_request{} | #next_protocol{}. -%% Handshake messages +%% Create handshake messages -export([hello_request/0, server_hello/4, server_hello_done/0, - certificate/4, certificate_request/4, key_exchange/3, + certificate/4, client_certificate_verify/6, certificate_request/5, key_exchange/3, finished/5, next_protocol/1]). %% Handle handshake messages --export([certify/8, client_certificate_verify/6, certificate_verify/6, verify_signature/5, - master_secret/5, server_key_exchange_hash/2, verify_connection/6, - init_handshake_history/0, update_handshake_history/2, verify_server_key/5 +-export([certify/7, certificate_verify/6, verify_signature/5, + master_secret/4, server_key_exchange_hash/2, verify_connection/6, + init_handshake_history/0, update_handshake_history/2, verify_server_key/5, + select_version/3, extension_value/1 ]). -%% Encode/Decode +%% Encode -export([encode_handshake/2, encode_hello_extensions/1, - encode_client_protocol_negotiation/2, encode_protocols_advertised_on_server/1, - decode_handshake/3, decode_hello_extensions/1, + encode_client_protocol_negotiation/2, encode_protocols_advertised_on_server/1]). +%% Decode +-export([decode_handshake/3, decode_hello_extensions/1, decode_server_key/3, decode_client_key/3, decode_suites/2 ]). %% Cipher suites handling --export([available_suites/2, cipher_suites/2, - select_session/10, supported_ecc/1]). +-export([available_suites/2, available_signature_algs/2, available_signature_algs/4, + cipher_suites/3, prf/6, select_session/11, supported_ecc/1, + premaster_secret/2, premaster_secret/3, premaster_secret/4]). %% Extensions handling --export([client_hello_extensions/6, +-export([client_hello_extensions/5, handle_client_hello_extensions/9, %% Returns server hello extensions - handle_server_hello_extensions/9, select_curve/2 + handle_server_hello_extensions/9, select_curve/2, select_curve/3, + select_hashsign/4, select_hashsign/5, + select_hashsign_algs/3 ]). -%% MISC --export([select_version/3, prf/5, select_hashsign/3, - select_hashsign_algs/2, select_hashsign_algs/3, - premaster_secret/2, premaster_secret/3, premaster_secret/4]). - %%==================================================================== -%% Internal application API +%% Create handshake messages %%==================================================================== -%% ---------- Create handshake messages ---------- - %%-------------------------------------------------------------------- -spec hello_request() -> #hello_request{}. %% @@ -93,15 +92,14 @@ hello_request() -> #hello_request{}. %%-------------------------------------------------------------------- --spec server_hello(#session{}, ssl_record:ssl_version(), #connection_states{}, +-spec server_hello(#session{}, ssl_record:ssl_version(), ssl_record:connection_states(), #hello_extensions{}) -> #server_hello{}. %% %% Description: Creates a server hello message. %%-------------------------------------------------------------------- server_hello(SessionId, Version, ConnectionStates, Extensions) -> - Pending = ssl_record:pending_connection_state(ConnectionStates, read), - SecParams = Pending#connection_state.security_parameters, - + #{security_parameters := SecParams} = + ssl_record:pending_connection_state(ConnectionStates, read), #server_hello{server_version = Version, cipher_suite = SecParams#security_parameters.cipher_suite, compression_method = @@ -119,28 +117,6 @@ server_hello(SessionId, Version, ConnectionStates, Extensions) -> server_hello_done() -> #server_hello_done{}. -client_hello_extensions(Host, Version, CipherSuites, SslOpts, ConnectionStates, Renegotiation) -> - {EcPointFormats, EllipticCurves} = - case advertises_ec_ciphers(lists:map(fun ssl_cipher:suite_definition/1, CipherSuites)) of - true -> - client_ecc_extensions(tls_v1, Version); - false -> - {undefined, undefined} - end, - SRP = srp_user(SslOpts), - - #hello_extensions{ - renegotiation_info = renegotiation_info(tls_record, client, - ConnectionStates, Renegotiation), - srp = SRP, - hash_signs = advertised_hash_signs(Version), - ec_point_formats = EcPointFormats, - elliptic_curves = EllipticCurves, - next_protocol_negotiation = - encode_client_protocol_negotiation(SslOpts#ssl_options.next_protocol_selector, - Renegotiation), - sni = sni(Host, SslOpts#ssl_options.server_name_indication)}. - %%-------------------------------------------------------------------- -spec certificate(der_cert(), db_handle(), certdb_ref(), client | server) -> #certificate{} | #alert{}. %% @@ -149,7 +125,7 @@ client_hello_extensions(Host, Version, CipherSuites, SslOpts, ConnectionStates, certificate(OwnCert, CertDbHandle, CertDbRef, client) -> Chain = case ssl_certificate:certificate_chain(OwnCert, CertDbHandle, CertDbRef) of - {ok, CertChain} -> + {ok, _, CertChain} -> CertChain; {error, _} -> %% If no suitable certificate is available, the client @@ -161,21 +137,13 @@ certificate(OwnCert, CertDbHandle, CertDbRef, client) -> certificate(OwnCert, CertDbHandle, CertDbRef, server) -> case ssl_certificate:certificate_chain(OwnCert, CertDbHandle, CertDbRef) of - {ok, Chain} -> + {ok, _, Chain} -> #certificate{asn1_certificates = Chain}; - {error, _} -> - ?ALERT_REC(?FATAL, ?INTERNAL_ERROR) + {error, Error} -> + ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, {server_has_no_suitable_certificates, Error}) end. %%-------------------------------------------------------------------- --spec next_protocol(binary()) -> #next_protocol{}. -%% -%% Description: Creates a next protocol message -%%------------------------------------------------------------------- -next_protocol(SelectedProtocol) -> - #next_protocol{selected_protocol = SelectedProtocol}. - -%%-------------------------------------------------------------------- -spec client_certificate_verify(undefined | der_cert(), binary(), ssl_record:ssl_version(), term(), public_key:private_key(), ssl_handshake_history()) -> @@ -192,7 +160,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), @@ -201,14 +169,14 @@ client_certificate_verify(OwnCert, MasterSecret, Version, end. %%-------------------------------------------------------------------- --spec certificate_request(ssl_cipher:cipher_suite(), db_handle(), certdb_ref(), ssl_record:ssl_version()) -> - #certificate_request{}. +-spec certificate_request(ssl_cipher_format:cipher_suite(), db_handle(), + certdb_ref(), #hash_sign_algos{}, ssl_record:ssl_version()) -> + #certificate_request{}. %% %% Description: Creates a certificate_request message, called by the server. %%-------------------------------------------------------------------- -certificate_request(CipherSuite, CertDbHandle, CertDbRef, Version) -> - Types = certificate_types(ssl_cipher:suite_definition(CipherSuite), Version), - HashSigns = advertised_hash_signs(Version), +certificate_request(CipherSuite, CertDbHandle, CertDbRef, HashSigns, Version) -> + Types = certificate_types(ssl_cipher_format:suite_definition(CipherSuite), Version), Authorities = certificate_authorities(CertDbHandle, CertDbRef), #certificate_request{ certificate_types = Types, @@ -221,11 +189,18 @@ certificate_request(CipherSuite, CertDbHandle, CertDbRef, Version) -> {dh, binary()} | {dh, {binary(), binary()}, #'DHParameter'{}, {HashAlgo::atom(), SignAlgo::atom()}, binary(), binary(), public_key:private_key()} | + {ecdh, _, _, _, _, _} | {ecdh, #'ECPrivateKey'{}} | + {psk, _, _, _, _, _} | {psk, binary()} | + {dhe_psk, _, _, _, _, _, _, _} | {dhe_psk, binary(), binary()} | + {ecdhe_psk, _, _, _, _, _, _} | + {ecdhe_psk, binary(), #'ECPrivateKey'{}} | {srp, {binary(), binary()}, #srp_user{}, {HashAlgo::atom(), SignAlgo::atom()}, - binary(), binary(), public_key:private_key()}) -> + binary(), binary(), public_key:private_key()} | + {srp, _} | + {psk_premaster_secret, _, _, _}) -> #client_key_exchange{} | #server_key_exchange{}. %% @@ -242,7 +217,7 @@ key_exchange(client, _Version, {dh, PublicKey}) -> dh_public = PublicKey} }; -key_exchange(client, _Version, {ecdh, #'ECPrivateKey'{publicKey = {0, ECPublicKey}}}) -> +key_exchange(client, _Version, {ecdh, #'ECPrivateKey'{publicKey = ECPublicKey}}) -> #client_key_exchange{ exchange_keys = #client_ec_diffie_hellman_public{ dh_public = ECPublicKey} @@ -261,6 +236,13 @@ key_exchange(client, _Version, {dhe_psk, Identity, PublicKey}) -> dh_public = PublicKey} }; +key_exchange(client, _Version, {ecdhe_psk, Identity, #'ECPrivateKey'{publicKey = ECPublicKey}}) -> + #client_key_exchange{ + exchange_keys = #client_ecdhe_psk_identity{ + identity = Identity, + dh_public = ECPublicKey} + }; + key_exchange(client, _Version, {psk_premaster_secret, PskIdentity, Secret, {_, PublicKey, _}}) -> EncPremasterSecret = encrypted_premaster_secret(Secret, PublicKey), @@ -283,7 +265,7 @@ key_exchange(server, Version, {dh, {PublicKey, _}, enc_server_key_exchange(Version, ServerDHParams, HashSign, ClientRandom, ServerRandom, PrivateKey); -key_exchange(server, Version, {ecdh, #'ECPrivateKey'{publicKey = {0, ECPublicKey}, +key_exchange(server, Version, {ecdh, #'ECPrivateKey'{publicKey = ECPublicKey, parameters = ECCurve}, HashSign, ClientRandom, ServerRandom, PrivateKey}) -> ServerECParams = #server_ecdh_params{curve = ECCurve, public = ECPublicKey}, @@ -307,6 +289,16 @@ key_exchange(server, Version, {dhe_psk, PskIdentityHint, {PublicKey, _}, enc_server_key_exchange(Version, ServerEDHPSKParams, HashSign, ClientRandom, ServerRandom, PrivateKey); +key_exchange(server, Version, {ecdhe_psk, PskIdentityHint, + #'ECPrivateKey'{publicKey = ECPublicKey, + parameters = ECCurve}, + HashSign, ClientRandom, ServerRandom, PrivateKey}) -> + ServerECDHEPSKParams = #server_ecdhe_psk_params{ + hint = PskIdentityHint, + dh_params = #server_ecdh_params{curve = ECCurve, public = ECPublicKey}}, + enc_server_key_exchange(Version, ServerECDHEPSKParams, HashSign, + ClientRandom, ServerRandom, PrivateKey); + key_exchange(server, Version, {srp, {PublicKey, _}, #srp_user{generator = Generator, prime = Prime, salt = Salt}, @@ -325,30 +317,61 @@ key_exchange(server, Version, {srp, {PublicKey, _}, finished(Version, Role, PrfAlgo, MasterSecret, {Handshake, _}) -> % use the current handshake #finished{verify_data = calc_finished(Version, Role, PrfAlgo, MasterSecret, Handshake)}. +%%-------------------------------------------------------------------- +-spec next_protocol(binary()) -> #next_protocol{}. +%% +%% Description: Creates a next protocol message +%%------------------------------------------------------------------- +next_protocol(SelectedProtocol) -> + #next_protocol{selected_protocol = SelectedProtocol}. -%% ---------- Handle handshake messages ---------- - -verify_server_key(#server_key_params{params_bin = EncParams, - signature = Signature}, - HashSign = {HashAlgo, _}, - ConnectionStates, Version, PubKeyInfo) -> - ConnectionState = - ssl_record:pending_connection_state(ConnectionStates, read), - SecParams = ConnectionState#connection_state.security_parameters, - #security_parameters{client_random = ClientRandom, - server_random = ServerRandom} = SecParams, - Hash = server_key_exchange_hash(HashAlgo, - <<ClientRandom/binary, - ServerRandom/binary, - EncParams/binary>>), - verify_signature(Version, Hash, HashSign, Signature, PubKeyInfo). +%%==================================================================== +%% Handle handshake messages +%%==================================================================== +%%-------------------------------------------------------------------- +-spec certify(#certificate{}, db_handle(), certdb_ref(), #ssl_options{}, term(), + client | server, inet:hostname() | inet:ip_address()) -> {der_cert(), public_key_info()} | #alert{}. +%% +%% Description: Handles a certificate handshake message +%%-------------------------------------------------------------------- +certify(#certificate{asn1_certificates = ASN1Certs}, CertDbHandle, CertDbRef, + Opts, CRLDbHandle, Role, Host) -> + ServerName = server_name(Opts#ssl_options.server_name_indication, Host, Role), + [PeerCert | ChainCerts ] = ASN1Certs, + try + {TrustedCert, CertPath} = + ssl_certificate:trusted_cert_and_path(ASN1Certs, CertDbHandle, CertDbRef, + Opts#ssl_options.partial_chain), + ValidationFunAndState = validation_fun_and_state(Opts#ssl_options.verify_fun, Role, + CertDbHandle, CertDbRef, ServerName, + Opts#ssl_options.customize_hostname_check, + Opts#ssl_options.crl_check, CRLDbHandle, CertPath), + Options = [{max_path_length, Opts#ssl_options.depth}, + {verify_fun, ValidationFunAndState}], + case public_key:pkix_path_validation(TrustedCert, CertPath, Options) of + {ok, {PublicKeyInfo,_}} -> + {PeerCert, PublicKeyInfo}; + {error, Reason} -> + handle_path_validation_error(Reason, PeerCert, ChainCerts, Opts, Options, + CertDbHandle, CertDbRef) + end + catch + error:{badmatch,{asn1, Asn1Reason}} -> + %% ASN-1 decode of certificate somehow failed + ?ALERT_REC(?FATAL, ?CERTIFICATE_UNKNOWN, {failed_to_decode_certificate, Asn1Reason}); + error:OtherReason -> + ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, {unexpected_error, OtherReason}) + end. %%-------------------------------------------------------------------- -spec certificate_verify(binary(), public_key_info(), ssl_record:ssl_version(), term(), binary(), ssl_handshake_history()) -> valid | #alert{}. %% %% Description: Checks that the certificate_verify message is valid. %%-------------------------------------------------------------------- +certificate_verify(_, _, _, undefined, _, _) -> + ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, invalid_certificate_verify_message); + certificate_verify(Signature, PublicKeyInfo, Version, HashSign = {HashAlgo, _}, MasterSecret, {_, Handshake}) -> Hash = calc_certificate_verify(Version, HashAlgo, MasterSecret, Handshake), @@ -377,68 +400,59 @@ verify_signature(_Version, Hash, _HashAlgo, Signature, {?rsaEncryption, PubKey, end; verify_signature(_Version, Hash, {HashAlgo, dsa}, Signature, {?'id-dsa', PublicKey, PublicKeyParams}) -> public_key:verify({digest, Hash}, HashAlgo, Signature, {PublicKey, PublicKeyParams}); -verify_signature(_Version, Hash, {HashAlgo, ecdsa}, Signature, +verify_signature(_, Hash, {HashAlgo, _SignAlg}, Signature, {?'id-ecPublicKey', PublicKey, PublicKeyParams}) -> public_key:verify({digest, Hash}, HashAlgo, Signature, {PublicKey, PublicKeyParams}). %%-------------------------------------------------------------------- --spec certify(#certificate{}, db_handle(), certdb_ref(), integer() | nolimit, - verify_peer | verify_none, {fun(), term}, fun(), - client | server) -> {der_cert(), public_key_info()} | #alert{}. +-spec master_secret(ssl_record:ssl_version(), #session{} | binary(), ssl_record:connection_states(), + client | server) -> {binary(), ssl_record:connection_states()} | #alert{}. %% -%% Description: Handles a certificate handshake message -%%-------------------------------------------------------------------- -certify(#certificate{asn1_certificates = ASN1Certs}, CertDbHandle, CertDbRef, - MaxPathLen, _Verify, VerifyFunAndState, PartialChain, Role) -> - [PeerCert | _] = ASN1Certs, - - ValidationFunAndState = - case VerifyFunAndState of - undefined -> - {fun(OtpCert, ExtensionOrVerifyResult, SslState) -> - ssl_certificate:validate_extension(OtpCert, - ExtensionOrVerifyResult, SslState) - end, Role}; - {Fun, UserState0} -> - {fun(OtpCert, {extension, _} = Extension, {SslState, UserState}) -> - case ssl_certificate:validate_extension(OtpCert, - Extension, - SslState) of - {valid, NewSslState} -> - {valid, {NewSslState, UserState}}; - {fail, Reason} -> - apply_user_fun(Fun, OtpCert, Reason, UserState, - SslState); - {unknown, _} -> - apply_user_fun(Fun, OtpCert, - Extension, UserState, SslState) - end; - (OtpCert, VerifyResult, {SslState, UserState}) -> - apply_user_fun(Fun, OtpCert, VerifyResult, UserState, - SslState) - end, {Role, UserState0}} - end, +%% Description: Sets or calculates the master secret and calculate keys, +%% updating the pending connection states. The Mastersecret and the update +%% connection states are returned or an alert if the calculation fails. +%%------------------------------------------------------------------- +master_secret(Version, #session{master_secret = Mastersecret}, + ConnectionStates, Role) -> + #{security_parameters := SecParams} = + ssl_record:pending_connection_state(ConnectionStates, read), + try master_secret(Version, Mastersecret, SecParams, + ConnectionStates, Role) + catch + exit:_ -> + ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, key_calculation_failure) + end; - try - {TrustedErlCert, CertPath} = - ssl_certificate:trusted_cert_and_path(ASN1Certs, CertDbHandle, CertDbRef, PartialChain), - case public_key:pkix_path_validation(TrustedErlCert, - CertPath, - [{max_path_length, - MaxPathLen}, - {verify_fun, ValidationFunAndState}]) of - {ok, {PublicKeyInfo,_}} -> - {PeerCert, PublicKeyInfo}; - {error, Reason} -> - path_validation_alert(Reason) - end +master_secret(Version, PremasterSecret, ConnectionStates, Role) -> + #{security_parameters := SecParams} = + ssl_record:pending_connection_state(ConnectionStates, read), + + #security_parameters{prf_algorithm = PrfAlgo, + client_random = ClientRandom, + server_random = ServerRandom} = SecParams, + try master_secret(Version, + calc_master_secret(Version,PrfAlgo,PremasterSecret, + ClientRandom, ServerRandom), + SecParams, ConnectionStates, Role) catch - error:_ -> - %% ASN-1 decode of certificate somehow failed - ?ALERT_REC(?FATAL, ?CERTIFICATE_UNKNOWN) + exit:_ -> + ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, master_secret_calculation_failure) end. %%-------------------------------------------------------------------- +-spec server_key_exchange_hash(md5sha | md5 | sha | sha224 |sha256 | sha384 | sha512, binary()) -> binary(). +%% +%% Description: Calculate server key exchange hash +%%-------------------------------------------------------------------- +server_key_exchange_hash(md5sha, Value) -> + MD5 = crypto:hash(md5, Value), + SHA = crypto:hash(sha, Value), + <<MD5/binary, SHA/binary>>; + +server_key_exchange_hash(Hash, Value) -> + crypto:hash(Hash, Value). + +%%-------------------------------------------------------------------- -spec verify_connection(ssl_record:ssl_version(), #finished{}, client | server, integer(), binary(), ssl_handshake_history()) -> verified | #alert{}. %% @@ -470,264 +484,34 @@ init_handshake_history() -> %% %% Description: Update the handshake history buffer with Data. %%-------------------------------------------------------------------- -update_handshake_history(Handshake, % special-case SSL2 client hello - <<?CLIENT_HELLO, ?UINT24(_), ?BYTE(Major), ?BYTE(Minor), - ?UINT16(CSLength), ?UINT16(0), - ?UINT16(CDLength), - CipherSuites:CSLength/binary, - ChallengeData:CDLength/binary>>) -> - update_handshake_history(Handshake, - <<?CLIENT_HELLO, ?BYTE(Major), ?BYTE(Minor), - ?UINT16(CSLength), ?UINT16(0), - ?UINT16(CDLength), - CipherSuites:CSLength/binary, - ChallengeData:CDLength/binary>>); update_handshake_history({Handshake0, _Prev}, Data) -> {[Data|Handshake0], Handshake0}. -%% %%-------------------------------------------------------------------- -%% -spec decrypt_premaster_secret(binary(), #'RSAPrivateKey'{}) -> binary(). - -%% %% -%% %% Description: Public key decryption using the private key. -%% %%-------------------------------------------------------------------- -%% decrypt_premaster_secret(Secret, RSAPrivateKey) -> -%% try public_key:decrypt_private(Secret, RSAPrivateKey, -%% [{rsa_pad, rsa_pkcs1_padding}]) -%% catch -%% _:_ -> -%% throw(?ALERT_REC(?FATAL, ?DECRYPT_ERROR)) -%% end. - -premaster_secret(OtherPublicDhKey, MyPrivateKey, #'DHParameter'{} = Params) -> - try - public_key:compute_key(OtherPublicDhKey, MyPrivateKey, Params) - catch - error:computation_failed -> - throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)) - end; -premaster_secret(PublicDhKey, PrivateDhKey, #server_dh_params{dh_p = Prime, dh_g = Base}) -> - try - crypto:compute_key(dh, PublicDhKey, PrivateDhKey, [Prime, Base]) - catch - error:computation_failed -> - throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)) - end; -premaster_secret(#client_srp_public{srp_a = ClientPublicKey}, ServerKey, #srp_user{prime = Prime, - verifier = Verifier}) -> - case crypto:compute_key(srp, ClientPublicKey, ServerKey, {host, [Verifier, Prime, '6a']}) of - error -> - throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)); - PremasterSecret -> - PremasterSecret - end; -premaster_secret(#server_srp_params{srp_n = Prime, srp_g = Generator, srp_s = Salt, srp_b = Public}, - ClientKeys, {Username, Password}) -> - case ssl_srp_primes:check_srp_params(Generator, Prime) of - ok -> - DerivedKey = crypto:hash(sha, [Salt, crypto:hash(sha, [Username, <<$:>>, Password])]), - case crypto:compute_key(srp, Public, ClientKeys, {user, [DerivedKey, Prime, Generator, '6a']}) of - error -> - throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)); - PremasterSecret -> - PremasterSecret - end; - _ -> - throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)) - end; -premaster_secret(#client_rsa_psk_identity{ - identity = PSKIdentity, - exchange_keys = #encrypted_premaster_secret{premaster_secret = EncPMS} - }, #'RSAPrivateKey'{} = Key, PSKLookup) -> - PremasterSecret = premaster_secret(EncPMS, Key), - psk_secret(PSKIdentity, PSKLookup, PremasterSecret); -premaster_secret(#server_dhe_psk_params{ - hint = IdentityHint, - dh_params = #server_dh_params{dh_y = PublicDhKey} = Params}, - PrivateDhKey, - LookupFun) -> - PremasterSecret = premaster_secret(PublicDhKey, PrivateDhKey, Params), - psk_secret(IdentityHint, LookupFun, PremasterSecret); -premaster_secret({rsa_psk, PSKIdentity}, PSKLookup, RSAPremasterSecret) -> - psk_secret(PSKIdentity, PSKLookup, RSAPremasterSecret). - -premaster_secret(#client_dhe_psk_identity{ - identity = PSKIdentity, - dh_public = PublicDhKey}, PrivateKey, #'DHParameter'{} = Params, PSKLookup) -> - PremasterSecret = premaster_secret(PublicDhKey, PrivateKey, Params), - psk_secret(PSKIdentity, PSKLookup, PremasterSecret). -premaster_secret(#client_psk_identity{identity = PSKIdentity}, PSKLookup) -> - psk_secret(PSKIdentity, PSKLookup); -premaster_secret({psk, PSKIdentity}, PSKLookup) -> - psk_secret(PSKIdentity, PSKLookup); -premaster_secret(#'ECPoint'{} = ECPoint, #'ECPrivateKey'{} = ECDHKeys) -> - public_key:compute_key(ECPoint, ECDHKeys); -premaster_secret(EncSecret, #'RSAPrivateKey'{} = RSAPrivateKey) -> - try public_key:decrypt_private(EncSecret, RSAPrivateKey, - [{rsa_pad, rsa_pkcs1_padding}]) - catch - _:_ -> - throw(?ALERT_REC(?FATAL, ?DECRYPT_ERROR)) - end. -%%-------------------------------------------------------------------- --spec server_key_exchange_hash(md5sha | md5 | sha | sha224 |sha256 | sha384 | sha512, binary()) -> binary(). -%% -%% Description: Calculate server key exchange hash -%%-------------------------------------------------------------------- -server_key_exchange_hash(md5sha, Value) -> - MD5 = crypto:hash(md5, Value), - SHA = crypto:hash(sha, Value), - <<MD5/binary, SHA/binary>>; - -server_key_exchange_hash(Hash, Value) -> - crypto:hash(Hash, Value). -%%-------------------------------------------------------------------- --spec prf(ssl_record:ssl_version(), binary(), binary(), [binary()], non_neg_integer()) -> - {ok, binary()} | {error, undefined}. -%% -%% Description: use the TLS PRF to generate key material -%%-------------------------------------------------------------------- -prf({3,0}, _, _, _, _) -> - {error, undefined}; -prf({3,1}, Secret, Label, Seed, WantedLength) -> - {ok, tls_v1:prf(?MD5SHA, Secret, Label, Seed, WantedLength)}; -prf({3,_N}, Secret, Label, Seed, WantedLength) -> - {ok, tls_v1:prf(?SHA256, Secret, Label, Seed, WantedLength)}. - - -%%-------------------------------------------------------------------- --spec select_hashsign(#hash_sign_algos{}| undefined, undefined | binary(), ssl_record:ssl_version()) -> - {atom(), atom()} | undefined. - -%% -%% Description: -%%-------------------------------------------------------------------- -select_hashsign(_, undefined, _Version) -> - {null, anon}; -select_hashsign(undefined, Cert, Version) -> - #'OTPCertificate'{tbsCertificate = TBSCert} = public_key:pkix_decode_cert(Cert, otp), - #'OTPSubjectPublicKeyInfo'{algorithm = {_,Algo, _}} = TBSCert#'OTPTBSCertificate'.subjectPublicKeyInfo, - select_hashsign_algs(undefined, Algo, Version); -select_hashsign(#hash_sign_algos{hash_sign_algos = HashSigns}, Cert, Version) -> - #'OTPCertificate'{tbsCertificate = TBSCert} =public_key:pkix_decode_cert(Cert, otp), - #'OTPSubjectPublicKeyInfo'{algorithm = {_,Algo, _}} = TBSCert#'OTPTBSCertificate'.subjectPublicKeyInfo, - DefaultHashSign = {_, Sign} = select_hashsign_algs(undefined, Algo, Version), - case lists:filter(fun({sha, dsa}) -> - true; - ({_, dsa}) -> - false; - ({Hash, S}) when S == Sign -> - ssl_cipher:is_acceptable_hash(Hash, - proplists:get_value(hashs, crypto:supports())); - (_) -> - false - end, HashSigns) of - [] -> - DefaultHashSign; - [HashSign| _] -> - HashSign - end. - -%%-------------------------------------------------------------------- --spec select_hashsign_algs(#hash_sign_algos{}| undefined, oid(), ssl_record:ssl_version()) -> - {atom(), atom()}. - -%% Description: For TLS 1.2 hash function and signature algorithm pairs can be -%% negotiated with the signature_algorithms extension, -%% for previous versions always use appropriate defaults. -%% RFC 5246, Sect. 7.4.1.4.1. Signature Algorithms -%% If the client does not send the signature_algorithms extension, the -%% server MUST do the following: (e.i defaults for TLS 1.2) -%% -%% - If the negotiated key exchange algorithm is one of (RSA, DHE_RSA, -%% DH_RSA, RSA_PSK, ECDH_RSA, ECDHE_RSA), behave as if client had -%% sent the value {sha1,rsa}. -%% -%% - If the negotiated key exchange algorithm is one of (DHE_DSS, -%% DH_DSS), behave as if the client had sent the value {sha1,dsa}. -%% -%% - If the negotiated key exchange algorithm is one of (ECDH_ECDSA, -%% ECDHE_ECDSA), behave as if the client had sent value {sha1,ecdsa}. - -%%-------------------------------------------------------------------- -select_hashsign_algs(HashSign, _, {Major, Minor}) when HashSign =/= undefined andalso - Major >= 3 andalso Minor >= 3 -> - HashSign; -select_hashsign_algs(undefined, ?rsaEncryption, {Major, Minor}) when Major >= 3 andalso Minor >= 3 -> - {sha, rsa}; -select_hashsign_algs(undefined,?'id-ecPublicKey', _) -> - {sha, ecdsa}; -select_hashsign_algs(undefined, ?rsaEncryption, _) -> - {md5sha, rsa}; -select_hashsign_algs(undefined, ?'id-dsa', _) -> - {sha, dsa}. - --spec select_hashsign_algs(atom(), ssl_record:ssl_version()) -> {atom(), atom()}. -%% Wrap function to keep the knowledge of the default values in -%% one place only -select_hashsign_algs(Alg, Version) when (Alg == rsa orelse - Alg == dhe_rsa orelse - Alg == dh_rsa orelse - Alg == ecdhe_rsa orelse - Alg == ecdh_rsa orelse - Alg == srp_rsa) -> - select_hashsign_algs(undefined, ?rsaEncryption, Version); -select_hashsign_algs(Alg, Version) when (Alg == dhe_dss orelse - Alg == dh_dss orelse - Alg == srp_dss) -> - select_hashsign_algs(undefined, ?'id-dsa', Version); -select_hashsign_algs(Alg, Version) when (Alg == ecdhe_ecdsa orelse - Alg == ecdh_ecdsa) -> - select_hashsign_algs(undefined, ?'id-ecPublicKey', Version). - -%%-------------------------------------------------------------------- --spec master_secret(atom(), ssl_record:ssl_version(), #session{} | binary(), #connection_states{}, - client | server) -> {binary(), #connection_states{}} | #alert{}. -%% -%% Description: Sets or calculates the master secret and calculate keys, -%% updating the pending connection states. The Mastersecret and the update -%% connection states are returned or an alert if the calculation fails. -%%------------------------------------------------------------------- -master_secret(RecordCB, Version, #session{master_secret = Mastersecret}, - ConnectionStates, Role) -> - ConnectionState = - ssl_record:pending_connection_state(ConnectionStates, read), - SecParams = ConnectionState#connection_state.security_parameters, - try master_secret(RecordCB, Version, Mastersecret, SecParams, - ConnectionStates, Role) - catch - exit:Reason -> - Report = io_lib:format("Key calculation failed due to ~p", - [Reason]), - error_logger:error_report(Report), - ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE) - end; - -master_secret(RecordCB, Version, PremasterSecret, ConnectionStates, Role) -> - ConnectionState = +verify_server_key(#server_key_params{params_bin = EncParams, + signature = Signature}, + HashSign = {HashAlgo, _}, + ConnectionStates, Version, PubKeyInfo) -> + #{security_parameters := SecParams} = ssl_record:pending_connection_state(ConnectionStates, read), - SecParams = ConnectionState#connection_state.security_parameters, - #security_parameters{prf_algorithm = PrfAlgo, - client_random = ClientRandom, + #security_parameters{client_random = ClientRandom, server_random = ServerRandom} = SecParams, - try master_secret(RecordCB, Version, - calc_master_secret(Version,PrfAlgo,PremasterSecret, - ClientRandom, ServerRandom), - SecParams, ConnectionStates, Role) - catch - exit:Reason -> - Report = io_lib:format("Master secret calculation failed" - " due to ~p", [Reason]), - error_logger:error_report(Report), - ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE) - end. + Hash = server_key_exchange_hash(HashAlgo, + <<ClientRandom/binary, + ServerRandom/binary, + EncParams/binary>>), + verify_signature(Version, Hash, HashSign, Signature, PubKeyInfo). + +select_version(RecordCB, ClientVersion, Versions) -> + do_select_version(RecordCB, ClientVersion, Versions). + +%%==================================================================== +%% Encode handshake +%%==================================================================== -%%-------------Encode/Decode -------------------------------- encode_handshake(#next_protocol{selected_protocol = SelectedProtocol}, _Version) -> PaddingLength = 32 - ((byte_size(SelectedProtocol) + 2) rem 32), {?NEXT_PROTOCOL, <<?BYTE((byte_size(SelectedProtocol))), SelectedProtocol/binary, ?BYTE(PaddingLength), 0:(PaddingLength * 8)>>}; - encode_handshake(#server_hello{server_version = {Major, Minor}, random = Random, session_id = Session_ID, @@ -791,6 +575,11 @@ encode_hello_extensions([], Acc) -> Size = byte_size(Acc), <<?UINT16(Size), Acc/binary>>; +encode_hello_extensions([#alpn{extension_data = ExtensionData} | Rest], Acc) -> + Len = byte_size(ExtensionData), + ExtLen = Len + 2, + encode_hello_extensions(Rest, <<?UINT16(?ALPN_EXT), ?UINT16(ExtLen), ?UINT16(Len), + ExtensionData/binary, Acc/binary>>); encode_hello_extensions([#next_protocol_negotiation{extension_data = ExtensionData} | Rest], Acc) -> Len = byte_size(ExtensionData), encode_hello_extensions(Rest, <<?UINT16(?NEXTPROTONEG_EXT), ?UINT16(Len), @@ -844,51 +633,6 @@ encode_hello_extensions([#sni{hostname = Hostname} | Rest], Acc) -> ?UINT16(HostLen), HostnameBin/binary, Acc/binary>>). -enc_server_key_exchange(Version, Params, {HashAlgo, SignAlgo}, - ClientRandom, ServerRandom, PrivateKey) -> - EncParams = encode_server_key(Params), - case HashAlgo of - null -> - #server_key_params{params = Params, - params_bin = EncParams, - hashsign = {null, anon}, - signature = <<>>}; - _ -> - Hash = - server_key_exchange_hash(HashAlgo, <<ClientRandom/binary, - ServerRandom/binary, - EncParams/binary>>), - Signature = digitally_signed(Version, Hash, HashAlgo, PrivateKey), - #server_key_params{params = Params, - params_bin = EncParams, - hashsign = {HashAlgo, SignAlgo}, - signature = Signature} - end. - -%%-------------------------------------------------------------------- --spec decode_client_key(binary(), ssl_cipher:key_algo(), ssl_record:ssl_version()) -> - #encrypted_premaster_secret{} - | #client_diffie_hellman_public{} - | #client_ec_diffie_hellman_public{} - | #client_psk_identity{} - | #client_dhe_psk_identity{} - | #client_rsa_psk_identity{} - | #client_srp_public{}. -%% -%% Description: Decode client_key data and return appropriate type -%%-------------------------------------------------------------------- -decode_client_key(ClientKey, Type, Version) -> - dec_client_key(ClientKey, key_exchange_alg(Type), Version). - -%%-------------------------------------------------------------------- --spec decode_server_key(binary(), ssl_cipher:key_algo(), ssl_record:ssl_version()) -> - #server_key_params{}. -%% -%% Description: Decode server_key data and return appropriate type -%%-------------------------------------------------------------------- -decode_server_key(ServerKey, Type, Version) -> - dec_server_key(ServerKey, key_exchange_alg(Type), Version). - encode_client_protocol_negotiation(undefined, _) -> undefined; encode_client_protocol_negotiation(_, false) -> @@ -902,6 +646,10 @@ encode_protocols_advertised_on_server(undefined) -> encode_protocols_advertised_on_server(Protocols) -> #next_protocol_negotiation{extension_data = lists:foldl(fun encode_protocol/2, <<>>, Protocols)}. +%%==================================================================== +%% Decode handshake +%%==================================================================== + decode_handshake(_, ?HELLO_REQUEST, <<>>) -> #hello_request{}; decode_handshake(_, ?NEXT_PROTOCOL, <<?BYTE(SelectedProtocolLength), @@ -934,7 +682,6 @@ decode_handshake(_Version, ?SERVER_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:3 cipher_suite = Cipher_suite, compression_method = Comp_method, extensions = HelloExtensions}; - decode_handshake(_Version, ?CERTIFICATE, <<?UINT24(ACLen), ASN1Certs:ACLen/binary>>) -> #certificate{asn1_certificates = certs_to_list(ASN1Certs)}; decode_handshake(_Version, ?SERVER_KEY_EXCHANGE, Keys) -> @@ -966,8 +713,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{}. @@ -981,66 +728,30 @@ decode_hello_extensions({client, <<?UINT16(ExtLen), Extensions:ExtLen/binary>>}) decode_hello_extensions(Extensions) -> dec_hello_extensions(Extensions, #hello_extensions{}). -dec_server_key(<<?UINT16(PLen), P:PLen/binary, - ?UINT16(GLen), G:GLen/binary, - ?UINT16(YLen), Y:YLen/binary, _/binary>> = KeyStruct, - ?KEY_EXCHANGE_DIFFIE_HELLMAN, Version) -> - Params = #server_dh_params{dh_p = P, dh_g = G, dh_y = Y}, - {BinMsg, HashSign, Signature} = dec_server_key_params(PLen + GLen + YLen + 6, KeyStruct, Version), - #server_key_params{params = Params, - params_bin = BinMsg, - hashsign = HashSign, - signature = Signature}; -%% ECParameters with named_curve -%% TODO: explicit curve -dec_server_key(<<?BYTE(?NAMED_CURVE), ?UINT16(CurveID), - ?BYTE(PointLen), ECPoint:PointLen/binary, - _/binary>> = KeyStruct, - ?KEY_EXCHANGE_EC_DIFFIE_HELLMAN, Version) -> - Params = #server_ecdh_params{curve = {namedCurve, tls_v1:enum_to_oid(CurveID)}, - public = ECPoint}, - {BinMsg, HashSign, Signature} = dec_server_key_params(PointLen + 4, KeyStruct, Version), - #server_key_params{params = Params, - params_bin = BinMsg, - hashsign = HashSign, - signature = Signature}; -dec_server_key(<<?UINT16(Len), PskIdentityHint:Len/binary, _/binary>> = KeyStruct, - KeyExchange, Version) - when KeyExchange == ?KEY_EXCHANGE_PSK; KeyExchange == ?KEY_EXCHANGE_RSA_PSK -> - Params = #server_psk_params{ - hint = PskIdentityHint}, - {BinMsg, HashSign, Signature} = dec_server_key_params(Len + 2, KeyStruct, Version), - #server_key_params{params = Params, - params_bin = BinMsg, - hashsign = HashSign, - signature = Signature}; -dec_server_key(<<?UINT16(Len), IdentityHint:Len/binary, - ?UINT16(PLen), P:PLen/binary, - ?UINT16(GLen), G:GLen/binary, - ?UINT16(YLen), Y:YLen/binary, _/binary>> = KeyStruct, - ?KEY_EXCHANGE_DHE_PSK, Version) -> - DHParams = #server_dh_params{dh_p = P, dh_g = G, dh_y = Y}, - Params = #server_dhe_psk_params{ - hint = IdentityHint, - dh_params = DHParams}, - {BinMsg, HashSign, Signature} = dec_server_key_params(Len + PLen + GLen + YLen + 8, KeyStruct, Version), - #server_key_params{params = Params, - params_bin = BinMsg, - hashsign = HashSign, - signature = Signature}; -dec_server_key(<<?UINT16(NLen), N:NLen/binary, - ?UINT16(GLen), G:GLen/binary, - ?BYTE(SLen), S:SLen/binary, - ?UINT16(BLen), B:BLen/binary, _/binary>> = KeyStruct, - ?KEY_EXCHANGE_SRP, Version) -> - Params = #server_srp_params{srp_n = N, srp_g = G, srp_s = S, srp_b = B}, - {BinMsg, HashSign, Signature} = dec_server_key_params(NLen + GLen + SLen + BLen + 7, KeyStruct, Version), - #server_key_params{params = Params, - params_bin = BinMsg, - hashsign = HashSign, - signature = Signature}; -dec_server_key(_, _, _) -> - throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE)). +%%-------------------------------------------------------------------- +-spec decode_server_key(binary(), ssl_cipher_format:key_algo(), ssl_record:ssl_version()) -> + #server_key_params{}. +%% +%% Description: Decode server_key data and return appropriate type +%%-------------------------------------------------------------------- +decode_server_key(ServerKey, Type, Version) -> + dec_server_key(ServerKey, key_exchange_alg(Type), Version). + +%%-------------------------------------------------------------------- +-spec decode_client_key(binary(), ssl_cipher_format:key_algo(), ssl_record:ssl_version()) -> + #encrypted_premaster_secret{} + | #client_diffie_hellman_public{} + | #client_ec_diffie_hellman_public{} + | #client_psk_identity{} + | #client_dhe_psk_identity{} + | #client_ecdhe_psk_identity{} + | #client_rsa_psk_identity{} + | #client_srp_public{}. +%% +%% Description: Decode client_key data and return appropriate type +%%-------------------------------------------------------------------- +decode_client_key(ClientKey, Type, Version) -> + dec_client_key(ClientKey, key_exchange_alg(Type), Version). %%-------------------------------------------------------------------- -spec decode_suites('2_bytes'|'3_bytes', binary()) -> list(). @@ -1052,107 +763,212 @@ decode_suites('2_bytes', Dec) -> decode_suites('3_bytes', Dec) -> from_3bytes(Dec). -%%-------------Cipeher suite handling -------------------------------- +%%==================================================================== +%% Cipher suite handling +%%==================================================================== available_suites(UserSuites, Version) -> - lists:filtermap(fun(Suite) -> - lists:member(Suite, ssl_cipher:all_suites(Version)) - end, UserSuites). - -available_suites(ServerCert, UserSuites, Version, Curve) -> - ssl_cipher:filter(ServerCert, available_suites(UserSuites, Version)) - -- unavailable_ecc_suites(Curve). - -unavailable_ecc_suites(no_curve) -> - ssl_cipher:ec_keyed_suites(); -unavailable_ecc_suites(_) -> - []. + VersionSuites = ssl_cipher:all_suites(Version) ++ ssl_cipher:anonymous_suites(Version), + lists:filtermap(fun(Suite) -> lists:member(Suite, VersionSuites) end, UserSuites). + +available_suites(ServerCert, UserSuites, Version, undefined, Curve) -> + Suites = ssl_cipher:filter(ServerCert, available_suites(UserSuites, Version), Version), + filter_unavailable_ecc_suites(Curve, Suites); +available_suites(ServerCert, UserSuites, Version, HashSigns, Curve) -> + Suites = available_suites(ServerCert, UserSuites, Version, undefined, Curve), + filter_hashsigns(Suites, [ssl_cipher_format:suite_definition(Suite) || Suite <- Suites], HashSigns, + Version, []). + +available_signature_algs(undefined, _) -> + undefined; +available_signature_algs(SupportedHashSigns, Version) when Version >= {3, 3} -> + #hash_sign_algos{hash_sign_algos = SupportedHashSigns}; +available_signature_algs(_, _) -> + undefined. +available_signature_algs(undefined, SupportedHashSigns, _, Version) when + Version >= {3,3} -> + SupportedHashSigns; +available_signature_algs(#hash_sign_algos{hash_sign_algos = ClientHashSigns}, SupportedHashSigns, + _, Version) when Version >= {3,3} -> + sets:to_list(sets:intersection(sets:from_list(ClientHashSigns), + sets:from_list(SupportedHashSigns))); +available_signature_algs(_, _, _, _) -> + undefined. +cipher_suites(Suites, Renegotiation, true) -> + %% TLS_FALLBACK_SCSV should be placed last -RFC7507 + cipher_suites(Suites, Renegotiation) ++ [?TLS_FALLBACK_SCSV]; +cipher_suites(Suites, Renegotiation, false) -> + cipher_suites(Suites, Renegotiation). cipher_suites(Suites, false) -> [?TLS_EMPTY_RENEGOTIATION_INFO_SCSV | Suites]; cipher_suites(Suites, true) -> Suites. +%%-------------------------------------------------------------------- +-spec prf(ssl_record:ssl_version(), non_neg_integer(), binary(), binary(), [binary()], non_neg_integer()) -> + {ok, binary()} | {error, undefined}. +%% +%% Description: use the TLS PRF to generate key material +%%-------------------------------------------------------------------- +prf({3,0}, _, _, _, _, _) -> + {error, undefined}; +prf({3,_N}, PRFAlgo, Secret, Label, Seed, WantedLength) -> + {ok, tls_v1:prf(PRFAlgo, Secret, Label, Seed, WantedLength)}. -select_session(SuggestedSessionId, CipherSuites, Compressions, Port, #session{ecc = ECCCurve} = +select_session(SuggestedSessionId, CipherSuites, HashSigns, Compressions, Port, #session{ecc = ECCCurve0} = Session, Version, - #ssl_options{ciphers = UserSuites, honor_cipher_order = HCO} = SslOpts, + #ssl_options{ciphers = UserSuites, honor_cipher_order = HonorCipherOrder} = SslOpts, Cache, CacheCb, Cert) -> {SessionId, Resumed} = ssl_session:server_id(Port, SuggestedSessionId, SslOpts, Cert, Cache, CacheCb), case Resumed of undefined -> - Suites = available_suites(Cert, UserSuites, Version, ECCCurve), - CipherSuite = select_cipher_suite(CipherSuites, Suites, HCO), + Suites = available_suites(Cert, UserSuites, Version, HashSigns, ECCCurve0), + CipherSuite0 = select_cipher_suite(CipherSuites, Suites, HonorCipherOrder), + {ECCCurve, CipherSuite} = cert_curve(Cert, ECCCurve0, CipherSuite0), Compression = select_compression(Compressions), {new, Session#session{session_id = SessionId, + ecc = ECCCurve, cipher_suite = CipherSuite, compression_method = Compression}}; _ -> {resumed, Resumed} end. -supported_ecc({Major, Minor} = Version) when ((Major == 3) and (Minor >= 1)) orelse (Major > 3) -> - Curves = tls_v1:ecc_curves(Version), +supported_ecc({Major, Minor}) when ((Major == 3) and (Minor >= 1)) orelse (Major > 3) -> + Curves = tls_v1:ecc_curves(Minor), #elliptic_curves{elliptic_curve_list = Curves}; supported_ecc(_) -> #elliptic_curves{elliptic_curve_list = []}. -%%-------------certificate handling -------------------------------- - -certificate_types(_, {N, M}) when N >= 3 andalso M >= 3 -> - case proplists:get_bool(ecdsa, - proplists:get_value(public_keys, crypto:supports())) of - true -> - <<?BYTE(?ECDSA_SIGN), ?BYTE(?RSA_SIGN), ?BYTE(?DSS_SIGN)>>; - false -> - <<?BYTE(?RSA_SIGN), ?BYTE(?DSS_SIGN)>> +premaster_secret(OtherPublicDhKey, MyPrivateKey, #'DHParameter'{} = Params) -> + try + public_key:compute_key(OtherPublicDhKey, MyPrivateKey, Params) + catch + error:computation_failed -> + throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)) + end; +premaster_secret(PublicDhKey, PrivateDhKey, #server_dh_params{dh_p = Prime, dh_g = Base}) -> + try + crypto:compute_key(dh, PublicDhKey, PrivateDhKey, [Prime, Base]) + catch + error:computation_failed -> + throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)) end; +premaster_secret(#client_srp_public{srp_a = ClientPublicKey}, ServerKey, #srp_user{prime = Prime, + verifier = Verifier}) -> + try crypto:compute_key(srp, ClientPublicKey, ServerKey, {host, [Verifier, Prime, '6a']}) of + PremasterSecret -> + PremasterSecret + catch + error:_ -> + throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)) + end; +premaster_secret(#server_srp_params{srp_n = Prime, srp_g = Generator, srp_s = Salt, srp_b = Public}, + ClientKeys, {Username, Password}) -> + case ssl_srp_primes:check_srp_params(Generator, Prime) of + ok -> + DerivedKey = crypto:hash(sha, [Salt, crypto:hash(sha, [Username, <<$:>>, Password])]), + try crypto:compute_key(srp, Public, ClientKeys, {user, [DerivedKey, Prime, Generator, '6a']}) of + PremasterSecret -> + PremasterSecret + catch + error -> + throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)) + end; + _ -> + throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)) + end; +premaster_secret(#client_rsa_psk_identity{ + identity = PSKIdentity, + exchange_keys = #encrypted_premaster_secret{premaster_secret = EncPMS} + }, #'RSAPrivateKey'{} = Key, PSKLookup) -> + PremasterSecret = premaster_secret(EncPMS, Key), + psk_secret(PSKIdentity, PSKLookup, PremasterSecret); +premaster_secret(#server_dhe_psk_params{ + hint = IdentityHint, + dh_params = #server_dh_params{dh_y = PublicDhKey} = Params}, + PrivateDhKey, + LookupFun) -> + PremasterSecret = premaster_secret(PublicDhKey, PrivateDhKey, Params), + psk_secret(IdentityHint, LookupFun, PremasterSecret); +premaster_secret(#server_ecdhe_psk_params{ + hint = IdentityHint, + dh_params = #server_ecdh_params{ + public = ECServerPubKey}}, + PrivateEcDhKey, + LookupFun) -> + PremasterSecret = premaster_secret(#'ECPoint'{point = ECServerPubKey}, PrivateEcDhKey), + psk_secret(IdentityHint, LookupFun, PremasterSecret); +premaster_secret({rsa_psk, PSKIdentity}, PSKLookup, RSAPremasterSecret) -> + psk_secret(PSKIdentity, PSKLookup, RSAPremasterSecret); +premaster_secret(#client_ecdhe_psk_identity{ + identity = PSKIdentity, + dh_public = PublicEcDhPoint}, PrivateEcDhKey, PSKLookup) -> + PremasterSecret = premaster_secret(#'ECPoint'{point = PublicEcDhPoint}, PrivateEcDhKey), + psk_secret(PSKIdentity, PSKLookup, PremasterSecret). +premaster_secret(#client_dhe_psk_identity{ + identity = PSKIdentity, + dh_public = PublicDhKey}, PrivateKey, #'DHParameter'{} = Params, PSKLookup) -> + PremasterSecret = premaster_secret(PublicDhKey, PrivateKey, Params), + psk_secret(PSKIdentity, PSKLookup, PremasterSecret). +premaster_secret(#client_psk_identity{identity = PSKIdentity}, PSKLookup) -> + psk_secret(PSKIdentity, PSKLookup); +premaster_secret({psk, PSKIdentity}, PSKLookup) -> + psk_secret(PSKIdentity, PSKLookup); +premaster_secret(#'ECPoint'{} = ECPoint, #'ECPrivateKey'{} = ECDHKeys) -> + public_key:compute_key(ECPoint, ECDHKeys); +premaster_secret(EncSecret, #'RSAPrivateKey'{} = RSAPrivateKey) -> + try public_key:decrypt_private(EncSecret, RSAPrivateKey, + [{rsa_pad, rsa_pkcs1_padding}]) + catch + _:_ -> + throw(?ALERT_REC(?FATAL, ?DECRYPT_ERROR)) + end; +premaster_secret(EncSecret, #{algorithm := rsa} = Engine) -> + try crypto:private_decrypt(rsa, EncSecret, maps:remove(algorithm, Engine), + [{rsa_pad, rsa_pkcs1_padding}]) + catch + _:_ -> + throw(?ALERT_REC(?FATAL, ?DECRYPT_ERROR)) + end. +%%==================================================================== +%% Extensions handling +%%==================================================================== +client_hello_extensions(Version, CipherSuites, + #ssl_options{signature_algs = SupportedHashSigns, + eccs = SupportedECCs} = SslOpts, ConnectionStates, Renegotiation) -> + {EcPointFormats, EllipticCurves} = + case advertises_ec_ciphers(lists:map(fun ssl_cipher_format:suite_definition/1, CipherSuites)) of + true -> + client_ecc_extensions(SupportedECCs); + false -> + {undefined, undefined} + end, + SRP = srp_user(SslOpts), -certificate_types({KeyExchange, _, _, _}, _) when KeyExchange == rsa; - KeyExchange == dhe_rsa; - KeyExchange == ecdhe_rsa -> - <<?BYTE(?RSA_SIGN)>>; - -certificate_types({KeyExchange, _, _, _}, _) when KeyExchange == dhe_dss; - KeyExchange == srp_dss -> - <<?BYTE(?DSS_SIGN)>>; - -certificate_types({KeyExchange, _, _, _}, _) when KeyExchange == dh_ecdsa; - KeyExchange == dhe_ecdsa; - KeyExchange == ecdh_ecdsa; - KeyExchange == ecdhe_ecdsa -> - <<?BYTE(?ECDSA_SIGN)>>; - -certificate_types(_, _) -> - <<?BYTE(?RSA_SIGN)>>. - -certificate_authorities(CertDbHandle, CertDbRef) -> - Authorities = certificate_authorities_from_db(CertDbHandle, CertDbRef), - Enc = fun(#'OTPCertificate'{tbsCertificate=TBSCert}) -> - OTPSubj = TBSCert#'OTPTBSCertificate'.subject, - DNEncodedBin = public_key:pkix_encode('Name', OTPSubj, otp), - DNEncodedLen = byte_size(DNEncodedBin), - <<?UINT16(DNEncodedLen), DNEncodedBin/binary>> - end, - list_to_binary([Enc(Cert) || {_, Cert} <- Authorities]). - -certificate_authorities_from_db(CertDbHandle, CertDbRef) -> - ConnectionCerts = fun({{Ref, _, _}, Cert}, Acc) when Ref == CertDbRef -> - [Cert | Acc]; - (_, Acc) -> - Acc - end, - ssl_pkix_db:foldl(ConnectionCerts, [], CertDbHandle). - -%%-------------Extension handling -------------------------------- + #hello_extensions{ + renegotiation_info = renegotiation_info(tls_record, client, + ConnectionStates, Renegotiation), + srp = SRP, + signature_algs = available_signature_algs(SupportedHashSigns, Version), + ec_point_formats = EcPointFormats, + elliptic_curves = EllipticCurves, + alpn = encode_alpn(SslOpts#ssl_options.alpn_advertised_protocols, Renegotiation), + next_protocol_negotiation = + encode_client_protocol_negotiation(SslOpts#ssl_options.next_protocol_selector, + Renegotiation), + sni = sni(SslOpts#ssl_options.server_name_indication)}. handle_client_hello_extensions(RecordCB, Random, ClientCipherSuites, #hello_extensions{renegotiation_info = Info, srp = SRP, ec_point_formats = ECCFormat, + alpn = ALPN, next_protocol_negotiation = NextProtocolNegotiation}, Version, - #ssl_options{secure_renegotiate = SecureRenegotation} = Opts, + #ssl_options{secure_renegotiate = SecureRenegotation, + alpn_preferred_protocols = ALPNPreferredProtocols} = Opts, #session{cipher_suite = NegotiatedCipherSuite, compression_method = Compression} = Session0, ConnectionStates0, Renegotiation) -> @@ -1161,19 +977,34 @@ handle_client_hello_extensions(RecordCB, Random, ClientCipherSuites, Random, NegotiatedCipherSuite, ClientCipherSuites, Compression, ConnectionStates0, Renegotiation, SecureRenegotation), - ProtocolsToAdvertise = handle_next_protocol_extension(NextProtocolNegotiation, Renegotiation, Opts), - + ServerHelloExtensions = #hello_extensions{ renegotiation_info = renegotiation_info(RecordCB, server, ConnectionStates, Renegotiation), - ec_point_formats = server_ecc_extension(Version, ECCFormat), - next_protocol_negotiation = - encode_protocols_advertised_on_server(ProtocolsToAdvertise) + ec_point_formats = server_ecc_extension(Version, ECCFormat) }, - {Session, ConnectionStates, ServerHelloExtensions}. + + %% If we receive an ALPN extension and have ALPN configured for this connection, + %% we handle it. Otherwise we check for the NPN extension. + if + ALPN =/= undefined, ALPNPreferredProtocols =/= undefined -> + case handle_alpn_extension(ALPNPreferredProtocols, decode_alpn(ALPN)) of + #alert{} = Alert -> + Alert; + Protocol -> + {Session, ConnectionStates, Protocol, + ServerHelloExtensions#hello_extensions{alpn=encode_alpn([Protocol], Renegotiation)}} + end; + true -> + ProtocolsToAdvertise = handle_next_protocol_extension(NextProtocolNegotiation, Renegotiation, Opts), + {Session, ConnectionStates, undefined, + ServerHelloExtensions#hello_extensions{next_protocol_negotiation= + encode_protocols_advertised_on_server(ProtocolsToAdvertise)}} + end. handle_server_hello_extensions(RecordCB, Random, CipherSuite, Compression, #hello_extensions{renegotiation_info = Info, + alpn = ALPN, next_protocol_negotiation = NextProtocolNegotiation}, Version, #ssl_options{secure_renegotiate = SecureRenegotation, next_protocol_selector = NextProtoSelector}, @@ -1182,209 +1013,350 @@ handle_server_hello_extensions(RecordCB, Random, CipherSuite, Compression, CipherSuite, undefined, Compression, ConnectionStates0, Renegotiation, SecureRenegotation), - case handle_next_protocol(NextProtocolNegotiation, NextProtoSelector, Renegotiation) of - #alert{} = Alert -> - Alert; - Protocol -> - {ConnectionStates, Protocol} + + %% If we receive an ALPN extension then this is the protocol selected, + %% otherwise handle the NPN extension. + case decode_alpn(ALPN) of + %% ServerHello contains exactly one protocol: the one selected. + %% We also ignore the ALPN extension during renegotiation (see encode_alpn/2). + [Protocol] when not Renegotiation -> + {ConnectionStates, alpn, Protocol}; + undefined -> + case handle_next_protocol(NextProtocolNegotiation, NextProtoSelector, Renegotiation) of + #alert{} = Alert -> + Alert; + Protocol -> + {ConnectionStates, npn, Protocol} + end; + {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) -> - ServerVersion = RecordCB:highest_protocol_version(Versions), - RecordCB:lowest_protocol_version(ClientVersion, ServerVersion). +select_curve(Client, Server) -> + select_curve(Client, Server, false). -renegotiation_info(_, client, _, false) -> - #renegotiation_info{renegotiated_connection = undefined}; -renegotiation_info(_RecordCB, server, ConnectionStates, false) -> - CS = ssl_record:current_connection_state(ConnectionStates, read), - case CS#connection_state.secure_renegotiation of - true -> - #renegotiation_info{renegotiated_connection = ?byte(0)}; - false -> - #renegotiation_info{renegotiated_connection = undefined} - end; -renegotiation_info(_RecordCB, client, ConnectionStates, true) -> - CS = ssl_record:current_connection_state(ConnectionStates, read), - case CS#connection_state.secure_renegotiation of - true -> - Data = CS#connection_state.client_verify_data, - #renegotiation_info{renegotiated_connection = Data}; - false -> - #renegotiation_info{renegotiated_connection = undefined} +select_curve(#elliptic_curves{elliptic_curve_list = ClientCurves}, + #elliptic_curves{elliptic_curve_list = ServerCurves}, + ServerOrder) -> + case ServerOrder of + false -> + select_shared_curve(ClientCurves, ServerCurves); + true -> + select_shared_curve(ServerCurves, ClientCurves) end; +select_curve(undefined, _, _) -> + %% Client did not send ECC extension use default curve if + %% ECC cipher is negotiated + {namedCurve, ?secp256r1}. -renegotiation_info(_RecordCB, server, ConnectionStates, true) -> - CS = ssl_record:current_connection_state(ConnectionStates, read), - case CS#connection_state.secure_renegotiation of - true -> - CData = CS#connection_state.client_verify_data, - SData =CS#connection_state.server_verify_data, - #renegotiation_info{renegotiated_connection = <<CData/binary, SData/binary>>}; - false -> - #renegotiation_info{renegotiated_connection = undefined} - end. +%%-------------------------------------------------------------------- +-spec select_hashsign(#hash_sign_algos{} | undefined, undefined | binary(), + atom(), [atom()], ssl_record:ssl_version()) -> + {atom(), atom()} | undefined | #alert{}. -handle_renegotiation_info(_RecordCB, _, #renegotiation_info{renegotiated_connection = ?byte(0)}, - ConnectionStates, false, _, _) -> - {ok, ssl_record:set_renegotiation_flag(true, ConnectionStates)}; +%% +%% Description: Handles signature_algorithms hello extension (server) +%%-------------------------------------------------------------------- +select_hashsign(_, _, KeyExAlgo, _, _Version) when KeyExAlgo == dh_anon; + KeyExAlgo == ecdh_anon; + KeyExAlgo == srp_anon; + KeyExAlgo == psk; + KeyExAlgo == dhe_psk; + KeyExAlgo == ecdhe_psk -> + {null, anon}; +%% The signature_algorithms extension was introduced with TLS 1.2. Ignore it if we have +%% negotiated a lower version. +select_hashsign(HashSigns, Cert, KeyExAlgo, + undefined, {Major, Minor} = Version) when Major >= 3 andalso Minor >= 3-> + select_hashsign(HashSigns, Cert, KeyExAlgo, tls_v1:default_signature_algs(Version), Version); +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 = {_, SubjAlgo, _}} = + TBSCert#'OTPTBSCertificate'.subjectPublicKeyInfo, -handle_renegotiation_info(_RecordCB, server, undefined, ConnectionStates, _, _, CipherSuites) -> - case is_member(?TLS_EMPTY_RENEGOTIATION_INFO_SCSV, CipherSuites) of - true -> - {ok, ssl_record:set_renegotiation_flag(true, ConnectionStates)}; - false -> - {ok, ssl_record:set_renegotiation_flag(false, ConnectionStates)} + SubSign = sign_algo(SubjAlgo), + + case lists:filter(fun({_, S} = Algos) when S == SubSign -> + is_acceptable_hash_sign(Algos, KeyExAlgo, SupportedHashSigns); + (_) -> + false + end, HashSigns) of + [] -> + ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_signature_algorithm); + [HashSign | _] -> + HashSign end; +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{}. -handle_renegotiation_info(_RecordCB, _, undefined, ConnectionStates, false, _, _) -> - {ok, ssl_record:set_renegotiation_flag(false, ConnectionStates)}; - -handle_renegotiation_info(_RecordCB, client, #renegotiation_info{renegotiated_connection = ClientServerVerify}, - ConnectionStates, true, _, _) -> - CS = ssl_record:current_connection_state(ConnectionStates, read), - CData = CS#connection_state.client_verify_data, - SData = CS#connection_state.server_verify_data, - case <<CData/binary, SData/binary>> == ClientServerVerify of +%% +%% Description: Handles signature algorithms selection for certificate requests (client) +%%-------------------------------------------------------------------- +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 -> - {ok, ConnectionStates}; + 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, ?HANDSHAKE_FAILURE) + ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_signature_algorithm) end; -handle_renegotiation_info(_RecordCB, server, #renegotiation_info{renegotiated_connection = ClientVerify}, - ConnectionStates, true, _, CipherSuites) -> - - case is_member(?TLS_EMPTY_RENEGOTIATION_INFO_SCSV, CipherSuites) of - true -> - ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE); - false -> - CS = ssl_record:current_connection_state(ConnectionStates, read), - Data = CS#connection_state.client_verify_data, - case Data == ClientVerify of - true -> - {ok, ConnectionStates}; - false -> - ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE) - end - end; - -handle_renegotiation_info(RecordCB, client, undefined, ConnectionStates, true, SecureRenegotation, _) -> - handle_renegotiation_info(RecordCB, ConnectionStates, SecureRenegotation); +select_hashsign(#certificate_request{}, Cert, _, Version) -> + select_hashsign(undefined, Cert, undefined, [], Version). -handle_renegotiation_info(RecordCB, server, undefined, ConnectionStates, true, SecureRenegotation, CipherSuites) -> - case is_member(?TLS_EMPTY_RENEGOTIATION_INFO_SCSV, CipherSuites) of - true -> - ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE); - false -> - handle_renegotiation_info(RecordCB, ConnectionStates, SecureRenegotation) - end. +%%-------------------------------------------------------------------- +-spec select_hashsign_algs({atom(), atom()}| undefined, oid(), ssl_record:ssl_version()) -> + {atom(), atom()}. -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); - {true, false} -> - ?ALERT_REC(?FATAL, ?NO_RENEGOTIATION); - {false, false} -> - {ok, ConnectionStates} - end. +%% Description: For TLS 1.2 hash function and signature algorithm pairs can be +%% negotiated with the signature_algorithms extension, +%% for previous versions always use appropriate defaults. +%% RFC 5246, Sect. 7.4.1.4.1. Signature Algorithms +%% If the client does not send the signature_algorithms extension, the +%% server MUST do the following: (e.i defaults for TLS 1.2) +%% +%% - If the negotiated key exchange algorithm is one of (RSA, DHE_RSA, +%% DH_RSA, RSA_PSK, ECDH_RSA, ECDHE_RSA), behave as if client had +%% sent the value {sha1,rsa}. +%% +%% - If the negotiated key exchange algorithm is one of (DHE_DSS, +%% DH_DSS), behave as if the client had sent the value {sha1,dsa}. +%% +%% - If the negotiated key exchange algorithm is one of (ECDH_ECDSA, +%% ECDHE_ECDSA), behave as if the client had sent value {sha1,ecdsa}. -hello_extensions_list(#hello_extensions{renegotiation_info = RenegotiationInfo, - srp = SRP, - hash_signs = HashSigns, - ec_point_formats = EcPointFormats, - elliptic_curves = EllipticCurves, - next_protocol_negotiation = NextProtocolNegotiation, - sni = Sni}) -> - [Ext || Ext <- [RenegotiationInfo, SRP, HashSigns, - EcPointFormats, EllipticCurves, NextProtocolNegotiation, Sni], Ext =/= undefined]. +%%-------------------------------------------------------------------- +select_hashsign_algs(HashSign, _, {Major, Minor}) when HashSign =/= undefined andalso + Major >= 3 andalso Minor >= 3 -> + HashSign; +select_hashsign_algs(undefined, ?rsaEncryption, {Major, Minor}) when Major >= 3 andalso Minor >= 3 -> + {sha, rsa}; +select_hashsign_algs(undefined,?'id-ecPublicKey', _) -> + {sha, ecdsa}; +select_hashsign_algs(undefined, ?rsaEncryption, _) -> + {md5sha, rsa}; +select_hashsign_algs(undefined, ?'id-dsa', _) -> + {sha, dsa}. srp_user(#ssl_options{srp_identity = {UserName, _}}) -> #srp{username = UserName}; srp_user(_) -> undefined. -client_ecc_extensions(Module, Version) -> - CryptoSupport = proplists:get_value(public_keys, crypto:supports()), - case proplists:get_bool(ecdh, CryptoSupport) of - true -> - EcPointFormats = #ec_point_formats{ec_point_format_list = [?ECPOINT_UNCOMPRESSED]}, - EllipticCurves = #elliptic_curves{elliptic_curve_list = Module:ecc_curves(Version)}, - {EcPointFormats, EllipticCurves}; - _ -> - {undefined, undefined} - end. - -server_ecc_extension(_Version, EcPointFormats) -> - CryptoSupport = proplists:get_value(public_keys, crypto:supports()), - case proplists:get_bool(ecdh, CryptoSupport) of - true -> - handle_ecc_point_fmt_extension(EcPointFormats); - false -> - undefined - end. - -handle_ecc_point_fmt_extension(undefined) -> +extension_value(undefined) -> undefined; -handle_ecc_point_fmt_extension(_) -> - #ec_point_formats{ec_point_format_list = [?ECPOINT_UNCOMPRESSED]}. +extension_value(#sni{hostname = HostName}) -> + HostName; +extension_value(#ec_point_formats{ec_point_format_list = List}) -> + List; +extension_value(#elliptic_curves{elliptic_curve_list = List}) -> + List; +extension_value(#hash_sign_algos{hash_sign_algos = Algos}) -> + Algos; +extension_value(#alpn{extension_data = Data}) -> + Data; +extension_value(#next_protocol_negotiation{extension_data = Data}) -> + Data; +extension_value(#srp{username = Name}) -> + Name; +extension_value(#renegotiation_info{renegotiated_connection = Data}) -> + Data. -advertises_ec_ciphers([]) -> - false; -advertises_ec_ciphers([{ecdh_ecdsa, _,_,_} | _]) -> - true; -advertises_ec_ciphers([{ecdhe_ecdsa, _,_,_} | _]) -> - true; -advertises_ec_ciphers([{ecdh_rsa, _,_,_} | _]) -> - true; -advertises_ec_ciphers([{ecdhe_rsa, _,_,_} | _]) -> - true; -advertises_ec_ciphers([{ecdh_anon, _,_,_} | _]) -> - true; -advertises_ec_ciphers([_| Rest]) -> - advertises_ec_ciphers(Rest). -select_curve(#elliptic_curves{elliptic_curve_list = ClientCurves}, - #elliptic_curves{elliptic_curve_list = ServerCurves}) -> - select_curve(ClientCurves, ServerCurves); -select_curve(undefined, _) -> - %% Client did not send ECC extension use default curve if - %% ECC cipher is negotiated - {namedCurve, ?secp256r1}; -select_curve(_, []) -> - no_curve; -select_curve(Curves, [Curve| Rest]) -> - case lists:member(Curve, Curves) of - true -> - {namedCurve, Curve}; - false -> - select_curve(Curves, Rest) - end. -%% RFC 6066, Section 3: Currently, the only server names supported are -%% DNS hostnames -sni(_, disable) -> - undefined; -sni(Host, undefined) -> - sni1(Host); -sni(_Host, SNIOption) -> - sni1(SNIOption). - -sni1(Hostname) -> - case inet_parse:domain(Hostname) of - false -> undefined; - true -> #sni{hostname = Hostname} - end. %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- -apply_user_fun(Fun, OtpCert, ExtensionOrError, UserState0, SslState) -> +%%------------- Create handshake messages ---------------------------- + +int_to_bin(I) -> + L = (length(integer_to_list(I, 16)) + 1) div 2, + <<I:(L*8)>>. + +certificate_types(_, {N, M}) when N >= 3 andalso M >= 3 -> + case proplists:get_bool(ecdsa, + proplists:get_value(public_keys, crypto:supports())) of + true -> + <<?BYTE(?ECDSA_SIGN), ?BYTE(?RSA_SIGN), ?BYTE(?DSS_SIGN)>>; + false -> + <<?BYTE(?RSA_SIGN), ?BYTE(?DSS_SIGN)>> + end; + +certificate_types(#{key_exchange := KeyExchange}, _) when KeyExchange == rsa; + KeyExchange == dh_rsa; + KeyExchange == dhe_rsa; + KeyExchange == ecdhe_rsa -> + <<?BYTE(?RSA_SIGN)>>; + +certificate_types(#{key_exchange := KeyExchange}, _) when KeyExchange == dh_dss; + KeyExchange == dhe_dss; + KeyExchange == srp_dss -> + <<?BYTE(?DSS_SIGN)>>; + +certificate_types(#{key_exchange := KeyExchange}, _) when KeyExchange == dh_ecdsa; + KeyExchange == dhe_ecdsa; + KeyExchange == ecdh_ecdsa; + KeyExchange == ecdhe_ecdsa -> + <<?BYTE(?ECDSA_SIGN)>>; +certificate_types(_, _) -> + <<?BYTE(?RSA_SIGN)>>. + +certificate_authorities(CertDbHandle, CertDbRef) -> + Authorities = certificate_authorities_from_db(CertDbHandle, CertDbRef), + Enc = fun(#'OTPCertificate'{tbsCertificate=TBSCert}) -> + OTPSubj = TBSCert#'OTPTBSCertificate'.subject, + DNEncodedBin = public_key:pkix_encode('Name', OTPSubj, otp), + DNEncodedLen = byte_size(DNEncodedBin), + <<?UINT16(DNEncodedLen), DNEncodedBin/binary>> + end, + list_to_binary([Enc(Cert) || {_, Cert} <- Authorities]). + +certificate_authorities_from_db(CertDbHandle, CertDbRef) when is_reference(CertDbRef) -> + ConnectionCerts = fun({{Ref, _, _}, Cert}, Acc) when Ref == CertDbRef -> + [Cert | Acc]; + (_, Acc) -> + Acc + end, + ssl_pkix_db:foldl(ConnectionCerts, [], CertDbHandle); +certificate_authorities_from_db(_CertDbHandle, {extracted, CertDbData}) -> + %% Cache disabled, Ref contains data + lists:foldl(fun({decoded, {_Key,Cert}}, Acc) -> [Cert | Acc] end, + [], CertDbData). + +%%-------------Handle handshake messages -------------------------------- +validation_fun_and_state({Fun, UserState0}, Role, CertDbHandle, CertDbRef, + ServerNameIndication, CustomizeHostCheck, CRLCheck, CRLDbHandle, CertPath) -> + {fun(OtpCert, {extension, _} = Extension, {SslState, UserState}) -> + case ssl_certificate:validate(OtpCert, + Extension, + SslState) of + {valid, NewSslState} -> + {valid, {NewSslState, UserState}}; + {fail, Reason} -> + apply_user_fun(Fun, OtpCert, Reason, UserState, + SslState, CertPath); + {unknown, _} -> + apply_user_fun(Fun, OtpCert, + Extension, UserState, SslState, CertPath) + end; + (OtpCert, VerifyResult, {SslState, UserState}) -> + apply_user_fun(Fun, OtpCert, VerifyResult, UserState, + SslState, CertPath) + end, {{Role, CertDbHandle, CertDbRef, {ServerNameIndication, CustomizeHostCheck}, CRLCheck, CRLDbHandle}, UserState0}}; +validation_fun_and_state(undefined, Role, CertDbHandle, CertDbRef, + ServerNameIndication, CustomizeHostCheck, CRLCheck, CRLDbHandle, CertPath) -> + {fun(OtpCert, {extension, _} = Extension, SslState) -> + ssl_certificate:validate(OtpCert, + Extension, + SslState); + (OtpCert, VerifyResult, SslState) when (VerifyResult == valid) or + (VerifyResult == valid_peer) -> + case crl_check(OtpCert, CRLCheck, CertDbHandle, CertDbRef, + CRLDbHandle, VerifyResult, CertPath) of + valid -> + ssl_certificate:validate(OtpCert, + VerifyResult, + SslState); + Reason -> + {fail, Reason} + end; + (OtpCert, VerifyResult, SslState) -> + ssl_certificate:validate(OtpCert, + VerifyResult, + SslState) + end, {Role, CertDbHandle, CertDbRef, {ServerNameIndication, CustomizeHostCheck}, CRLCheck, CRLDbHandle}}. + +apply_user_fun(Fun, OtpCert, VerifyResult, UserState0, + {_, CertDbHandle, CertDbRef, _, CRLCheck, CRLDbHandle} = SslState, CertPath) when + (VerifyResult == valid) or (VerifyResult == valid_peer) -> + case Fun(OtpCert, VerifyResult, UserState0) of + {Valid, UserState} when (Valid == valid) or (Valid == valid_peer) -> + case crl_check(OtpCert, CRLCheck, CertDbHandle, CertDbRef, + CRLDbHandle, VerifyResult, CertPath) of + valid -> + {Valid, {SslState, UserState}}; + Result -> + apply_user_fun(Fun, OtpCert, Result, UserState, SslState, CertPath) + end; + {fail, _} = Fail -> + Fail + end; +apply_user_fun(Fun, OtpCert, ExtensionOrError, UserState0, SslState, _CertPath) -> case Fun(OtpCert, ExtensionOrError, UserState0) of - {valid, UserState} -> - {valid, {SslState, UserState}}; + {Valid, UserState} when (Valid == valid) or (Valid == valid_peer)-> + {Valid, {SslState, UserState}}; {fail, _} = Fail -> Fail; {unknown, UserState} -> {unknown, {SslState, UserState}} end. + +handle_path_validation_error({bad_cert, unknown_ca} = Reason, PeerCert, Chain, + Opts, Options, CertDbHandle, CertsDbRef) -> + handle_incomplete_chain(PeerCert, Chain, Opts, Options, CertDbHandle, CertsDbRef, Reason); +handle_path_validation_error({bad_cert, invalid_issuer} = Reason, PeerCert, Chain0, + Opts, Options, CertDbHandle, CertsDbRef) -> + case ssl_certificate:certificate_chain(PeerCert, CertDbHandle, CertsDbRef, Chain0) of + {ok, _, [PeerCert | Chain] = OrdedChain} when Chain =/= Chain0 -> %% Chain appaears to be unorded + {Trusted, Path} = ssl_certificate:trusted_cert_and_path(OrdedChain, + CertDbHandle, CertsDbRef, + Opts#ssl_options.partial_chain), + case public_key:pkix_path_validation(Trusted, Path, Options) of + {ok, {PublicKeyInfo,_}} -> + {PeerCert, PublicKeyInfo}; + {error, PathError} -> + handle_path_validation_error(PathError, PeerCert, Path, + Opts, Options, CertDbHandle, CertsDbRef) + end; + _ -> + path_validation_alert(Reason) + end; +handle_path_validation_error(Reason, _, _, _, _,_, _) -> + path_validation_alert(Reason). + +handle_incomplete_chain(PeerCert, Chain0, Opts, Options, CertDbHandle, CertsDbRef, PathError0) -> + case ssl_certificate:certificate_chain(PeerCert, CertDbHandle, CertsDbRef) of + {ok, _, [PeerCert | _] = Chain} when Chain =/= Chain0 -> %% Chain candidate found + {Trusted, Path} = ssl_certificate:trusted_cert_and_path(Chain, + CertDbHandle, CertsDbRef, + Opts#ssl_options.partial_chain), + case public_key:pkix_path_validation(Trusted, Path, Options) of + {ok, {PublicKeyInfo,_}} -> + {PeerCert, PublicKeyInfo}; + {error, PathError} -> + path_validation_alert(PathError) + end; + _ -> + path_validation_alert(PathError0) + end. + path_validation_alert({bad_cert, cert_expired}) -> ?ALERT_REC(?FATAL, ?CERTIFICATE_EXPIRED); path_validation_alert({bad_cert, invalid_issuer}) -> @@ -1395,14 +1367,131 @@ path_validation_alert({bad_cert, name_not_permitted}) -> ?ALERT_REC(?FATAL, ?BAD_CERTIFICATE); path_validation_alert({bad_cert, unknown_critical_extension}) -> ?ALERT_REC(?FATAL, ?UNSUPPORTED_CERTIFICATE); -path_validation_alert({bad_cert, cert_revoked}) -> +path_validation_alert({bad_cert, {revoked, _}}) -> ?ALERT_REC(?FATAL, ?CERTIFICATE_REVOKED); +path_validation_alert({bad_cert, {revocation_status_undetermined, Details}}) -> + Alert = ?ALERT_REC(?FATAL, ?BAD_CERTIFICATE), + Alert#alert{reason = Details}; 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). + +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. +do_digitally_signed({3, Minor}, Hash, HashAlgo, #{algorithm := Alg} = Engine) + when Minor >= 3 -> + crypto:sign(Alg, HashAlgo, {digest, Hash}, maps:remove(algorithm, Engine)); +do_digitally_signed({3, Minor}, Hash, HashAlgo, Key) when Minor >= 3 -> + public_key:sign({digest, Hash}, HashAlgo, Key); +do_digitally_signed(_Version, Hash, _HashAlgo, #'RSAPrivateKey'{} = Key) -> + public_key:encrypt_private(Hash, Key, + [{rsa_pad, rsa_pkcs1_padding}]); +do_digitally_signed({3, _}, Hash, _, + #{algorithm := rsa} = Engine) -> + crypto:private_encrypt(rsa, Hash, maps:remove(algorithm, Engine), + rsa_pkcs1_padding); +do_digitally_signed({3, _}, Hash, HashAlgo, #{algorithm := Alg} = Engine) -> + crypto:sign(Alg, HashAlgo, {digest, Hash}, maps:remove(algorithm, Engine)); +do_digitally_signed(_Version, Hash, HashAlgo, Key) -> + public_key:sign({digest, Hash}, HashAlgo, Key). + +bad_key(#'DSAPrivateKey'{}) -> + unacceptable_dsa_key; +bad_key(#'RSAPrivateKey'{}) -> + unacceptable_rsa_key; +bad_key(#'ECPrivateKey'{}) -> + unacceptable_ecdsa_key. + +crl_check(_, false, _,_,_, _, _) -> + valid; +crl_check(_, peer, _, _,_, valid, _) -> %% Do not check CAs with this option. + valid; +crl_check(OtpCert, Check, CertDbHandle, CertDbRef, {Callback, CRLDbHandle}, _, CertPath) -> + Options = [{issuer_fun, {fun(_DP, CRL, Issuer, DBInfo) -> + ssl_crl:trusted_cert_and_path(CRL, Issuer, {CertPath, + DBInfo}) + end, {CertDbHandle, CertDbRef}}}, + {update_crl, fun(DP, CRL) -> Callback:fresh_crl(DP, CRL) end}, + {undetermined_details, true} + ], + case dps_and_crls(OtpCert, Callback, CRLDbHandle, ext) of + no_dps -> + crl_check_same_issuer(OtpCert, Check, + dps_and_crls(OtpCert, Callback, CRLDbHandle, same_issuer), + Options); + DpsAndCRLs -> %% This DP list may be empty if relevant CRLs existed + %% but could not be retrived, will result in {bad_cert, revocation_status_undetermined} + case public_key:pkix_crls_validate(OtpCert, DpsAndCRLs, Options) of + {bad_cert, {revocation_status_undetermined, _}} -> + crl_check_same_issuer(OtpCert, Check, dps_and_crls(OtpCert, Callback, + CRLDbHandle, same_issuer), Options); + Other -> + Other + end + end. + +crl_check_same_issuer(OtpCert, best_effort, Dps, Options) -> + case public_key:pkix_crls_validate(OtpCert, Dps, Options) of + {bad_cert, {revocation_status_undetermined, _}} -> + valid; + Other -> + Other + end; +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 -> + Issuer = OtpCert#'OTPCertificate'.tbsCertificate#'OTPTBSCertificate'.issuer, + CRLs = distpoints_lookup(DistPoints, Issuer, Callback, CRLDbHandle), + dps_and_crls(DistPoints, CRLs, []) + end; + +dps_and_crls(OtpCert, Callback, CRLDbHandle, same_issuer) -> + DP = #'DistributionPoint'{distributionPoint = {fullName, GenNames}} = + public_key:pkix_dist_point(OtpCert), + CRLs = lists:flatmap(fun({directoryName, Issuer}) -> + Callback:select(Issuer, CRLDbHandle); + (_) -> + [] + end, GenNames), + [{DP, {CRL, public_key:der_decode('CertificateList', CRL)}} || CRL <- CRLs]. + +dps_and_crls([], _, Acc) -> + Acc; +dps_and_crls([DP | Rest], CRLs, Acc) -> + DpCRL = [{DP, {CRL, public_key:der_decode('CertificateList', CRL)}} || CRL <- CRLs], + dps_and_crls(Rest, CRLs, DpCRL ++ Acc). + +distpoints_lookup([],_, _, _) -> + []; +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, Issuer, Callback, CRLDbHandle); + CRLs -> + CRLs + end. encrypted_premaster_secret(Secret, RSAPublicKey) -> try @@ -1411,20 +1500,10 @@ 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({3, Minor}, Hash, HashAlgo, Key) when Minor >= 3 -> - public_key:sign({digest, Hash}, HashAlgo, Key); -digitally_signed(_Version, Hash, HashAlgo, #'DSAPrivateKey'{} = Key) -> - public_key:sign({digest, Hash}, HashAlgo, Key); -digitally_signed(_Version, Hash, _HashAlgo, #'RSAPrivateKey'{} = Key) -> - public_key:encrypt_private(Hash, Key, - [{rsa_pad, rsa_pkcs1_padding}]); -digitally_signed(_Version, Hash, HashAlgo, Key) -> - public_key:sign({digest, Hash}, HashAlgo, Key). - calc_certificate_verify({3, 0}, HashAlgo, MasterSecret, Handshake) -> ssl_v3:certificate_verify(HashAlgo, MasterSecret, lists:reverse(Handshake)); calc_certificate_verify({3, N}, HashAlgo, _MasterSecret, Handshake) -> @@ -1435,8 +1514,9 @@ calc_finished({3, 0}, Role, _PrfAlgo, MasterSecret, Handshake) -> calc_finished({3, N}, Role, PrfAlgo, MasterSecret, Handshake) -> tls_v1:finished(Role, N, PrfAlgo, MasterSecret, lists:reverse(Handshake)). -master_secret(_RecordCB, Version, MasterSecret, +master_secret(Version, MasterSecret, #security_parameters{ + bulk_cipher_algorithm = BCA, client_random = ClientRandom, server_random = ServerRandom, hash_size = HashSize, @@ -1455,8 +1535,8 @@ master_secret(_RecordCB, Version, MasterSecret, ssl_record:set_mac_secret(ClientWriteMacSecret, ServerWriteMacSecret, Role, ConnStates1), - ClientCipherState = #cipher_state{iv = ClientIV, key = ClientWriteKey}, - ServerCipherState = #cipher_state{iv = ServerIV, key = ServerWriteKey}, + ClientCipherState = ssl_cipher:cipher_init(BCA, ClientIV, ClientWriteKey), + ServerCipherState = ssl_cipher:cipher_init(BCA, ServerIV, ServerWriteKey), {MasterSecret, ssl_record:set_pending_cipher_state(ConnStates2, ClientCipherState, ServerCipherState, Role)}. @@ -1476,24 +1556,7 @@ calc_master_secret({3,0}, _PrfAlgo, PremasterSecret, ClientRandom, ServerRandom) calc_master_secret({3,_}, PrfAlgo, PremasterSecret, ClientRandom, ServerRandom) -> tls_v1:master_secret(PrfAlgo, PremasterSecret, ClientRandom, ServerRandom). - -handle_renegotiation_extension(Role, RecordCB, Version, Info, Random, NegotiatedCipherSuite, - ClientCipherSuites, Compression, - ConnectionStates0, Renegotiation, SecureRenegotation) -> - case handle_renegotiation_info(RecordCB, Role, Info, ConnectionStates0, - Renegotiation, SecureRenegotation, - ClientCipherSuites) of - {ok, ConnectionStates} -> - hello_pending_connection_states(RecordCB, Role, - Version, - NegotiatedCipherSuite, - Random, - Compression, - ConnectionStates); - #alert{} = Alert -> - throw(Alert) - end. - + %% Update pending connection states with parameters exchanged via %% hello messages %% NOTE : Role is the role of the receiver of the hello message @@ -1517,25 +1580,59 @@ hello_pending_connection_states(_RecordCB, Role, Version, CipherSuite, Random, C NewWriteSecParams, ConnectionStates). -hello_security_parameters(client, Version, ConnectionState, CipherSuite, Random, +hello_security_parameters(client, Version, #{security_parameters := SecParams}, CipherSuite, Random, Compression) -> - SecParams = ConnectionState#connection_state.security_parameters, NewSecParams = ssl_cipher:security_parameters(Version, CipherSuite, SecParams), NewSecParams#security_parameters{ server_random = Random, compression_algorithm = Compression }; -hello_security_parameters(server, Version, ConnectionState, CipherSuite, Random, +hello_security_parameters(server, Version, #{security_parameters := SecParams}, CipherSuite, Random, Compression) -> - SecParams = ConnectionState#connection_state.security_parameters, NewSecParams = ssl_cipher:security_parameters(Version, CipherSuite, SecParams), NewSecParams#security_parameters{ client_random = Random, compression_algorithm = Compression }. -%%-------------Encode/Decode -------------------------------- +select_compression(_CompressionMetodes) -> + ?NULL. + +do_select_version(_, ClientVersion, []) -> + ClientVersion; +do_select_version(RecordCB, ClientVersion, [Version | Versions]) -> + case RecordCB:is_higher(Version, ClientVersion) of + true -> + %% Version too high for client - keep looking + do_select_version(RecordCB, ClientVersion, Versions); + false -> + %% Version ok for client - look for a higher + do_select_version(RecordCB, ClientVersion, Versions, Version) + end. +%% +do_select_version(_, _, [], GoodVersion) -> + GoodVersion; +do_select_version( + RecordCB, ClientVersion, [Version | Versions], GoodVersion) -> + BetterVersion = + case RecordCB:is_higher(Version, ClientVersion) of + true -> + %% Version too high for client + GoodVersion; + false -> + %% Version ok for client + case RecordCB:is_higher(Version, GoodVersion) of + true -> + %% Use higher version + Version; + false -> + GoodVersion + end + end, + do_select_version(RecordCB, ClientVersion, Versions, BetterVersion). + +%%-------------Encode handshakes -------------------------------- encode_server_key(#server_dh_params{dh_p = P, dh_g = G, dh_y = Y}) -> PLen = byte_size(P), @@ -1561,6 +1658,18 @@ encode_server_key(#server_dhe_psk_params{ YLen = byte_size(Y), <<?UINT16(Len), PskIdentityHint/binary, ?UINT16(PLen), P/binary, ?UINT16(GLen), G/binary, ?UINT16(YLen), Y/binary>>; +encode_server_key(Params = #server_ecdhe_psk_params{hint = undefined}) -> + encode_server_key(Params#server_ecdhe_psk_params{hint = <<>>}); +encode_server_key(#server_ecdhe_psk_params{ + hint = PskIdentityHint, + dh_params = #server_ecdh_params{ + curve = {namedCurve, ECCurve}, public = ECPubKey}}) -> + %%TODO: support arbitrary keys + Len = byte_size(PskIdentityHint), + KLen = size(ECPubKey), + <<?UINT16(Len), PskIdentityHint/binary, + ?BYTE(?NAMED_CURVE), ?UINT16((tls_v1:oid_to_enum(ECCurve))), + ?BYTE(KLen), ECPubKey/binary>>; encode_server_key(#server_srp_params{srp_n = N, srp_g = G, srp_s = S, srp_b = B}) -> NLen = byte_size(N), GLen = byte_size(G), @@ -1593,6 +1702,12 @@ encode_client_key(#client_dhe_psk_identity{identity = Id, dh_public = DHPublic}, Len = byte_size(Id), DHLen = byte_size(DHPublic), <<?UINT16(Len), Id/binary, ?UINT16(DHLen), DHPublic/binary>>; +encode_client_key(Identity = #client_ecdhe_psk_identity{identity = undefined}, Version) -> + encode_client_key(Identity#client_ecdhe_psk_identity{identity = <<"psk_identity">>}, Version); +encode_client_key(#client_ecdhe_psk_identity{identity = Id, dh_public = DHPublic}, _) -> + Len = byte_size(Id), + DHLen = byte_size(DHPublic), + <<?UINT16(Len), Id/binary, ?BYTE(DHLen), DHPublic/binary>>; encode_client_key(Identity = #client_rsa_psk_identity{identity = undefined}, Version) -> encode_client_key(Identity#client_rsa_psk_identity{identity = <<"psk_identity">>}, Version); encode_client_key(#client_rsa_psk_identity{identity = Id, exchange_keys = ExchangeKeys}, Version) -> @@ -1623,17 +1738,137 @@ encode_protocol(Protocol, Acc) -> Len = byte_size(Protocol), <<Acc/binary, ?BYTE(Len), Protocol/binary>>. +enc_server_key_exchange(Version, Params, {HashAlgo, SignAlgo}, + ClientRandom, ServerRandom, PrivateKey) -> + EncParams = encode_server_key(Params), + case HashAlgo of + null -> + #server_key_params{params = Params, + params_bin = EncParams, + hashsign = {null, anon}, + signature = <<>>}; + _ -> + Hash = + server_key_exchange_hash(HashAlgo, <<ClientRandom/binary, + ServerRandom/binary, + EncParams/binary>>), + Signature = digitally_signed(Version, Hash, HashAlgo, PrivateKey), + #server_key_params{params = Params, + params_bin = EncParams, + hashsign = {HashAlgo, SignAlgo}, + signature = Signature} + end. + +%% While the RFC opens the door to allow ALPN during renegotiation, in practice +%% this does not work and it is recommended to ignore any ALPN extension during +%% renegotiation, as done here. +encode_alpn(_, true) -> + undefined; +encode_alpn(undefined, _) -> + undefined; +encode_alpn(Protocols, _) -> + #alpn{extension_data = lists:foldl(fun encode_protocol/2, <<>>, Protocols)}. + +hello_extensions_list(#hello_extensions{renegotiation_info = RenegotiationInfo, + srp = SRP, + signature_algs = HashSigns, + ec_point_formats = EcPointFormats, + elliptic_curves = EllipticCurves, + alpn = ALPN, + next_protocol_negotiation = NextProtocolNegotiation, + sni = Sni}) -> + [Ext || Ext <- [RenegotiationInfo, SRP, HashSigns, + EcPointFormats, EllipticCurves, ALPN, NextProtocolNegotiation, Sni], Ext =/= undefined]. + +%%-------------Decode handshakes--------------------------------- +dec_server_key(<<?UINT16(PLen), P:PLen/binary, + ?UINT16(GLen), G:GLen/binary, + ?UINT16(YLen), Y:YLen/binary, _/binary>> = KeyStruct, + ?KEY_EXCHANGE_DIFFIE_HELLMAN, Version) -> + Params = #server_dh_params{dh_p = P, dh_g = G, dh_y = Y}, + {BinMsg, HashSign, Signature} = dec_server_key_params(PLen + GLen + YLen + 6, KeyStruct, Version), + #server_key_params{params = Params, + params_bin = BinMsg, + hashsign = HashSign, + signature = Signature}; +%% ECParameters with named_curve +%% TODO: explicit curve +dec_server_key(<<?BYTE(?NAMED_CURVE), ?UINT16(CurveID), + ?BYTE(PointLen), ECPoint:PointLen/binary, + _/binary>> = KeyStruct, + ?KEY_EXCHANGE_EC_DIFFIE_HELLMAN, Version) -> + Params = #server_ecdh_params{curve = {namedCurve, tls_v1:enum_to_oid(CurveID)}, + public = ECPoint}, + {BinMsg, HashSign, Signature} = dec_server_key_params(PointLen + 4, KeyStruct, Version), + #server_key_params{params = Params, + params_bin = BinMsg, + hashsign = HashSign, + signature = Signature}; +dec_server_key(<<?UINT16(Len), PskIdentityHint:Len/binary, _/binary>> = KeyStruct, + KeyExchange, Version) + when KeyExchange == ?KEY_EXCHANGE_PSK; KeyExchange == ?KEY_EXCHANGE_RSA_PSK -> + Params = #server_psk_params{ + hint = PskIdentityHint}, + {BinMsg, HashSign, Signature} = dec_server_key_params(Len + 2, KeyStruct, Version), + #server_key_params{params = Params, + params_bin = BinMsg, + hashsign = HashSign, + signature = Signature}; +dec_server_key(<<?UINT16(Len), IdentityHint:Len/binary, + ?UINT16(PLen), P:PLen/binary, + ?UINT16(GLen), G:GLen/binary, + ?UINT16(YLen), Y:YLen/binary, _/binary>> = KeyStruct, + ?KEY_EXCHANGE_DHE_PSK, Version) -> + DHParams = #server_dh_params{dh_p = P, dh_g = G, dh_y = Y}, + Params = #server_dhe_psk_params{ + hint = IdentityHint, + dh_params = DHParams}, + {BinMsg, HashSign, Signature} = dec_server_key_params(Len + PLen + GLen + YLen + 8, KeyStruct, Version), + #server_key_params{params = Params, + params_bin = BinMsg, + hashsign = HashSign, + signature = Signature}; +dec_server_key(<<?UINT16(Len), IdentityHint:Len/binary, + ?BYTE(?NAMED_CURVE), ?UINT16(CurveID), + ?BYTE(PointLen), ECPoint:PointLen/binary, + _/binary>> = KeyStruct, + ?KEY_EXCHANGE_EC_DIFFIE_HELLMAN_PSK, Version) -> + DHParams = #server_ecdh_params{ + curve = {namedCurve, tls_v1:enum_to_oid(CurveID)}, + public = ECPoint}, + Params = #server_ecdhe_psk_params{ + hint = IdentityHint, + dh_params = DHParams}, + {BinMsg, HashSign, Signature} = dec_server_key_params(Len + 2 + PointLen + 4, KeyStruct, Version), + #server_key_params{params = Params, + params_bin = BinMsg, + hashsign = HashSign, + signature = Signature}; +dec_server_key(<<?UINT16(NLen), N:NLen/binary, + ?UINT16(GLen), G:GLen/binary, + ?BYTE(SLen), S:SLen/binary, + ?UINT16(BLen), B:BLen/binary, _/binary>> = KeyStruct, + ?KEY_EXCHANGE_SRP, Version) -> + Params = #server_srp_params{srp_n = N, srp_g = G, srp_s = S, srp_b = B}, + {BinMsg, HashSign, Signature} = dec_server_key_params(NLen + GLen + SLen + BLen + 7, KeyStruct, Version), + #server_key_params{params = Params, + params_bin = BinMsg, + hashsign = HashSign, + signature = Signature}; +dec_server_key(_, KeyExchange, _) -> + throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, {unknown_or_malformed_key_exchange, KeyExchange})). + dec_client_key(PKEPMS, ?KEY_EXCHANGE_RSA, {3, 0}) -> #encrypted_premaster_secret{premaster_secret = PKEPMS}; 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}; @@ -1644,6 +1879,10 @@ dec_client_key(<<?UINT16(Len), Id:Len/binary, ?UINT16(DH_YLen), DH_Y:DH_YLen/binary>>, ?KEY_EXCHANGE_DHE_PSK, _) -> #client_dhe_psk_identity{identity = Id, dh_public = DH_Y}; +dec_client_key(<<?UINT16(Len), Id:Len/binary, + ?BYTE(DH_YLen), DH_Y:DH_YLen/binary>>, + ?KEY_EXCHANGE_EC_DIFFIE_HELLMAN_PSK, _) -> + #client_ecdhe_psk_identity{identity = Id, dh_public = DH_Y}; dec_client_key(<<?UINT16(Len), Id:Len/binary, PKEPMS/binary>>, ?KEY_EXCHANGE_RSA_PSK, {3, 0}) -> #client_rsa_psk_identity{identity = Id, @@ -1677,10 +1916,14 @@ 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; +dec_hello_extensions(<<?UINT16(?ALPN_EXT), ?UINT16(ExtLen), ?UINT16(Len), ExtensionData:Len/binary, Rest/binary>>, Acc) + when Len + 2 =:= ExtLen -> + ALPN = #alpn{extension_data = ExtensionData}, + dec_hello_extensions(Rest, Acc#hello_extensions{alpn = ALPN}); dec_hello_extensions(<<?UINT16(?NEXTPROTONEG_EXT), ?UINT16(Len), ExtensionData:Len/binary, Rest/binary>>, Acc) -> NextP = #next_protocol_negotiation{extension_data = ExtensionData}, dec_hello_extensions(Rest, Acc#hello_extensions{next_protocol_negotiation = NextP}); @@ -1707,7 +1950,7 @@ dec_hello_extensions(<<?UINT16(?SIGNATURE_ALGORITHMS_EXT), ?UINT16(Len), <<?UINT16(SignAlgoListLen), SignAlgoList/binary>> = ExtData, HashSignAlgos = [{ssl_cipher:hash_algorithm(Hash), ssl_cipher:sign_algorithm(Sign)} || <<?BYTE(Hash), ?BYTE(Sign)>> <= SignAlgoList], - dec_hello_extensions(Rest, Acc#hello_extensions{hash_signs = + dec_hello_extensions(Rest, Acc#hello_extensions{signature_algs = #hash_sign_algos{hash_sign_algos = HashSignAlgos}}); dec_hello_extensions(<<?UINT16(?ELLIPTIC_CURVES_EXT), ?UINT16(Len), @@ -1735,7 +1978,7 @@ dec_hello_extensions(<<?UINT16(?EC_POINT_FORMATS_EXT), ?UINT16(Len), ECPointFormats}}); dec_hello_extensions(<<?UINT16(?SNI_EXT), ?UINT16(Len), Rest/binary>>, Acc) when Len == 0 -> - dec_hello_extensions(Rest, Acc#hello_extensions{sni = ""}); %% Server may send an empy SNI + dec_hello_extensions(Rest, Acc#hello_extensions{sni = #sni{hostname = ""}}); %% Server may send an empy SNI dec_hello_extensions(<<?UINT16(?SNI_EXT), ?UINT16(Len), ExtData:Len/binary, Rest/binary>>, Acc) -> @@ -1760,19 +2003,25 @@ dec_sni(<<?BYTE(?SNI_NAMETYPE_HOST_NAME), ?UINT16(Len), dec_sni(<<?BYTE(_), ?UINT16(Len), _:Len, Rest/binary>>) -> dec_sni(Rest); dec_sni(_) -> undefined. +decode_alpn(undefined) -> + undefined; +decode_alpn(#alpn{extension_data=Data}) -> + decode_protocols(Data, []). + decode_next_protocols({next_protocol_negotiation, Protocols}) -> - decode_next_protocols(Protocols, []). -decode_next_protocols(<<>>, Acc) -> + decode_protocols(Protocols, []). + +decode_protocols(<<>>, Acc) -> lists:reverse(Acc); -decode_next_protocols(<<?BYTE(Len), Protocol:Len/binary, Rest/binary>>, Acc) -> +decode_protocols(<<?BYTE(Len), Protocol:Len/binary, Rest/binary>>, Acc) -> case Len of 0 -> - {error, invalid_next_protocols}; + {error, invalid_protocols}; _ -> - decode_next_protocols(Rest, [Protocol|Acc]) + decode_protocols(Rest, [Protocol|Acc]) end; -decode_next_protocols(_Bytes, _Acc) -> - {error, invalid_next_protocols}. +decode_protocols(_Bytes, _Acc) -> + {error, invalid_protocols}. %% encode/decode stream of certificate data to/from list of certificate data certs_to_list(ASN1Certs) -> @@ -1803,10 +2052,11 @@ from_2bytes(<<>>, Acc) -> lists:reverse(Acc); from_2bytes(<<?UINT16(N), Rest/binary>>, Acc) -> from_2bytes(Rest, [?uint16(N) | Acc]). + key_exchange_alg(rsa) -> ?KEY_EXCHANGE_RSA; key_exchange_alg(Alg) when Alg == dhe_rsa; Alg == dhe_dss; - Alg == dh_dss; Alg == dh_rsa; Alg == dh_anon -> + Alg == dh_dss; Alg == dh_rsa; Alg == dh_anon -> ?KEY_EXCHANGE_DIFFIE_HELLMAN; key_exchange_alg(Alg) when Alg == ecdhe_rsa; Alg == ecdh_rsa; Alg == ecdhe_ecdsa; Alg == ecdh_ecdsa; @@ -1816,6 +2066,8 @@ key_exchange_alg(psk) -> ?KEY_EXCHANGE_PSK; key_exchange_alg(dhe_psk) -> ?KEY_EXCHANGE_DHE_PSK; +key_exchange_alg(ecdhe_psk) -> + ?KEY_EXCHANGE_EC_DIFFIE_HELLMAN_PSK; key_exchange_alg(rsa_psk) -> ?KEY_EXCHANGE_RSA_PSK; key_exchange_alg(Alg) @@ -1824,8 +2076,144 @@ key_exchange_alg(Alg) key_exchange_alg(_) -> ?NULL. +%%-------------Cipher suite handling ----------------------------- +select_cipher_suite(CipherSuites, Suites, false) -> + select_cipher_suite(CipherSuites, Suites); +select_cipher_suite(CipherSuites, Suites, true) -> + select_cipher_suite(Suites, CipherSuites). + +select_cipher_suite([], _) -> + no_suite; +select_cipher_suite([Suite | ClientSuites], SupportedSuites) -> + case is_member(Suite, SupportedSuites) of + true -> + Suite; + false -> + select_cipher_suite(ClientSuites, SupportedSuites) + end. + +is_member(Suite, SupportedSuites) -> + lists:member(Suite, SupportedSuites). + +psk_secret(PSKIdentity, PSKLookup) -> + case handle_psk_identity(PSKIdentity, PSKLookup) of + {ok, PSK} when is_binary(PSK) -> + Len = erlang:byte_size(PSK), + <<?UINT16(Len), 0:(Len*8), ?UINT16(Len), PSK/binary>>; + #alert{} = Alert -> + Alert; + _ -> + throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)) + end. + +psk_secret(PSKIdentity, PSKLookup, PremasterSecret) -> + case handle_psk_identity(PSKIdentity, PSKLookup) of + {ok, PSK} when is_binary(PSK) -> + Len = erlang:byte_size(PremasterSecret), + PSKLen = erlang:byte_size(PSK), + <<?UINT16(Len), PremasterSecret/binary, ?UINT16(PSKLen), PSK/binary>>; + #alert{} = Alert -> + Alert; + _ -> + throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)) + end. + +handle_psk_identity(_PSKIdentity, LookupFun) + when LookupFun == undefined -> + error; +handle_psk_identity(PSKIdentity, {Fun, UserState}) -> + Fun(psk, PSKIdentity, UserState). + + +filter_hashsigns([], [], _, _, Acc) -> + lists:reverse(Acc); +filter_hashsigns([Suite | Suites], [#{key_exchange := KeyExchange} | Algos], HashSigns, Version, + Acc) when KeyExchange == dhe_ecdsa; + KeyExchange == ecdhe_ecdsa -> + do_filter_hashsigns(ecdsa, Suite, Suites, Algos, HashSigns, Version, Acc); +filter_hashsigns([Suite | Suites], [#{key_exchange := KeyExchange} | Algos], HashSigns, Version, + Acc) when KeyExchange == rsa; + KeyExchange == dhe_rsa; + KeyExchange == ecdhe_rsa; + KeyExchange == srp_rsa; + KeyExchange == rsa_psk -> + do_filter_hashsigns(rsa, Suite, Suites, Algos, HashSigns, Version, Acc); +filter_hashsigns([Suite | Suites], [#{key_exchange := KeyExchange} | Algos], HashSigns, Version, Acc) when + KeyExchange == dhe_dss; + KeyExchange == srp_dss -> + do_filter_hashsigns(dsa, Suite, Suites, Algos, HashSigns, Version, Acc); +filter_hashsigns([Suite | Suites], [#{key_exchange := KeyExchange} | Algos], HashSigns, Verion, + Acc) when + KeyExchange == dh_dss; + KeyExchange == dh_rsa; + KeyExchange == dh_ecdsa; + KeyExchange == ecdh_rsa; + KeyExchange == ecdh_ecdsa -> + %% Fixed DH certificates MAY be signed with any hash/signature + %% algorithm pair appearing in the hash_sign extension. The names + %% DH_DSS, DH_RSA, ECDH_ECDSA, and ECDH_RSA are historical. + filter_hashsigns(Suites, Algos, HashSigns, Verion, [Suite| Acc]); +filter_hashsigns([Suite | Suites], [#{key_exchange := KeyExchange} | Algos], HashSigns, Version, + Acc) when + KeyExchange == dh_anon; + KeyExchange == ecdh_anon; + KeyExchange == srp_anon; + KeyExchange == psk; + KeyExchange == dhe_psk; + KeyExchange == ecdhe_psk -> + %% In this case hashsigns is not used as the kexchange is anonaymous + filter_hashsigns(Suites, Algos, HashSigns, Version, [Suite| Acc]). + +do_filter_hashsigns(SignAlgo, Suite, Suites, Algos, HashSigns, Version, Acc) -> + case lists:keymember(SignAlgo, 2, HashSigns) of + true -> + filter_hashsigns(Suites, Algos, HashSigns, Version, [Suite| Acc]); + false -> + filter_hashsigns(Suites, Algos, HashSigns, Version, Acc) + end. + +filter_unavailable_ecc_suites(no_curve, Suites) -> + ECCSuites = ssl_cipher:filter_suites(Suites, #{key_exchange_filters => [fun(ecdh_ecdsa) -> true; + (ecdhe_ecdsa) -> true; + (ecdh_rsa) -> true; + (_) -> false + end], + cipher_filters => [], + mac_filters => [], + prf_filters => []}), + Suites -- ECCSuites; +filter_unavailable_ecc_suites(_, Suites) -> + Suites. %%-------------Extension handling -------------------------------- +handle_renegotiation_extension(Role, RecordCB, Version, Info, Random, NegotiatedCipherSuite, + ClientCipherSuites, Compression, + ConnectionStates0, Renegotiation, SecureRenegotation) -> + case handle_renegotiation_info(RecordCB, Role, Info, ConnectionStates0, + Renegotiation, SecureRenegotation, + ClientCipherSuites) of + {ok, ConnectionStates} -> + hello_pending_connection_states(RecordCB, Role, + Version, + NegotiatedCipherSuite, + Random, + Compression, + ConnectionStates); + #alert{} = Alert -> + throw(Alert) + end. + +%% Receive protocols, choose one from the list, return it. +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) -> + case lists:member(ServerProtocol, ClientProtocols) of + true -> ServerProtocol; + false -> handle_alpn_extension(Tail, ClientProtocols) + end. + handle_next_protocol(undefined, _NextProtocolSelector, _Renegotiating) -> undefined; @@ -1837,7 +2225,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. @@ -1857,17 +2245,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. @@ -1877,81 +2265,224 @@ handle_srp_extension(undefined, Session) -> handle_srp_extension(#srp{username = Username}, Session) -> Session#session{srp_username = Username}. -%%-------------Misc -------------------------------- -select_cipher_suite(CipherSuites, Suites, false) -> - select_cipher_suite(CipherSuites, Suites); -select_cipher_suite(CipherSuites, Suites, true) -> - select_cipher_suite(Suites, CipherSuites). +sign_algo(?rsaEncryption) -> + rsa; +sign_algo(?'id-ecPublicKey') -> + ecdsa; +sign_algo(?'id-dsa') -> + dsa; +sign_algo(Alg) -> + {_, Sign} =public_key:pkix_sign_types(Alg), + Sign. + +is_acceptable_hash_sign( _, KeyExAlgo, _) when + KeyExAlgo == psk; + KeyExAlgo == dhe_psk; + KeyExAlgo == ecdhe_psk; + KeyExAlgo == srp_anon; + KeyExAlgo == dh_anon; + KeyExAlgo == ecdhe_anon + -> + true; +is_acceptable_hash_sign(Algos,_, SupportedHashSigns) -> + is_acceptable_hash_sign(Algos, SupportedHashSigns). + +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. + +server_name(_, _, server) -> + undefined; %% Not interesting to check your own name. +server_name(undefined, Host, client) -> + {fallback, Host}; %% Fallback to Host argument to connect +server_name(SNI, _, client) -> + SNI. %% If Server Name Indication is available + +client_ecc_extensions(SupportedECCs) -> + CryptoSupport = proplists:get_value(public_keys, crypto:supports()), + case proplists:get_bool(ecdh, CryptoSupport) of + true -> + EcPointFormats = #ec_point_formats{ec_point_format_list = [?ECPOINT_UNCOMPRESSED]}, + EllipticCurves = SupportedECCs, + {EcPointFormats, EllipticCurves}; + _ -> + {undefined, undefined} + end. -select_cipher_suite([], _) -> - no_suite; -select_cipher_suite([Suite | ClientSuites], SupportedSuites) -> - case is_member(Suite, SupportedSuites) of +server_ecc_extension(_Version, EcPointFormats) -> + CryptoSupport = proplists:get_value(public_keys, crypto:supports()), + case proplists:get_bool(ecdh, CryptoSupport) of true -> - Suite; - false -> - select_cipher_suite(ClientSuites, SupportedSuites) + handle_ecc_point_fmt_extension(EcPointFormats); + false -> + undefined end. -int_to_bin(I) -> - L = (length(integer_to_list(I, 16)) + 1) div 2, - <<I:(L*8)>>. +handle_ecc_point_fmt_extension(undefined) -> + undefined; +handle_ecc_point_fmt_extension(_) -> + #ec_point_formats{ec_point_format_list = [?ECPOINT_UNCOMPRESSED]}. -is_member(Suite, SupportedSuites) -> - lists:member(Suite, SupportedSuites). +advertises_ec_ciphers([]) -> + false; +advertises_ec_ciphers([#{key_exchange := ecdh_ecdsa} | _]) -> + true; +advertises_ec_ciphers([#{key_exchange := ecdhe_ecdsa} | _]) -> + true; +advertises_ec_ciphers([#{key_exchange := ecdh_rsa} | _]) -> + true; +advertises_ec_ciphers([#{key_exchange := ecdhe_rsa} | _]) -> + true; +advertises_ec_ciphers([#{key_exchange := ecdh_anon} | _]) -> + true; +advertises_ec_ciphers([{ecdhe_psk, _,_,_} | _]) -> + true; +advertises_ec_ciphers([_| Rest]) -> + advertises_ec_ciphers(Rest). -select_compression(_CompressionMetodes) -> - ?NULL. +select_shared_curve([], _) -> + no_curve; +select_shared_curve([Curve | Rest], Curves) -> + case lists:member(Curve, Curves) of + true -> + {namedCurve, Curve}; + false -> + select_shared_curve(Rest, Curves) + end. --define(TLSEXT_SIGALG_RSA(MD), {MD, rsa}). --define(TLSEXT_SIGALG_DSA(MD), {MD, dsa}). --define(TLSEXT_SIGALG_ECDSA(MD), {MD, ecdsa}). - --define(TLSEXT_SIGALG(MD), ?TLSEXT_SIGALG_ECDSA(MD), ?TLSEXT_SIGALG_RSA(MD)). - -advertised_hash_signs({Major, Minor}) when Major >= 3 andalso Minor >= 3 -> - HashSigns = [?TLSEXT_SIGALG(sha512), - ?TLSEXT_SIGALG(sha384), - ?TLSEXT_SIGALG(sha256), - ?TLSEXT_SIGALG(sha224), - ?TLSEXT_SIGALG(sha), - ?TLSEXT_SIGALG_DSA(sha), - ?TLSEXT_SIGALG_RSA(md5)], - CryptoSupport = crypto:supports(), - HasECC = proplists:get_bool(ecdsa, proplists:get_value(public_keys, CryptoSupport)), - Hashs = proplists:get_value(hashs, CryptoSupport), - #hash_sign_algos{hash_sign_algos = - lists:filter(fun({Hash, ecdsa}) -> HasECC andalso proplists:get_bool(Hash, Hashs); - ({Hash, _}) -> proplists:get_bool(Hash, Hashs) end, HashSigns)}; -advertised_hash_signs(_) -> - undefined. +sni(undefined) -> + undefined; +sni(disable) -> + undefined; +sni(Hostname) -> + #sni{hostname = Hostname}. -psk_secret(PSKIdentity, PSKLookup) -> - case handle_psk_identity(PSKIdentity, PSKLookup) of - {ok, PSK} when is_binary(PSK) -> - Len = erlang:byte_size(PSK), - <<?UINT16(Len), 0:(Len*8), ?UINT16(Len), PSK/binary>>; - #alert{} = Alert -> - Alert; - _ -> - throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)) +renegotiation_info(_, client, _, false) -> + #renegotiation_info{renegotiated_connection = undefined}; +renegotiation_info(_RecordCB, server, ConnectionStates, false) -> + ConnectionState = ssl_record:current_connection_state(ConnectionStates, read), + case maps:get(secure_renegotiation, ConnectionState) of + true -> + #renegotiation_info{renegotiated_connection = ?byte(0)}; + false -> + #renegotiation_info{renegotiated_connection = undefined} + end; +renegotiation_info(_RecordCB, client, ConnectionStates, true) -> + ConnectionState = ssl_record:current_connection_state(ConnectionStates, read), + case maps:get(secure_renegotiation, ConnectionState) of + true -> + Data = maps:get(client_verify_data, ConnectionState), + #renegotiation_info{renegotiated_connection = Data}; + false -> + #renegotiation_info{renegotiated_connection = undefined} + end; + +renegotiation_info(_RecordCB, server, ConnectionStates, true) -> + ConnectionState = ssl_record:current_connection_state(ConnectionStates, read), + case maps:get(secure_renegotiation, ConnectionState) of + true -> + CData = maps:get(client_verify_data, ConnectionState), + SData = maps:get(server_verify_data, ConnectionState), + #renegotiation_info{renegotiated_connection = <<CData/binary, SData/binary>>}; + false -> + #renegotiation_info{renegotiated_connection = undefined} end. -psk_secret(PSKIdentity, PSKLookup, PremasterSecret) -> - case handle_psk_identity(PSKIdentity, PSKLookup) of - {ok, PSK} when is_binary(PSK) -> - Len = erlang:byte_size(PremasterSecret), - PSKLen = erlang:byte_size(PSK), - <<?UINT16(Len), PremasterSecret/binary, ?UINT16(PSKLen), PSK/binary>>; - #alert{} = Alert -> - Alert; - _ -> - throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)) +handle_renegotiation_info(_RecordCB, _, #renegotiation_info{renegotiated_connection = ?byte(0)}, + ConnectionStates, false, _, _) -> + {ok, ssl_record:set_renegotiation_flag(true, ConnectionStates)}; + +handle_renegotiation_info(_RecordCB, server, undefined, ConnectionStates, _, _, CipherSuites) -> + case is_member(?TLS_EMPTY_RENEGOTIATION_INFO_SCSV, CipherSuites) of + true -> + {ok, ssl_record:set_renegotiation_flag(true, ConnectionStates)}; + false -> + {ok, ssl_record:set_renegotiation_flag(false, ConnectionStates)} + end; + +handle_renegotiation_info(_RecordCB, _, undefined, ConnectionStates, false, _, _) -> + {ok, ssl_record:set_renegotiation_flag(false, ConnectionStates)}; + +handle_renegotiation_info(_RecordCB, client, #renegotiation_info{renegotiated_connection = ClientServerVerify}, + ConnectionStates, true, _, _) -> + ConnectionState = ssl_record:current_connection_state(ConnectionStates, read), + CData = maps:get(client_verify_data, ConnectionState), + SData = maps:get(server_verify_data, ConnectionState), + case <<CData/binary, SData/binary>> == ClientServerVerify of + true -> + {ok, ConnectionStates}; + false -> + ?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, {server_renegotiation, empty_renegotiation_info_scsv}); + false -> + ConnectionState = ssl_record:current_connection_state(ConnectionStates, read), + Data = maps:get(client_verify_data, ConnectionState), + case Data == ClientVerify of + true -> + {ok, ConnectionStates}; + false -> + ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, server_renegotiation) + end + end; + +handle_renegotiation_info(RecordCB, client, undefined, ConnectionStates, true, SecureRenegotation, _) -> + handle_renegotiation_info(RecordCB, ConnectionStates, SecureRenegotation); + +handle_renegotiation_info(RecordCB, server, undefined, ConnectionStates, true, SecureRenegotation, CipherSuites) -> + case is_member(?TLS_EMPTY_RENEGOTIATION_INFO_SCSV, CipherSuites) of + true -> + ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, {server_renegotiation, empty_renegotiation_info_scsv}); + false -> + handle_renegotiation_info(RecordCB, ConnectionStates, SecureRenegotation) + end. + +handle_renegotiation_info(_RecordCB, ConnectionStates, SecureRenegotation) -> + ConnectionState = ssl_record:current_connection_state(ConnectionStates, read), + case {SecureRenegotation, maps:get(secure_renegotiation, ConnectionState)} of + {_, true} -> + ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, already_secure); + {true, false} -> + ?ALERT_REC(?FATAL, ?NO_RENEGOTIATION); + {false, false} -> + {ok, ConnectionStates} end. -handle_psk_identity(_PSKIdentity, LookupFun) - when LookupFun == undefined -> - error; -handle_psk_identity(PSKIdentity, {Fun, UserState}) -> - Fun(psk, PSKIdentity, UserState). +cert_curve(_, _, no_suite) -> + {no_curve, no_suite}; +cert_curve(Cert, ECCCurve0, CipherSuite) -> + case ssl_cipher_format:suite_definition(CipherSuite) of + #{key_exchange := Kex} when Kex == ecdh_ecdsa; + Kex == ecdh_rsa -> + OtpCert = public_key:pkix_decode_cert(Cert, otp), + TBSCert = OtpCert#'OTPCertificate'.tbsCertificate, + #'OTPSubjectPublicKeyInfo'{algorithm = AlgInfo} + = TBSCert#'OTPTBSCertificate'.subjectPublicKeyInfo, + {namedCurve, Oid} = AlgInfo#'PublicKeyAlgorithm'.parameters, + {{namedCurve, Oid}, CipherSuite}; + _ -> + {ECCCurve0, CipherSuite} + end. + + diff --git a/lib/ssl/src/ssl_handshake.hrl b/lib/ssl/src/ssl_handshake.hrl index 80284faef0..a191fcf766 100644 --- a/lib/ssl/src/ssl_handshake.hrl +++ b/lib/ssl/src/ssl_handshake.hrl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2014. All Rights Reserved. +%% Copyright Ericsson AB 2007-2016. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. +%% 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 %% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% 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% %% @@ -52,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 @@ -78,6 +80,9 @@ -define(CLIENT_KEY_EXCHANGE, 16). -define(FINISHED, 20). +-define(MAX_UNIT24, 8388607). +-define(DEFAULT_MAX_HANDSHAKE_SIZE, (256*1024)). + -record(random, { gmt_unix_time, % uint32 random_bytes % opaque random_bytes[28] @@ -94,7 +99,8 @@ -record(hello_extensions, { renegotiation_info, - hash_signs, % supported combinations of hashes/signature algos + signature_algs, % supported combinations of hashes/signature algos + alpn, next_protocol_negotiation = undefined, % [binary()] srp, ec_point_formats, @@ -127,6 +133,7 @@ -define(KEY_EXCHANGE_DIFFIE_HELLMAN, 1). -define(KEY_EXCHANGE_EC_DIFFIE_HELLMAN, 6). -define(KEY_EXCHANGE_PSK, 2). +-define(KEY_EXCHANGE_EC_DIFFIE_HELLMAN_PSK, 7). -define(KEY_EXCHANGE_DHE_PSK, 3). -define(KEY_EXCHANGE_RSA_PSK, 4). -define(KEY_EXCHANGE_SRP, 5). @@ -156,6 +163,11 @@ dh_params }). +-record(server_ecdhe_psk_params, { + hint, + dh_params + }). + -record(server_srp_params, { srp_n, %% opaque srp_N<1..2^16-1> srp_g, %% opaque srp_g<1..2^16-1> @@ -248,6 +260,11 @@ dh_public }). +-record(client_ecdhe_psk_identity, { + identity, + dh_public + }). + -record(client_rsa_psk_identity, { identity, exchange_keys @@ -301,6 +318,14 @@ }). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Application-Layer Protocol Negotiation RFC 7301 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +-define(ALPN_EXT, 16). + +-record(alpn, {extension_data}). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Next Protocol Negotiation %% (http://tools.ietf.org/html/draft-agl-tls-nextprotoneg-02) %% (http://technotes.googlecode.com/git/nextprotoneg.html) diff --git a/lib/ssl/src/ssl_internal.hrl b/lib/ssl/src/ssl_internal.hrl index 88105cac5a..63e751440a 100644 --- a/lib/ssl/src/ssl_internal.hrl +++ b/lib/ssl/src/ssl_internal.hrl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2015. All Rights Reserved. +%% Copyright Ericsson AB 2007-2018. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. +%% 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 %% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% 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% %% @@ -59,18 +60,28 @@ -define(CDR_MAGIC, "GIOP"). -define(CDR_HDR_SIZE, 12). +-define(INTERNAL_ACTIVE_N, 100). -define(DEFAULT_TIMEOUT, 5000). +-define(NO_DIST_POINT, "http://dummy/no_distribution_point"). +-define(NO_DIST_POINT_PATH, "dummy/no_distribution_point"). %% Common enumerate values in for SSL-protocols -define(NULL, 0). -define(TRUE, 0). -define(FALSE, 1). --define(ALL_SUPPORTED_VERSIONS, ['tlsv1.2', 'tlsv1.1', tlsv1, sslv3]). --define(MIN_SUPPORTED_VERSIONS, ['tlsv1.1', tlsv1, sslv3]). +%% sslv3 is considered insecure due to lack of padding check (Poodle attack) +%% Keep as interop with legacy software but do not support as default +-define(ALL_AVAILABLE_VERSIONS, ['tlsv1.2', 'tlsv1.1', tlsv1, sslv3]). +-define(ALL_AVAILABLE_DATAGRAM_VERSIONS, ['dtlsv1.2', dtlsv1]). +-define(ALL_SUPPORTED_VERSIONS, ['tlsv1.2', 'tlsv1.1', tlsv1]). +-define(MIN_SUPPORTED_VERSIONS, ['tlsv1.1', tlsv1]). -define(ALL_DATAGRAM_SUPPORTED_VERSIONS, ['dtlsv1.2', dtlsv1]). --define(MIN_DATAGRAM_SUPPORTED_VERSIONS, ['dtlsv1.2', dtlsv1]). +-define(MIN_DATAGRAM_SUPPORTED_VERSIONS, [dtlsv1]). + +-define('24H_in_msec', 86400000). +-define('24H_in_sec', 86400). -record(ssl_options, { protocol :: tls | dtls, @@ -84,16 +95,17 @@ 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()} | key_map() | 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 @@ -105,22 +117,38 @@ reuse_sessions :: boolean(), renegotiate_at, secure_renegotiate, + client_renegotiation, %% 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(), - next_protocols_advertised = undefined, %% [binary()], + alpn_advertised_protocols = undefined :: [binary()] | undefined , + alpn_preferred_protocols = undefined :: [binary()] | undefined, + next_protocols_advertised = undefined :: [binary()] | undefined, next_protocol_selector = undefined, %% fun([binary()]) -> binary()) log_alert :: boolean(), server_name_indication = undefined, + sni_hosts :: [{inet:hostname(), [tuple()]}], + sni_fun :: function() | undefined, %% Should the server prefer its own cipher order over the one provided by %% the client? - honor_cipher_order = false, - padding_check = true, - fallback = false - }). + 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, + eccs, + honor_ecc_order :: boolean(), + max_handshake_size :: integer(), + handshake, + customize_hostname_check + }). -record(socket_options, { @@ -133,13 +161,22 @@ -record(config, {ssl, %% SSL parameters inet_user, %% User set inet options - emulated, %% Emulated option list or "inherit_tracker" pid + emulated, %% Emulated option list or "inherit_tracker" pid + dtls_handler, inet_ssl, %% inet options for internal ssl socket transport_info, %% Callback info connection_cb }). - +-type key_map() :: #{algorithm := rsa | dss | ecdsa, + %% engine and key_id ought to + %% be :=, but putting it in + %% the spec gives dialyzer warning + %% of correct code! + engine => crypto:engine_ref(), + key_id => crypto:key_id(), + password => crypto:password() + }. -type state_name() :: hello | abbreviated | certify | cipher | connection. -type gen_fsm_state_return() :: {next_state, state_name(), term()} | {next_state, state_name(), term(), timeout()} | diff --git a/lib/ssl/src/ssl_listen_tracker_sup.erl b/lib/ssl/src/ssl_listen_tracker_sup.erl index 29f40e846d..f7e97bcb76 100644 --- a/lib/ssl/src/ssl_listen_tracker_sup.erl +++ b/lib/ssl/src/ssl_listen_tracker_sup.erl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2014-2014. All Rights Reserved. +%% Copyright Ericsson AB 2014-2016. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% 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% %% @@ -56,10 +57,10 @@ init(_O) -> MaxT = 3600, Name = undefined, % As simple_one_for_one is used. - StartFunc = {ssl_socket, start_link, []}, + StartFunc = {tls_socket, start_link, []}, Restart = temporary, % E.g. should not be restarted Shutdown = 4000, - Modules = [ssl_socket], + Modules = [tls_socket], Type = worker, ChildSpec = {Name, StartFunc, Restart, Shutdown, Type, Modules}, diff --git a/lib/ssl/src/ssl_manager.erl b/lib/ssl/src/ssl_manager.erl index c4f1f7f193..4b735b2400 100644 --- a/lib/ssl/src/ssl_manager.erl +++ b/lib/ssl/src/ssl_manager.erl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2015. All Rights Reserved. +%% Copyright Ericsson AB 2007-2018. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. +%% 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 %% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% 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% %% @@ -26,14 +27,14 @@ %% Internal application API -export([start_link/1, start_link_dist/1, - connection_init/2, cache_pem_file/2, + connection_init/3, cache_pem_file/2, lookup_trusted_cert/4, new_session_id/1, clean_cert_db/2, register_session/2, register_session/3, invalidate_session/2, - invalidate_session/3, invalidate_pem/1, clear_pem_cache/0, manager_name/1]). + insert_crls/2, insert_crls/3, delete_crls/1, delete_crls/2, + invalidate_session/3, name/1]). -% Spawn export --export([init_session_validator/1, init_pem_cache_validator/1]). +-export([init_session_validator/1]). %% gen_server callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, @@ -44,39 +45,40 @@ -include_lib("kernel/include/file.hrl"). -record(state, { - session_cache, - session_cache_cb, - session_lifetime, - certificate_db, - session_validation_timer, - last_delay_timer = {undefined, undefined},%% Keep for testing purposes - last_pem_check, - clear_pem_cache + session_cache_client :: db_handle(), + session_cache_server :: db_handle(), + session_cache_cb :: atom(), + session_lifetime :: integer(), + certificate_db :: db_handle(), + session_validation_timer :: reference(), + last_delay_timer = {undefined, undefined},%% Keep for testing purposes + session_cache_client_max :: integer(), + session_cache_server_max :: integer(), + session_server_invalidator :: undefined | pid(), + session_client_invalidator :: undefined | pid() }). --define('24H_in_msec', 86400000). --define('24H_in_sec', 86400). -define(GEN_UNIQUE_ID_MAX_TRIES, 10). -define(SESSION_VALIDATION_INTERVAL, 60000). --define(CLEAR_PEM_CACHE, 120000). -define(CLEAN_SESSION_DB, 60000). -define(CLEAN_CERT_DB, 500). --define(NOT_TO_BIG, 10). +-define(DEFAULT_MAX_SESSION_CACHE, 1000). +-define(LOAD_MITIGATION, 10). %%==================================================================== %% API %%==================================================================== %%-------------------------------------------------------------------- --spec manager_name(normal | dist) -> atom(). +-spec name(normal | dist) -> atom(). %% %% Description: Returns the registered name of the ssl manager process %% in the operation modes 'normal' and 'dist'. %%-------------------------------------------------------------------- -manager_name(normal) -> +name(normal) -> ?MODULE; -manager_name(dist) -> - list_to_atom(atom_to_list(?MODULE) ++ "dist"). +name(dist) -> + list_to_atom(atom_to_list(?MODULE) ++ "_dist"). %%-------------------------------------------------------------------- -spec start_link(list()) -> {ok, pid()} | ignore | {error, term()}. @@ -85,8 +87,10 @@ manager_name(dist) -> %% and certificate caching. %%-------------------------------------------------------------------- start_link(Opts) -> - DistMangerName = manager_name(normal), - gen_server:start_link({local, DistMangerName}, ?MODULE, [DistMangerName, Opts], []). + MangerName = name(normal), + CacheName = ssl_pem_cache:name(normal), + gen_server:start_link({local, MangerName}, + ?MODULE, [MangerName, CacheName, Opts], []). %%-------------------------------------------------------------------- -spec start_link_dist(list()) -> {ok, pid()} | ignore | {error, term()}. @@ -95,23 +99,23 @@ start_link(Opts) -> %% be used by the erlang distribution. Note disables soft upgrade! %%-------------------------------------------------------------------- start_link_dist(Opts) -> - DistMangerName = manager_name(dist), - gen_server:start_link({local, DistMangerName}, ?MODULE, [DistMangerName, Opts], []). + DistMangerName = name(dist), + DistCacheName = ssl_pem_cache:name(dist), + gen_server:start_link({local, DistMangerName}, + ?MODULE, [DistMangerName, DistCacheName, Opts], []). %%-------------------------------------------------------------------- --spec connection_init(binary()| {der, list()}, client | server) -> - {ok, certdb_ref(), db_handle(), db_handle(), db_handle(), db_handle()}. +-spec connection_init(binary()| {der, list()}, client | server, + {Cb :: atom(), Handle:: term()}) -> + {ok, map()}. %% %% Description: Do necessary initializations for a new connection. %%-------------------------------------------------------------------- -connection_init({der, _} = Trustedcerts, Role) -> - call({connection_init, Trustedcerts, Role}); - -connection_init(<<>> = Trustedcerts, Role) -> - call({connection_init, Trustedcerts, Role}); - -connection_init(Trustedcerts, Role) -> - call({connection_init, Trustedcerts, Role}). +connection_init({der, _} = Trustedcerts, Role, CRLCache) -> + {ok, Extracted} = ssl_pkix_db:extract_trusted_certs(Trustedcerts), + call({connection_init, Extracted, Role, CRLCache}); +connection_init(Trustedcerts, Role, CRLCache) -> + call({connection_init, Trustedcerts, Role, CRLCache}). %%-------------------------------------------------------------------- -spec cache_pem_file(binary(), term()) -> {ok, term()} | {error, reason()}. @@ -119,26 +123,20 @@ connection_init(Trustedcerts, Role) -> %% Description: Cache a pem file and return its content. %%-------------------------------------------------------------------- cache_pem_file(File, DbHandle) -> - case ssl_pkix_db:lookup_cached_pem(DbHandle, File) of - [{Content,_}] -> + case ssl_pkix_db:lookup(File, DbHandle) of + [Content] -> {ok, Content}; - [Content] -> - {ok, Content}; undefined -> - call({cache_pem, File}) + case ssl_pkix_db:decode_pem_file(File) of + {ok, Content} -> + ssl_pem_cache:insert(File, Content), + {ok, Content}; + Error -> + Error + end end. %%-------------------------------------------------------------------- --spec clear_pem_cache() -> ok. -%% -%% Description: Clear the PEM cache -%%-------------------------------------------------------------------- -clear_pem_cache() -> - %% Not supported for distribution at the moement, should it be? - put(ssl_manager, manager_name(normal)), - call(unconditionally_clear_pem_cache). - -%%-------------------------------------------------------------------- -spec lookup_trusted_cert(term(), reference(), serialnumber(), issuer()) -> undefined | {ok, {der_cert(), #'OTPCertificate'{}}}. @@ -164,7 +162,8 @@ new_session_id(Port) -> %% be called by ssl-connection processes. %%-------------------------------------------------------------------- clean_cert_db(Ref, File) -> - erlang:send_after(?CLEAN_CERT_DB, get(ssl_manager), {clean_cert_db, Ref, File}), + erlang:send_after(?CLEAN_CERT_DB, get(ssl_manager), + {clean_cert_db, Ref, File}), ok. %%-------------------------------------------------------------------- @@ -186,16 +185,31 @@ register_session(Port, Session) -> %%-------------------------------------------------------------------- -spec invalidate_session(host(), inet:port_number(), #session{}) -> ok. invalidate_session(Host, Port, Session) -> + load_mitigation(), cast({invalidate_session, Host, Port, Session}). -spec invalidate_session(inet:port_number(), #session{}) -> ok. invalidate_session(Port, Session) -> + load_mitigation(), cast({invalidate_session, Port, Session}). - --spec invalidate_pem(File::binary()) -> ok. -invalidate_pem(File) -> - cast({invalidate_pem, File}). +insert_crls(Path, CRLs)-> + insert_crls(Path, CRLs, normal). +insert_crls(?NO_DIST_POINT_PATH = Path, CRLs, ManagerType)-> + put(ssl_manager, name(ManagerType)), + cast({insert_crls, Path, CRLs}); +insert_crls(Path, CRLs, ManagerType)-> + put(ssl_manager, name(ManagerType)), + call({insert_crls, Path, CRLs}). + +delete_crls(Path)-> + delete_crls(Path, normal). +delete_crls(?NO_DIST_POINT_PATH = Path, ManagerType)-> + put(ssl_manager, name(ManagerType)), + cast({delete_crls, Path}); +delete_crls(Path, ManagerType)-> + put(ssl_manager, name(ManagerType)), + call({delete_crls, Path}). %%==================================================================== %% gen_server callbacks @@ -208,25 +222,34 @@ invalidate_pem(File) -> %% %% Description: Initiates the server %%-------------------------------------------------------------------- -init([Name, Opts]) -> - put(ssl_manager, Name), +init([ManagerName, PemCacheName, Opts]) -> + put(ssl_manager, ManagerName), + put(ssl_pem_cache, PemCacheName), process_flag(trap_exit, true), CacheCb = proplists:get_value(session_cb, Opts, ssl_session_cache), SessionLifeTime = proplists:get_value(session_lifetime, Opts, ?'24H_in_sec'), - CertDb = ssl_pkix_db:create(), - SessionCache = CacheCb:init(proplists:get_value(session_cb_init_args, Opts, [])), + CertDb = ssl_pkix_db:create(PemCacheName), + ClientSessionCache = + CacheCb:init([{role, client} | + proplists:get_value(session_cb_init_args, Opts, [])]), + ServerSessionCache = + CacheCb:init([{role, server} | + proplists:get_value(session_cb_init_args, Opts, [])]), Timer = erlang:send_after(SessionLifeTime * 1000 + 5000, self(), validate_sessions), - Interval = pem_check_interval(), - erlang:send_after(Interval, self(), clear_pem_cache), {ok, #state{certificate_db = CertDb, - session_cache = SessionCache, + session_cache_client = ClientSessionCache, + session_cache_server = ServerSessionCache, session_cache_cb = CacheCb, session_lifetime = SessionLifeTime, session_validation_timer = Timer, - last_pem_check = os:timestamp(), - clear_pem_cache = Interval + session_cache_client_max = + max_session_cache_size(session_cache_client_max), + session_cache_server_max = + max_session_cache_size(session_cache_server_max), + session_client_invalidator = undefined, + session_server_invalidator = undefined }}. %%-------------------------------------------------------------------- @@ -240,44 +263,45 @@ init([Name, Opts]) -> %% %% Description: Handling call messages %%-------------------------------------------------------------------- -handle_call({{connection_init, <<>>, _Role}, _Pid}, _From, - #state{certificate_db = [CertDb, FileRefDb, PemChace], - session_cache = Cache} = State) -> - Result = {ok, make_ref(),CertDb, FileRefDb, PemChace, Cache}, - {reply, Result, State}; - -handle_call({{connection_init, Trustedcerts, _Role}, Pid}, _From, - #state{certificate_db = [CertDb, FileRefDb, PemChace] = Db, - session_cache = Cache} = State) -> - Result = - try - {ok, Ref} = ssl_pkix_db:add_trusted_certs(Pid, Trustedcerts, Db), - {ok, Ref, CertDb, FileRefDb, PemChace, Cache} - catch - _:Reason -> - {error, Reason} - end, - {reply, Result, State}; - -handle_call({{new_session_id,Port}, _}, - _, #state{session_cache_cb = CacheCb, - session_cache = Cache} = State) -> - Id = new_id(Port, ?GEN_UNIQUE_ID_MAX_TRIES, Cache, CacheCb), - {reply, Id, State}; +handle_call({{connection_init, <<>>, Role, {CRLCb, UserCRLDb}}, _Pid}, _From, + #state{certificate_db = [CertDb, FileRefDb, PemChace | _] = Db} = State) -> + Ref = make_ref(), + {reply, {ok, #{cert_db_ref => Ref, + cert_db_handle => CertDb, + fileref_db_handle => FileRefDb, + pem_cache => PemChace, + session_cache => session_cache(Role, State), + crl_db_info => {CRLCb, crl_db_info(Db, UserCRLDb)}}}, State}; + +handle_call({{connection_init, Trustedcerts, Role, {CRLCb, UserCRLDb}}, Pid}, _From, + #state{certificate_db = [CertDb, FileRefDb, PemChace | _] = Db} = State) -> + case add_trusted_certs(Pid, Trustedcerts, Db) of + {ok, Ref} -> + {reply, {ok, #{cert_db_ref => Ref, + cert_db_handle => CertDb, + fileref_db_handle => FileRefDb, + pem_cache => PemChace, + session_cache => session_cache(Role, State), + crl_db_info => {CRLCb, crl_db_info(Db, UserCRLDb)}}}, State}; + {error, _} = Error -> + {reply, Error, State} + end; +handle_call({{insert_crls, Path, CRLs}, _}, _From, + #state{certificate_db = Db} = State) -> + ssl_pkix_db:add_crls(Db, Path, CRLs), + {reply, ok, State}; -handle_call({{cache_pem,File}, _Pid}, _, +handle_call({{delete_crls, CRLsOrPath}, _}, _From, #state{certificate_db = Db} = State) -> - try ssl_pkix_db:cache_pem_file(File, Db) of - Result -> - {reply, Result, State} - catch - _:Reason -> - {reply, {error, Reason}, State} - end; -handle_call({unconditionally_clear_pem_cache, _},_, #state{certificate_db = [_,_,PemChace]} = State) -> - ssl_pkix_db:clear(PemChace), - {reply, ok, State}. + ssl_pkix_db:remove_crls(Db, CRLsOrPath), + {reply, ok, State}; + +handle_call({{new_session_id, Port}, _}, + _, #state{session_cache_cb = CacheCb, + session_cache_server = Cache} = State) -> + Id = new_id(Port, ?GEN_UNIQUE_ID_MAX_TRIES, Cache, CacheCb), + {reply, Id, State}. %%-------------------------------------------------------------------- -spec handle_cast(msg(), #state{}) -> {noreply, #state{}}. @@ -287,37 +311,34 @@ handle_call({unconditionally_clear_pem_cache, _},_, #state{certificate_db = [_,_ %% %% Description: Handling cast messages %%-------------------------------------------------------------------- -handle_cast({register_session, Host, Port, Session}, - #state{session_cache = Cache, - session_cache_cb = CacheCb} = State) -> - TimeStamp = calendar:datetime_to_gregorian_seconds({date(), time()}), - NewSession = Session#session{time_stamp = TimeStamp}, - CacheCb:update(Cache, {{Host, Port}, - NewSession#session.session_id}, NewSession), +handle_cast({register_session, Host, Port, Session}, State0) -> + State = ssl_client_register_session(Host, Port, Session, State0), {noreply, State}; -handle_cast({register_session, Port, Session}, - #state{session_cache = Cache, - session_cache_cb = CacheCb} = State) -> - TimeStamp = calendar:datetime_to_gregorian_seconds({date(), time()}), - NewSession = Session#session{time_stamp = TimeStamp}, - CacheCb:update(Cache, {Port, NewSession#session.session_id}, NewSession), +handle_cast({register_session, Port, Session}, State0) -> + State = server_register_session(Port, Session, State0), {noreply, State}; handle_cast({invalidate_session, Host, Port, #session{session_id = ID} = Session}, - #state{session_cache = Cache, + #state{session_cache_client = Cache, session_cache_cb = CacheCb} = State) -> invalidate_session(Cache, CacheCb, {{Host, Port}, ID}, Session, State); handle_cast({invalidate_session, Port, #session{session_id = ID} = Session}, - #state{session_cache = Cache, + #state{session_cache_server = Cache, session_cache_cb = CacheCb} = State) -> invalidate_session(Cache, CacheCb, {Port, ID}, Session, State); -handle_cast({invalidate_pem, File}, - #state{certificate_db = [_, _, PemCache]} = State) -> - ssl_pkix_db:remove(File, PemCache), + +handle_cast({insert_crls, Path, CRLs}, + #state{certificate_db = Db} = State) -> + ssl_pkix_db:add_crls(Db, Path, CRLs), + {noreply, State}; + +handle_cast({delete_crls, CRLsOrPath}, + #state{certificate_db = Db} = State) -> + ssl_pkix_db:remove_crls(Db, CRLsOrPath), {noreply, State}. %%-------------------------------------------------------------------- @@ -329,44 +350,41 @@ handle_cast({invalidate_pem, File}, %% Description: Handling all non call/cast messages %%------------------------------------------------------------------- handle_info(validate_sessions, #state{session_cache_cb = CacheCb, - session_cache = Cache, - session_lifetime = LifeTime + session_cache_client = ClientCache, + session_cache_server = ServerCache, + session_lifetime = LifeTime, + session_client_invalidator = Client, + session_server_invalidator = Server } = State) -> Timer = erlang:send_after(?SESSION_VALIDATION_INTERVAL, self(), validate_sessions), - start_session_validator(Cache, CacheCb, LifeTime), - {noreply, State#state{session_validation_timer = Timer}}; + CPid = start_session_validator(ClientCache, CacheCb, LifeTime, Client), + SPid = start_session_validator(ServerCache, CacheCb, LifeTime, Server), + {noreply, State#state{session_validation_timer = Timer, + session_client_invalidator = CPid, + session_server_invalidator = SPid}}; + -handle_info({delayed_clean_session, Key}, #state{session_cache = Cache, - session_cache_cb = CacheCb - } = State) -> +handle_info({delayed_clean_session, Key, Cache}, #state{session_cache_cb = CacheCb + } = State) -> CacheCb:delete(Cache, Key), {noreply, State}; -handle_info(clear_pem_cache, #state{certificate_db = [_,_,PemChace], - clear_pem_cache = Interval, - last_pem_check = CheckPoint} = State) -> - NewCheckPoint = os:timestamp(), - start_pem_cache_validator(PemChace, CheckPoint), - erlang:send_after(Interval, self(), clear_pem_cache), - {noreply, State#state{last_pem_check = NewCheckPoint}}; - - handle_info({clean_cert_db, Ref, File}, - #state{certificate_db = [CertDb,RefDb, PemCache]} = State) -> + #state{certificate_db = [CertDb, {RefDb, FileMapDb} | _]} = State) -> case ssl_pkix_db:lookup(Ref, RefDb) of undefined -> %% Alredy cleaned ok; _ -> - clean_cert_db(Ref, CertDb, RefDb, PemCache, File) + clean_cert_db(Ref, CertDb, RefDb, FileMapDb, File) end, {noreply, State}; -handle_info({'EXIT', _, _}, State) -> - %% Session validator died!! Do we need to take any action? - %% maybe error log - {noreply, State}; +handle_info({'EXIT', Pid, _}, #state{session_client_invalidator = Pid} = State) -> + {noreply, State#state{session_client_invalidator = undefined}}; +handle_info({'EXIT', Pid, _}, #state{session_server_invalidator = Pid} = State) -> + {noreply, State#state{session_server_invalidator = undefined}}; handle_info(_Info, State) -> {noreply, State}. @@ -380,12 +398,14 @@ handle_info(_Info, State) -> %% The return value is ignored. %%-------------------------------------------------------------------- terminate(_Reason, #state{certificate_db = Db, - session_cache = SessionCache, + session_cache_client = ClientSessionCache, + session_cache_server = ServerSessionCache, session_cache_cb = CacheCb, session_validation_timer = Timer}) -> erlang:cancel_timer(Timer), ssl_pkix_db:remove(Db), - CacheCb:terminate(SessionCache), + catch CacheCb:terminate(ClientSessionCache), + catch CacheCb:terminate(ServerSessionCache), ok. %%-------------------------------------------------------------------- @@ -421,9 +441,11 @@ validate_session(Port, Session, LifeTime) -> invalidate_session(Port, Session) end. -start_session_validator(Cache, CacheCb, LifeTime) -> +start_session_validator(Cache, CacheCb, LifeTime, undefined) -> spawn_link(?MODULE, init_session_validator, - [[get(ssl_manager), Cache, CacheCb, LifeTime]]). + [[get(ssl_manager), Cache, CacheCb, LifeTime]]); +start_session_validator(_,_,_, Pid) -> + Pid. init_session_validator([SslManagerName, Cache, CacheCb, LifeTime]) -> put(ssl_manager, SslManagerName), @@ -445,7 +467,15 @@ delay_time() -> ?CLEAN_SESSION_DB end. -invalidate_session(Cache, CacheCb, Key, Session, #state{last_delay_timer = LastTimer} = State) -> +max_session_cache_size(CacheType) -> + case application:get_env(ssl, CacheType) of + {ok, Size} when is_integer(Size) -> + Size; + _ -> + ?DEFAULT_MAX_SESSION_CACHE + end. + +invalidate_session(Cache, CacheCb, Key, Session, State) -> case CacheCb:lookup(Cache, Key) of undefined -> %% Session is already invalidated {noreply, State}; @@ -453,15 +483,23 @@ invalidate_session(Cache, CacheCb, Key, Session, #state{last_delay_timer = LastT CacheCb:delete(Cache, Key), {noreply, State}; _ -> - %% When a registered session is invalidated we need to wait a while before deleting - %% it as there might be pending connections that rightfully needs to look - %% up the session data but new connections should not get to use this session. - CacheCb:update(Cache, Key, Session#session{is_resumable = false}), - TRef = - erlang:send_after(delay_time(), self(), {delayed_clean_session, Key}), - {noreply, State#state{last_delay_timer = last_delay_timer(Key, TRef, LastTimer)}} + delayed_invalidate_session(CacheCb, Cache, Key, Session, State) end. +delayed_invalidate_session(CacheCb, Cache, Key, Session, + #state{last_delay_timer = LastTimer} = State) -> + %% When a registered session is invalidated we need to + %% wait a while before deleting it as there might be + %% pending connections that rightfully needs to look up + %% the session data but new connections should not get to + %% use this session. + CacheCb:update(Cache, Key, Session#session{is_resumable = false}), + TRef = + erlang:send_after(delay_time(), self(), + {delayed_clean_session, Key, Cache}), + {noreply, State#state{last_delay_timer = + last_delay_timer(Key, TRef, LastTimer)}}. + last_delay_timer({{_,_},_}, TRef, {LastServer, _}) -> {LastServer, TRef}; last_delay_timer({_,_}, TRef, {_, LastClient}) -> @@ -477,68 +515,138 @@ 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 = calendar:datetime_to_gregorian_seconds({date(), time()}), + Now = erlang:monotonic_time(), %% New sessions can not be set to resumable %% until handshake is compleate and the %% other session values are set. CacheCb:update(Cache, {Port, Id}, #session{session_id = Id, - is_resumable = false, + is_resumable = new, time_stamp = Now}), Id; _ -> new_id(Port, Tries - 1, Cache, CacheCb) end. -clean_cert_db(Ref, CertDb, RefDb, PemCache, File) -> +clean_cert_db(Ref, CertDb, RefDb, FileMapDb, File) -> case ssl_pkix_db:ref_count(Ref, RefDb, 0) of 0 -> - case ssl_pkix_db:lookup_cached_pem(PemCache, File) of - [{Content, Ref}] -> - ssl_pkix_db:insert(File, Content, PemCache); - _ -> - ok - end, ssl_pkix_db:remove(Ref, RefDb), + ssl_pkix_db:remove(File, FileMapDb), ssl_pkix_db:remove_trusted_certs(Ref, CertDb); _ -> ok end. -start_pem_cache_validator(PemCache, CheckPoint) -> - spawn_link(?MODULE, init_pem_cache_validator, - [[get(ssl_manager), PemCache, CheckPoint]]). +ssl_client_register_session(Host, Port, Session, #state{session_cache_client = Cache, + session_cache_cb = CacheCb, + session_cache_client_max = Max, + session_client_invalidator = Pid0} = State) -> + TimeStamp = erlang:monotonic_time(), + NewSession = Session#session{time_stamp = TimeStamp}, + + case CacheCb:select_session(Cache, {Host, Port}) of + no_session -> + Pid = do_register_session({{Host, Port}, + NewSession#session.session_id}, + NewSession, Max, Pid0, Cache, CacheCb), + State#state{session_client_invalidator = Pid}; + Sessions -> + register_unique_session(Sessions, NewSession, {Host, Port}, State) + end. -init_pem_cache_validator([SslManagerName, PemCache, CheckPoint]) -> - put(ssl_manager, SslManagerName), - ssl_pkix_db:foldl(fun pem_cache_validate/2, - CheckPoint, PemCache). - -pem_cache_validate({File, _}, CheckPoint) -> - case file:read_file_info(File, []) of - {ok, #file_info{mtime = Time}} -> - case is_before_checkpoint(Time, CheckPoint) of - true -> - ok; - false -> - invalidate_pem(File) - end; - _ -> - invalidate_pem(File) - end, - CheckPoint. - -pem_check_interval() -> - case application:get_env(ssl, ssl_pem_cache_clean) of - {ok, Interval} when is_integer(Interval) -> - Interval; - _ -> - ?CLEAR_PEM_CACHE +server_register_session(Port, Session, #state{session_cache_server_max = Max, + session_cache_server = Cache, + session_cache_cb = CacheCb, + session_server_invalidator = Pid0} = State) -> + TimeStamp = erlang:monotonic_time(), + NewSession = Session#session{time_stamp = TimeStamp}, + Pid = do_register_session({Port, NewSession#session.session_id}, + NewSession, Max, Pid0, Cache, CacheCb), + State#state{session_server_invalidator = Pid}. + +do_register_session(Key, Session, Max, Pid, Cache, CacheCb) -> + try CacheCb:size(Cache) of + Size when Size >= Max -> + invalidate_session_cache(Pid, CacheCb, Cache); + _ -> + CacheCb:update(Cache, Key, Session), + Pid + catch + error:undef -> + CacheCb:update(Cache, Key, Session), + Pid + end. + + +%% Do not let dumb clients create a gigantic session table +%% for itself creating big delays at connection time. +register_unique_session(Sessions, Session, PartialKey, + #state{session_cache_client_max = Max, + session_cache_client = Cache, + session_cache_cb = CacheCb, + session_client_invalidator = Pid0} = State) -> + case exists_equivalent(Session , Sessions) of + true -> + State; + false -> + Pid = do_register_session({PartialKey, + Session#session.session_id}, + Session, Max, Pid0, Cache, CacheCb), + State#state{session_client_invalidator = Pid} + end. + +exists_equivalent(_, []) -> + false; +exists_equivalent(#session{ + peer_certificate = PeerCert, + own_certificate = OwnCert, + compression_method = Compress, + cipher_suite = CipherSuite, + srp_username = SRP, + ecc = ECC} , + [#session{ + peer_certificate = PeerCert, + own_certificate = OwnCert, + compression_method = Compress, + cipher_suite = CipherSuite, + srp_username = SRP, + ecc = ECC} | _]) -> + true; +exists_equivalent(Session, [ _ | Rest]) -> + exists_equivalent(Session, Rest). + +add_trusted_certs(Pid, Trustedcerts, Db) -> + try + ssl_pkix_db:add_trusted_certs(Pid, Trustedcerts, Db) + catch + _:Reason -> + {error, Reason} end. - -is_before_checkpoint(Time, CheckPoint) -> - calendar:datetime_to_gregorian_seconds(calendar:now_to_datetime(CheckPoint)) - - calendar:datetime_to_gregorian_seconds(Time) > 0. +session_cache(client, #state{session_cache_client = Cache}) -> + Cache; +session_cache(server, #state{session_cache_server = Cache}) -> + Cache. + +crl_db_info([_,_,_,Local], {internal, Info}) -> + {Local, Info}; +crl_db_info(_, UserCRLDb) -> + UserCRLDb. + +%% Only start a session invalidator if there is not +%% one already active +invalidate_session_cache(undefined, CacheCb, Cache) -> + start_session_validator(Cache, CacheCb, {invalidate_before, erlang:monotonic_time()}, undefined); +invalidate_session_cache(Pid, _CacheCb, _Cache) -> + Pid. + +load_mitigation() -> + MSec = rand:uniform(?LOAD_MITIGATION), + receive + after + MSec -> + continue + end. diff --git a/lib/ssl/src/ssl_pem_cache.erl b/lib/ssl/src/ssl_pem_cache.erl new file mode 100644 index 0000000000..41bca2f7b5 --- /dev/null +++ b/lib/ssl/src/ssl_pem_cache.erl @@ -0,0 +1,255 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 20016-2018. 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% +%% + +%%---------------------------------------------------------------------- +%% Purpose: Manages ssl sessions and trusted certifacates +%%---------------------------------------------------------------------- + +-module(ssl_pem_cache). +-behaviour(gen_server). + +%% Internal application API +-export([start_link/1, + start_link_dist/1, + name/1, + insert/2, + clear/0]). + +% Spawn export +-export([init_pem_cache_validator/1]). + +%% gen_server callbacks +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, + terminate/2, code_change/3]). + +-include("ssl_handshake.hrl"). +-include("ssl_internal.hrl"). +-include_lib("kernel/include/file.hrl"). + +-record(state, { + pem_cache, + last_pem_check :: integer(), + clear :: integer() + }). + +-define(CLEAR_PEM_CACHE, 120000). +-define(DEFAULT_MAX_SESSION_CACHE, 1000). + +%%==================================================================== +%% API +%%==================================================================== + +%%-------------------------------------------------------------------- +-spec name(normal | dist) -> atom(). +%% +%% Description: Returns the registered name of the ssl cache process +%% in the operation modes 'normal' and 'dist'. +%%-------------------------------------------------------------------- +name(normal) -> + ?MODULE; +name(dist) -> + list_to_atom(atom_to_list(?MODULE) ++ "_dist"). + +%%-------------------------------------------------------------------- +-spec start_link(list()) -> {ok, pid()} | ignore | {error, term()}. +%% +%% Description: Starts the ssl pem cache handler +%%-------------------------------------------------------------------- +start_link(_) -> + CacheName = name(normal), + gen_server:start_link({local, CacheName}, + ?MODULE, [CacheName], []). + +%%-------------------------------------------------------------------- +-spec start_link_dist(list()) -> {ok, pid()} | ignore | {error, term()}. +%% +%% Description: Starts a special instance of the ssl manager to +%% be used by the erlang distribution. Note disables soft upgrade! +%%-------------------------------------------------------------------- +start_link_dist(_) -> + DistCacheName = name(dist), + gen_server:start_link({local, DistCacheName}, + ?MODULE, [DistCacheName], []). + + +%%-------------------------------------------------------------------- +-spec insert(binary(), term()) -> ok | {error, reason()}. +%% +%% Description: Cache a pem file and return its content. +%%-------------------------------------------------------------------- +insert(File, Content) -> + case bypass_cache() of + true -> + ok; + false -> + cast({cache_pem, File, Content}), + ok + end. + +%%-------------------------------------------------------------------- +-spec clear() -> ok. +%% +%% Description: Clear the PEM cache +%%-------------------------------------------------------------------- +clear() -> + %% Not supported for distribution at the moement, should it be? + put(ssl_pem_cache, name(normal)), + call(unconditionally_clear_pem_cache). + +-spec invalidate_pem(File::binary()) -> ok. +invalidate_pem(File) -> + cast({invalidate_pem, File}). + +%%==================================================================== +%% gen_server callbacks +%%==================================================================== + +%%-------------------------------------------------------------------- +-spec init(list()) -> {ok, #state{}}. +%% Possible return values not used now. +%% | {ok, #state{}, timeout()} | ignore | {stop, term()}. +%% +%% Description: Initiates the server +%%-------------------------------------------------------------------- +init([Name]) -> + put(ssl_pem_cache, Name), + process_flag(trap_exit, true), + PemCache = ssl_pkix_db:create_pem_cache(Name), + Interval = pem_check_interval(), + erlang:send_after(Interval, self(), clear_pem_cache), + erlang:system_time(second), + {ok, #state{pem_cache = PemCache, + last_pem_check = erlang:convert_time_unit(os:system_time(), native, second), + clear = Interval + }}. + +%%-------------------------------------------------------------------- +-spec handle_call(msg(), from(), #state{}) -> {reply, reply(), #state{}}. +%% Possible return values not used now. +%% {reply, reply(), #state{}, timeout()} | +%% {noreply, #state{}} | +%% {noreply, #state{}, timeout()} | +%% {stop, reason(), reply(), #state{}} | +%% {stop, reason(), #state{}}. +%% +%% Description: Handling call messages +%%-------------------------------------------------------------------- +handle_call({unconditionally_clear_pem_cache, _},_, + #state{pem_cache = PemCache} = State) -> + ssl_pkix_db:clear(PemCache), + {reply, ok, State}. + +%%-------------------------------------------------------------------- +-spec handle_cast(msg(), #state{}) -> {noreply, #state{}}. +%% Possible return values not used now. +%% | {noreply, #state{}, timeout()} | +%% {stop, reason(), #state{}}. +%% +%% Description: Handling cast messages +%%-------------------------------------------------------------------- +handle_cast({cache_pem, File, Content}, #state{pem_cache = Db} = State) -> + ssl_pkix_db:insert(File, Content, Db), + {noreply, State}; + +handle_cast({invalidate_pem, File}, #state{pem_cache = Db} = State) -> + ssl_pkix_db:remove(File, Db), + {noreply, State}. + + +%%-------------------------------------------------------------------- +-spec handle_info(msg(), #state{}) -> {noreply, #state{}}. +%% Possible return values not used now. +%% |{noreply, #state{}, timeout()} | +%% {stop, reason(), #state{}}. +%% +%% Description: Handling all non call/cast messages +%%------------------------------------------------------------------- +handle_info(clear_pem_cache, #state{pem_cache = PemCache, + clear = Interval, + last_pem_check = CheckPoint} = State) -> + NewCheckPoint = erlang:convert_time_unit(os:system_time(), native, second), + start_pem_cache_validator(PemCache, CheckPoint), + erlang:send_after(Interval, self(), clear_pem_cache), + {noreply, State#state{last_pem_check = NewCheckPoint}}; + +handle_info(_Info, State) -> + {noreply, State}. + +%%-------------------------------------------------------------------- +-spec terminate(reason(), #state{}) -> ok. +%% +%% Description: This function is called by a gen_server 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_server terminates with Reason. +%% The return value is ignored. +%%-------------------------------------------------------------------- +terminate(_Reason, #state{}) -> + ok. + +%%-------------------------------------------------------------------- +-spec code_change(term(), #state{}, list()) -> {ok, #state{}}. +%% +%% Description: Convert process state when code is changed +%%-------------------------------------------------------------------- +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +%%-------------------------------------------------------------------- +%%% Internal functions +%%-------------------------------------------------------------------- +call(Msg) -> + gen_server:call(get(ssl_pem_cache), {Msg, self()}, infinity). + +cast(Msg) -> + gen_server:cast(get(ssl_pem_cache), Msg). + +start_pem_cache_validator(PemCache, CheckPoint) -> + spawn_link(?MODULE, init_pem_cache_validator, + [[get(ssl_pem_cache), PemCache, CheckPoint]]). + +init_pem_cache_validator([CacheName, PemCache, CheckPoint]) -> + put(ssl_pem_cache, CacheName), + ssl_pkix_db:foldl(fun pem_cache_validate/2, + CheckPoint, PemCache). + +pem_cache_validate({File, _}, CheckPoint) -> + case file:read_file_info(File, [{time, posix}]) of + {ok, #file_info{mtime = Time}} when Time < CheckPoint -> + ok; + _ -> + invalidate_pem(File) + end, + CheckPoint. + +pem_check_interval() -> + case application:get_env(ssl, ssl_pem_cache_clean) of + {ok, Interval} when is_integer(Interval) -> + Interval; + _ -> + ?CLEAR_PEM_CACHE + end. + +bypass_cache() -> + case application:get_env(ssl, bypass_pem_cache) of + {ok, Bool} when is_boolean(Bool) -> + Bool; + _ -> + false + end. diff --git a/lib/ssl/src/ssl_pkix_db.erl b/lib/ssl/src/ssl_pkix_db.erl index 8531445ba4..f7ddbd060e 100644 --- a/lib/ssl/src/ssl_pkix_db.erl +++ b/lib/ssl/src/ssl_pkix_db.erl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2015. All Rights Reserved. +%% Copyright Ericsson AB 2007-2018. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. +%% 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 %% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% 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% %% @@ -27,40 +28,66 @@ -include_lib("public_key/include/public_key.hrl"). -include_lib("kernel/include/file.hrl"). --export([create/0, remove/1, add_trusted_certs/3, +-export([create/1, create_pem_cache/1, + add_crls/3, remove_crls/2, remove/1, add_trusted_certs/3, + extract_trusted_certs/1, remove_trusted_certs/2, insert/3, remove/2, clear/1, db_size/1, - ref_count/3, lookup_trusted_cert/4, foldl/3, - lookup_cached_pem/2, cache_pem_file/2, cache_pem_file/3, - lookup/2]). + ref_count/3, lookup_trusted_cert/4, foldl/3, select_cert_by_issuer/2, + decode_pem_file/1, lookup/2]). %%==================================================================== %% Internal application API %%==================================================================== %%-------------------------------------------------------------------- --spec create() -> [db_handle(),...]. +-spec create(atom()) -> [db_handle(),...]. %% %% Description: Creates a new certificate db. %% Note: lookup_trusted_cert/4 may be called from any process but only %% the process that called create may call the other functions. %%-------------------------------------------------------------------- -create() -> +create(PEMCacheName) -> [%% Let connection process delete trusted certs %% that can only belong to one connection. (Supplied directly %% on DER format to ssl:connect/listen.) ets:new(ssl_otp_cacertificate_db, [set, public]), %% Let connection processes call ref_count/3 directly - ets:new(ssl_otp_ca_file_ref, [set, public]), - ets:new(ssl_otp_pem_cache, [set, protected]) + {ets:new(ssl_otp_ca_file_ref, [set, public]), + ets:new(ssl_otp_ca_ref_file_mapping, [set, protected]) + }, + %% Lookups in named table owned by ssl_pem_cache process + PEMCacheName, + %% Default cache + {ets:new(ssl_otp_crl_cache, [set, protected]), + ets:new(ssl_otp_crl_issuer_mapping, [bag, protected])} ]. +create_pem_cache(Name) -> + ets:new(Name, [named_table, set, protected]). + %%-------------------------------------------------------------------- --spec remove([db_handle()]) -> ok. +-spec remove([db_handle()]) -> ok. %% %% Description: Removes database db %%-------------------------------------------------------------------- remove(Dbs) -> - lists:foreach(fun(Db) -> + lists:foreach(fun({Db0, Db1}) -> + true = ets:delete(Db0), + true = ets:delete(Db1); + (undefined) -> + ok; + (Name) when is_atom(Name) -> + NormalName = ssl_pem_cache:name(normal), + DistName = ssl_pem_cache:name(dist), + case Name of + NormalName -> + ok; + DistName -> + ok; + _ -> + true = ets:delete(Name) + end; + (Db) -> true = ets:delete(Db) end, Dbs). @@ -73,19 +100,24 @@ remove(Dbs) -> %% <SerialNumber, Issuer>. Ref is used as it is specified %% for each connection which certificates are trusted. %%-------------------------------------------------------------------- -lookup_trusted_cert(DbHandle, Ref, SerialNumber, Issuer) -> +lookup_trusted_cert(DbHandle, Ref, SerialNumber, Issuer) when is_reference(Ref) -> case lookup({Ref, SerialNumber, Issuer}, DbHandle) of undefined -> undefined; [Certs] -> {ok, Certs} + end; +lookup_trusted_cert(_DbHandle, {extracted,Certs}, SerialNumber, Issuer) -> + try + [throw(Cert) + || {decoded, {{_Ref,CertSerial,CertIssuer}, Cert}} <- Certs, + CertSerial =:= SerialNumber, CertIssuer =:= Issuer], + undefined + catch + Cert -> + {ok, Cert} end. -lookup_cached_pem([_, _, PemChache], File) -> - lookup_cached_pem(PemChache, File); -lookup_cached_pem(PemChache, File) -> - lookup(File, PemChache). - %%-------------------------------------------------------------------- -spec add_trusted_certs(pid(), {erlang:timestamp(), string()} | {der, list()}, [db_handle()]) -> {ok, [db_handle()]}. @@ -94,42 +126,48 @@ lookup_cached_pem(PemChache, File) -> %% runtime database. Returns Ref that should be handed to lookup_trusted_cert %% together with the cert serialnumber and issuer. %%-------------------------------------------------------------------- -add_trusted_certs(_Pid, {der, DerList}, [CerDb, _,_]) -> +add_trusted_certs(_Pid, {extracted, _} = Certs, _) -> + {ok, Certs}; + +add_trusted_certs(_Pid, {der, DerList}, [CertDb, _,_ | _]) -> NewRef = make_ref(), - add_certs_from_der(DerList, NewRef, CerDb), + add_certs_from_der(DerList, NewRef, CertDb), {ok, NewRef}; -add_trusted_certs(_Pid, File, [CertsDb, RefDb, PemChache] = Db) -> - case lookup_cached_pem(Db, File) of - [{_Content, Ref}] -> +add_trusted_certs(_Pid, File, [ _, {RefDb, FileMapDb} | _] = Db) -> + case lookup(File, FileMapDb) of + [Ref] -> ref_count(Ref, RefDb, 1), {ok, Ref}; - [Content] -> - Ref = make_ref(), - update_counter(Ref, 1, RefDb), - insert(File, {Content, Ref}, PemChache), - add_certs_from_pem(Content, Ref, CertsDb), - {ok, Ref}; undefined -> new_trusted_cert_entry(File, Db) end. -%%-------------------------------------------------------------------- -%% -%% Description: Cache file as binary in DB -%%-------------------------------------------------------------------- --spec cache_pem_file(binary(), [db_handle()]) -> {ok, term()}. -cache_pem_file(File, [_CertsDb, _RefDb, PemChache]) -> - {ok, PemBin} = file:read_file(File), - Content = public_key:pem_decode(PemBin), - insert(File, Content, PemChache), - {ok, Content}. --spec cache_pem_file(reference(), binary(), [db_handle()]) -> {ok, term()}. -cache_pem_file(Ref, File, [_CertsDb, _RefDb, PemChache]) -> - {ok, PemBin} = file:read_file(File), - Content = public_key:pem_decode(PemBin), - insert(File, {Content, Ref}, PemChache), - {ok, Content}. +extract_trusted_certs({der, DerList}) -> + {ok, {extracted, certs_from_der(DerList)}}; +extract_trusted_certs(File) -> + case file:read_file(File) of + {ok, PemBin} -> + Content = public_key:pem_decode(PemBin), + DerList = [Cert || {'Certificate', Cert, not_encrypted} <- Content], + {ok, {extracted, certs_from_der(DerList)}}; + Error -> + %% Have to simulate a failure happening in a server for + %% external handlers. + {error, {badmatch, Error}} + end. + +-spec decode_pem_file(binary()) -> {ok, term()} | {error, term()}. +decode_pem_file(File) -> + case file:read_file(File) of + {ok, PemBin} -> + Content = public_key:pem_decode(PemBin), + {ok, Content}; + Error -> + %% Have to simulate a failure happening in a server for + %% external handlers. + {error, {badmatch, Error}} + end. %%-------------------------------------------------------------------- -spec remove_trusted_certs(reference(), db_handle()) -> ok. @@ -149,6 +187,15 @@ remove(Key, Db) -> ok. %%-------------------------------------------------------------------- +-spec remove(term(), term(), db_handle()) -> ok. +%% +%% Description: Removes an element in a <Db>. +%%-------------------------------------------------------------------- +remove(Key, Data, Db) -> + ets:delete_object(Db, {Key, Data}), + ok. + +%%-------------------------------------------------------------------- -spec lookup(term(), db_handle()) -> [term()] | undefined. %% %% Description: Looks up an element in a <Db>. @@ -175,11 +222,19 @@ lookup(Key, Db) -> foldl(Fun, Acc0, Cache) -> ets:foldl(Fun, Acc0, Cache). + +select_cert_by_issuer(Cache, Issuer) -> + ets:select(Cache, [{{{'_','_', Issuer},{'_', '$1'}},[],['$$']}]). + %%-------------------------------------------------------------------- -spec ref_count(term(), db_handle(), integer()) -> integer(). %% %% Description: Updates a reference counter in a <Db>. %%-------------------------------------------------------------------- +ref_count({extracted, _}, _Db, _N) -> + not_cached; +ref_count(Key, {Db, _}, N) -> + ref_count(Key, Db, N); ref_count(Key, Db, N) -> ets:update_counter(Db,Key,N). @@ -212,9 +267,9 @@ insert(Key, Data, Db) -> %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- -update_counter(Key, Count, Db) -> - true = ets:insert(Db, {Key, Count}), - ok. +init_ref_db(Ref, File, {RefDb, FileMapDb}) -> + true = ets:insert(RefDb, {Ref, 1}), + true = ets:insert(FileMapDb, {File, Ref}). remove_certs(Ref, CertsDb) -> true = ets:match_delete(CertsDb, {{Ref, '_', '_'}, '_'}), @@ -225,28 +280,79 @@ add_certs_from_der(DerList, Ref, CertsDb) -> [Add(Cert) || Cert <- DerList], ok. +certs_from_der(DerList) -> + Ref = make_ref(), + [Decoded || Cert <- DerList, + Decoded <- [decode_certs(Ref, Cert)], + Decoded =/= undefined]. + add_certs_from_pem(PemEntries, Ref, CertsDb) -> Add = fun(Cert) -> add_certs(Cert, Ref, CertsDb) end, [Add(Cert) || {'Certificate', Cert, not_encrypted} <- PemEntries], ok. add_certs(Cert, Ref, CertsDb) -> + try + {decoded, {Key, Val}} = decode_certs(Ref, Cert), + insert(Key, Val, CertsDb) + catch + error:_ -> + ok + end. + +decode_certs(Ref, Cert) -> try ErlCert = public_key:pkix_decode_cert(Cert, otp), TBSCertificate = ErlCert#'OTPCertificate'.tbsCertificate, SerialNumber = TBSCertificate#'OTPTBSCertificate'.serialNumber, Issuer = public_key:pkix_normalize_name( TBSCertificate#'OTPTBSCertificate'.issuer), - insert({Ref, SerialNumber, Issuer}, {Cert,ErlCert}, CertsDb) + {decoded, {{Ref, SerialNumber, Issuer}, {Cert, ErlCert}}} catch error:_ -> Report = io_lib:format("SSL WARNING: Ignoring a CA cert as " "it could not be correctly decoded.~n", []), - error_logger:info_report(Report) + error_logger:info_report(Report), + undefined end. -new_trusted_cert_entry(File, [CertsDb, RefDb, _] = Db) -> - Ref = make_ref(), - update_counter(Ref, 1, RefDb), - {ok, Content} = cache_pem_file(Ref, File, Db), - add_certs_from_pem(Content, Ref, CertsDb), - {ok, Ref}. +new_trusted_cert_entry(File, [CertsDb, RefsDb, _ | _]) -> + case decode_pem_file(File) of + {ok, Content} -> + Ref = make_ref(), + init_ref_db(Ref, File, RefsDb), + ok = ssl_pem_cache:insert(File, Content), + add_certs_from_pem(Content, Ref, CertsDb), + {ok, Ref}; + Error -> + Error + end. + +add_crls([_,_,_, {_, Mapping} | _], ?NO_DIST_POINT, CRLs) -> + [add_crls(CRL, Mapping) || CRL <- CRLs]; +add_crls([_,_,_, {Cache, Mapping} | _], Path, CRLs) -> + insert(Path, CRLs, Cache), + [add_crls(CRL, Mapping) || CRL <- CRLs]. + +add_crls(CRL, Mapping) -> + insert(crl_issuer(CRL), CRL, Mapping). + +remove_crls([_,_,_, {_, Mapping} | _], {?NO_DIST_POINT, CRLs}) -> + [rm_crls(CRL, Mapping) || CRL <- CRLs]; + +remove_crls([_,_,_, {Cache, Mapping} | _], Path) -> + case lookup(Path, Cache) of + undefined -> + ok; + CRLs -> + remove(Path, Cache), + [rm_crls(CRL, Mapping) || CRL <- CRLs] + end. + +rm_crls(CRL, Mapping) -> + remove(crl_issuer(CRL), CRL, Mapping). + +crl_issuer(DerCRL) -> + CRL = public_key:der_decode('CertificateList', DerCRL), + TBSCRL = CRL#'CertificateList'.tbsCertList, + TBSCRL#'TBSCertList'.issuer. + diff --git a/lib/ssl/src/ssl_record.erl b/lib/ssl/src/ssl_record.erl index 025a46bf65..b9d1320ef3 100644 --- a/lib/ssl/src/ssl_record.erl +++ b/lib/ssl/src/ssl_record.erl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2013-2015. All Rights Reserved. +%% Copyright Ericsson AB 2013-2018. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. +%% 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 %% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% 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% @@ -29,139 +30,109 @@ -include("ssl_alert.hrl"). %% Connection state handling --export([init_connection_states/1, - current_connection_state/2, pending_connection_state/2, - activate_pending_connection_state/2, +-export([initial_security_params/1, current_connection_state/2, pending_connection_state/2, + activate_pending_connection_state/3, set_security_params/3, set_mac_secret/4, set_master_secret/2, set_pending_cipher_state/4, set_renegotiation_flag/2, set_client_verify_data/3, - set_server_verify_data/3]). - -%% Encoding records --export([encode_handshake/3, encode_alert_record/3, - encode_change_cipher_spec/2, encode_data/3]). + set_server_verify_data/3, + empty_connection_state/2, initial_connection_state/2, record_protocol_role/1]). %% Compression -export([compress/3, uncompress/3, compressions/0]). %% Payload encryption/decryption --export([cipher/4, decipher/4, is_correct_mac/2]). +-export([cipher/4, decipher/4, cipher_aead/4, decipher_aead/5, is_correct_mac/2, nonce_seed/3]). --export_type([ssl_version/0, ssl_atom_version/0]). +-export_type([ssl_version/0, ssl_atom_version/0, connection_states/0, connection_state/0]). -type ssl_version() :: {integer(), integer()}. -type ssl_atom_version() :: tls_record:tls_atom_version(). +-type connection_states() :: term(). %% Map +-type connection_state() :: term(). %% Map %%==================================================================== -%% Internal application API +%% Connection state handling %%==================================================================== %%-------------------------------------------------------------------- --spec init_connection_states(client | server) -> #connection_states{}. +-spec current_connection_state(connection_states(), read | write) -> + connection_state(). %% -%% Description: Creates a connection_states record with appropriate -%% values for the initial SSL connection setup. +%% Description: Returns the instance of the connection_state map +%% that is currently defined as the current connection state. %%-------------------------------------------------------------------- -init_connection_states(Role) -> - ConnectionEnd = record_protocol_role(Role), - Current = initial_connection_state(ConnectionEnd), - Pending = empty_connection_state(ConnectionEnd), - #connection_states{current_read = Current, - pending_read = Pending, - current_write = Current, - pending_write = Pending - }. +current_connection_state(ConnectionStates, read) -> + maps:get(current_read, ConnectionStates); +current_connection_state(ConnectionStates, write) -> + maps:get(current_write, ConnectionStates). %%-------------------------------------------------------------------- --spec current_connection_state(#connection_states{}, read | write) -> - #connection_state{}. +-spec pending_connection_state(connection_states(), read | write) -> + connection_state(). %% -%% Description: Returns the instance of the connection_state record -%% that is currently defined as the current conection state. +%% Description: Returns the instance of the connection_state map +%% that is pendingly defined as the pending connection state. %%-------------------------------------------------------------------- -current_connection_state(#connection_states{current_read = Current}, - read) -> - Current; -current_connection_state(#connection_states{current_write = Current}, - write) -> - Current. +pending_connection_state(ConnectionStates, read) -> + maps:get(pending_read, ConnectionStates); +pending_connection_state(ConnectionStates, write) -> + maps:get(pending_write, ConnectionStates). %%-------------------------------------------------------------------- --spec pending_connection_state(#connection_states{}, read | write) -> - term(). -%% -%% Description: Returns the instance of the connection_state record -%% that is currently defined as the pending conection state. -%%-------------------------------------------------------------------- -pending_connection_state(#connection_states{pending_read = Pending}, - read) -> - Pending; -pending_connection_state(#connection_states{pending_write = Pending}, - write) -> - Pending. - - -%%-------------------------------------------------------------------- --spec activate_pending_connection_state(#connection_states{}, read | write) -> - #connection_states{}. +-spec activate_pending_connection_state(connection_states(), read | write, tls_connection | dtls_connection) -> + connection_states(). %% %% Description: Creates a new instance of the connection_states record %% where the pending state of <Type> has been activated. %%-------------------------------------------------------------------- -activate_pending_connection_state(States = - #connection_states{current_read = Current, - pending_read = Pending}, - read) -> - NewCurrent = Pending#connection_state{epoch = dtls_next_epoch(Current), - sequence_number = 0}, - SecParams = Pending#connection_state.security_parameters, +activate_pending_connection_state(#{current_read := Current, + pending_read := Pending} = States, + read, Connection) -> + #{secure_renegotiation := SecureRenegotation} = Current, + #{beast_mitigation := BeastMitigation, + security_parameters := SecParams} = Pending, + NewCurrent = Pending#{sequence_number => 0}, ConnectionEnd = SecParams#security_parameters.connection_end, - EmptyPending = empty_connection_state(ConnectionEnd), - SecureRenegotation = NewCurrent#connection_state.secure_renegotiation, - NewPending = EmptyPending#connection_state{secure_renegotiation = SecureRenegotation}, - States#connection_states{current_read = NewCurrent, - pending_read = NewPending - }; - -activate_pending_connection_state(States = - #connection_states{current_write = Current, - pending_write = Pending}, - write) -> - NewCurrent = Pending#connection_state{epoch = dtls_next_epoch(Current), - sequence_number = 0}, - SecParams = Pending#connection_state.security_parameters, + EmptyPending = Connection:empty_connection_state(ConnectionEnd, BeastMitigation), + NewPending = EmptyPending#{secure_renegotiation => SecureRenegotation}, + States#{current_read => NewCurrent, + pending_read => NewPending + }; + +activate_pending_connection_state(#{current_write := Current, + pending_write := Pending} = States, + write, Connection) -> + NewCurrent = Pending#{sequence_number => 0}, + #{secure_renegotiation := SecureRenegotation} = Current, + #{beast_mitigation := BeastMitigation, + security_parameters := SecParams} = Pending, ConnectionEnd = SecParams#security_parameters.connection_end, - EmptyPending = empty_connection_state(ConnectionEnd), - SecureRenegotation = NewCurrent#connection_state.secure_renegotiation, - NewPending = EmptyPending#connection_state{secure_renegotiation = SecureRenegotation}, - States#connection_states{current_write = NewCurrent, - pending_write = NewPending - }. - + EmptyPending = Connection:empty_connection_state(ConnectionEnd, BeastMitigation), + NewPending = EmptyPending#{secure_renegotiation => SecureRenegotation}, + States#{current_write => NewCurrent, + pending_write => NewPending + }. %%-------------------------------------------------------------------- -spec set_security_params(#security_parameters{}, #security_parameters{}, - #connection_states{}) -> #connection_states{}. + connection_states()) -> connection_states(). %% %% Description: Creates a new instance of the connection_states record %% where the pending states gets its security parameters updated. %%-------------------------------------------------------------------- -set_security_params(ReadParams, WriteParams, States = - #connection_states{pending_read = Read, - pending_write = Write}) -> - States#connection_states{pending_read = - Read#connection_state{security_parameters = - ReadParams}, - pending_write = - Write#connection_state{security_parameters = - WriteParams} - }. +set_security_params(ReadParams, WriteParams, + #{pending_read := Read, + pending_write := Write} = States) -> + States#{pending_read => Read#{security_parameters => ReadParams}, + pending_write => Write#{security_parameters => WriteParams} + }. %%-------------------------------------------------------------------- -spec set_mac_secret(binary(), binary(), client | server, - #connection_states{}) -> #connection_states{}. + connection_states()) -> connection_states(). %% %% Description: update the mac_secret field in pending connection states %%-------------------------------------------------------------------- @@ -171,180 +142,134 @@ set_mac_secret(ClientWriteMacSecret, ServerWriteMacSecret, server, States) -> set_mac_secret(ClientWriteMacSecret, ServerWriteMacSecret, States). set_mac_secret(ReadMacSecret, WriteMacSecret, - States = #connection_states{pending_read = Read, - pending_write = Write}) -> - States#connection_states{ - pending_read = Read#connection_state{mac_secret = ReadMacSecret}, - pending_write = Write#connection_state{mac_secret = WriteMacSecret} + States = #{pending_read := Read, + pending_write := Write}) -> + States#{pending_read => Read#{mac_secret => ReadMacSecret}, + pending_write => Write#{mac_secret => WriteMacSecret} }. %%-------------------------------------------------------------------- --spec set_master_secret(binary(), #connection_states{}) -> #connection_states{}. +-spec set_master_secret(binary(), connection_states()) -> connection_states(). %% %% Description: Set master_secret in pending connection states %%-------------------------------------------------------------------- set_master_secret(MasterSecret, - States = #connection_states{pending_read = Read, - pending_write = Write}) -> - ReadSecPar = Read#connection_state.security_parameters, - Read1 = Read#connection_state{ - security_parameters = ReadSecPar#security_parameters{ - master_secret = MasterSecret}}, - WriteSecPar = Write#connection_state.security_parameters, - Write1 = Write#connection_state{ - security_parameters = WriteSecPar#security_parameters{ - master_secret = MasterSecret}}, - States#connection_states{pending_read = Read1, pending_write = Write1}. + States = #{pending_read := Read = #{security_parameters := ReadSecPar}, + pending_write := Write = #{security_parameters := WriteSecPar}}) -> + Read1 = Read#{security_parameters => ReadSecPar#security_parameters{ + master_secret = MasterSecret}}, + Write1 = Write#{security_parameters => WriteSecPar#security_parameters{ + master_secret = MasterSecret}}, + States#{pending_read => Read1, pending_write => Write1}. %%-------------------------------------------------------------------- --spec set_renegotiation_flag(boolean(), #connection_states{}) -> #connection_states{}. +-spec set_renegotiation_flag(boolean(), connection_states()) -> connection_states(). %% %% Description: Set secure_renegotiation in pending connection states %%-------------------------------------------------------------------- -set_renegotiation_flag(Flag, #connection_states{ - current_read = CurrentRead0, - current_write = CurrentWrite0, - pending_read = PendingRead0, - pending_write = PendingWrite0} +set_renegotiation_flag(Flag, #{current_read := CurrentRead0, + current_write := CurrentWrite0, + pending_read := PendingRead0, + pending_write := PendingWrite0} = ConnectionStates) -> - CurrentRead = CurrentRead0#connection_state{secure_renegotiation = Flag}, - CurrentWrite = CurrentWrite0#connection_state{secure_renegotiation = Flag}, - PendingRead = PendingRead0#connection_state{secure_renegotiation = Flag}, - PendingWrite = PendingWrite0#connection_state{secure_renegotiation = Flag}, - ConnectionStates#connection_states{current_read = CurrentRead, - current_write = CurrentWrite, - pending_read = PendingRead, - pending_write = PendingWrite}. + CurrentRead = CurrentRead0#{secure_renegotiation => Flag}, + CurrentWrite = CurrentWrite0#{secure_renegotiation => Flag}, + PendingRead = PendingRead0#{secure_renegotiation => Flag}, + PendingWrite = PendingWrite0#{secure_renegotiation => Flag}, + ConnectionStates#{current_read => CurrentRead, + current_write => CurrentWrite, + pending_read => PendingRead, + pending_write => PendingWrite}. %%-------------------------------------------------------------------- -spec set_client_verify_data(current_read | current_write | current_both, - binary(), #connection_states{})-> - #connection_states{}. + binary(), connection_states())-> + connection_states(). %% %% Description: Set verify data in connection states. %%-------------------------------------------------------------------- set_client_verify_data(current_read, Data, - #connection_states{current_read = CurrentRead0, - pending_write = PendingWrite0} + #{current_read := CurrentRead0, + pending_write := PendingWrite0} = ConnectionStates) -> - CurrentRead = CurrentRead0#connection_state{client_verify_data = Data}, - PendingWrite = PendingWrite0#connection_state{client_verify_data = Data}, - ConnectionStates#connection_states{current_read = CurrentRead, - pending_write = PendingWrite}; + CurrentRead = CurrentRead0#{client_verify_data => Data}, + PendingWrite = PendingWrite0#{client_verify_data => Data}, + ConnectionStates#{current_read => CurrentRead, + pending_write => PendingWrite}; set_client_verify_data(current_write, Data, - #connection_states{pending_read = PendingRead0, - current_write = CurrentWrite0} + #{pending_read := PendingRead0, + current_write := CurrentWrite0} = ConnectionStates) -> - PendingRead = PendingRead0#connection_state{client_verify_data = Data}, - CurrentWrite = CurrentWrite0#connection_state{client_verify_data = Data}, - ConnectionStates#connection_states{pending_read = PendingRead, - current_write = CurrentWrite}; + PendingRead = PendingRead0#{client_verify_data => Data}, + CurrentWrite = CurrentWrite0#{client_verify_data => Data}, + ConnectionStates#{pending_read => PendingRead, + current_write => CurrentWrite}; set_client_verify_data(current_both, Data, - #connection_states{current_read = CurrentRead0, - current_write = CurrentWrite0} + #{current_read := CurrentRead0, + current_write := CurrentWrite0} = ConnectionStates) -> - CurrentRead = CurrentRead0#connection_state{client_verify_data = Data}, - CurrentWrite = CurrentWrite0#connection_state{client_verify_data = Data}, - ConnectionStates#connection_states{current_read = CurrentRead, - current_write = CurrentWrite}. + CurrentRead = CurrentRead0#{client_verify_data => Data}, + CurrentWrite = CurrentWrite0#{client_verify_data => Data}, + ConnectionStates#{current_read => CurrentRead, + current_write => CurrentWrite}. %%-------------------------------------------------------------------- -spec set_server_verify_data(current_read | current_write | current_both, - binary(), #connection_states{})-> - #connection_states{}. + binary(), connection_states())-> + connection_states(). %% %% Description: Set verify data in pending connection states. %%-------------------------------------------------------------------- set_server_verify_data(current_write, Data, - #connection_states{pending_read = PendingRead0, - current_write = CurrentWrite0} + #{pending_read := PendingRead0, + current_write := CurrentWrite0} = ConnectionStates) -> - PendingRead = PendingRead0#connection_state{server_verify_data = Data}, - CurrentWrite = CurrentWrite0#connection_state{server_verify_data = Data}, - ConnectionStates#connection_states{pending_read = PendingRead, - current_write = CurrentWrite}; + PendingRead = PendingRead0#{server_verify_data => Data}, + CurrentWrite = CurrentWrite0#{server_verify_data => Data}, + ConnectionStates#{pending_read => PendingRead, + current_write => CurrentWrite}; set_server_verify_data(current_read, Data, - #connection_states{current_read = CurrentRead0, - pending_write = PendingWrite0} + #{current_read := CurrentRead0, + pending_write := PendingWrite0} = ConnectionStates) -> - CurrentRead = CurrentRead0#connection_state{server_verify_data = Data}, - PendingWrite = PendingWrite0#connection_state{server_verify_data = Data}, - ConnectionStates#connection_states{current_read = CurrentRead, - pending_write = PendingWrite}; + CurrentRead = CurrentRead0#{server_verify_data => Data}, + PendingWrite = PendingWrite0#{server_verify_data => Data}, + ConnectionStates#{current_read => CurrentRead, + pending_write => PendingWrite}; set_server_verify_data(current_both, Data, - #connection_states{current_read = CurrentRead0, - current_write = CurrentWrite0} + #{current_read := CurrentRead0, + current_write := CurrentWrite0} = ConnectionStates) -> - CurrentRead = CurrentRead0#connection_state{server_verify_data = Data}, - CurrentWrite = CurrentWrite0#connection_state{server_verify_data = Data}, - ConnectionStates#connection_states{current_read = CurrentRead, - current_write = CurrentWrite}. + CurrentRead = CurrentRead0#{server_verify_data => Data}, + CurrentWrite = CurrentWrite0#{server_verify_data => Data}, + ConnectionStates#{current_read => CurrentRead, + current_write => CurrentWrite}. %%-------------------------------------------------------------------- --spec set_pending_cipher_state(#connection_states{}, #cipher_state{}, +-spec set_pending_cipher_state(connection_states(), #cipher_state{}, #cipher_state{}, client | server) -> - #connection_states{}. + connection_states(). %% %% Description: Set the cipher state in the specified pending connection state. %%-------------------------------------------------------------------- -set_pending_cipher_state(#connection_states{pending_read = Read, - pending_write = Write} = States, +set_pending_cipher_state(#{pending_read := Read, + pending_write := Write} = States, ClientState, ServerState, server) -> - States#connection_states{ - pending_read = Read#connection_state{cipher_state = ClientState}, - pending_write = Write#connection_state{cipher_state = ServerState}}; + States#{ + pending_read => Read#{cipher_state => ClientState}, + pending_write => Write#{cipher_state => ServerState}}; -set_pending_cipher_state(#connection_states{pending_read = Read, - pending_write = Write} = States, +set_pending_cipher_state(#{pending_read := Read, + pending_write := Write} = States, ClientState, ServerState, client) -> - States#connection_states{ - pending_read = Read#connection_state{cipher_state = ServerState}, - pending_write = Write#connection_state{cipher_state = ClientState}}. - + States#{ + pending_read => Read#{cipher_state => ServerState}, + pending_write => Write#{cipher_state => ClientState}}. -%%-------------------------------------------------------------------- --spec encode_handshake(iolist(), ssl_version(), #connection_states{}) -> - {iolist(), #connection_states{}}. -%% -%% Description: Encodes a handshake message to send on the ssl-socket. -%%-------------------------------------------------------------------- -encode_handshake(Frag, Version, ConnectionStates) -> - encode_plain_text(?HANDSHAKE, Version, Frag, ConnectionStates). - -%%-------------------------------------------------------------------- --spec encode_alert_record(#alert{}, ssl_version(), #connection_states{}) -> - {iolist(), #connection_states{}}. -%% -%% Description: Encodes an alert message to send on the ssl-socket. -%%-------------------------------------------------------------------- -encode_alert_record(#alert{level = Level, description = Description}, - Version, ConnectionStates) -> - encode_plain_text(?ALERT, Version, <<?BYTE(Level), ?BYTE(Description)>>, - ConnectionStates). - -%%-------------------------------------------------------------------- --spec encode_change_cipher_spec(ssl_version(), #connection_states{}) -> - {iolist(), #connection_states{}}. -%% -%% Description: Encodes a change_cipher_spec-message to send on the ssl socket. -%%-------------------------------------------------------------------- -encode_change_cipher_spec(Version, ConnectionStates) -> - encode_plain_text(?CHANGE_CIPHER_SPEC, Version, <<1:8>>, ConnectionStates). - -%%-------------------------------------------------------------------- --spec encode_data(binary(), ssl_version(), #connection_states{}) -> - {iolist(), #connection_states{}}. -%% -%% Description: Encodes data to send on the ssl-socket. -%%-------------------------------------------------------------------- -encode_data(Frag, Version, - #connection_states{current_write = #connection_state{ - security_parameters = - #security_parameters{bulk_cipher_algorithm = BCA}}} = - ConnectionStates) -> - Data = split_bin(Frag, ?MAX_PLAIN_TEXT_LENGTH, Version, BCA), - encode_iolist(?APPLICATION_DATA, Data, Version, ConnectionStates). +%%==================================================================== +%% Compression +%%==================================================================== uncompress(?NULL, Data, CS) -> {Data, CS}. @@ -360,48 +285,107 @@ compress(?NULL, Data, CS) -> compressions() -> [?byte(?NULL)]. + +%%==================================================================== +%% Payload encryption/decryption +%%==================================================================== + %%-------------------------------------------------------------------- --spec cipher(ssl_version(), iodata(), #connection_state{}, MacHash::binary()) -> - {CipherFragment::binary(), #connection_state{}}. +-spec cipher(ssl_version(), iodata(), connection_state(), MacHash::binary()) -> + {CipherFragment::binary(), connection_state()}. %% %% Description: Payload encryption %%-------------------------------------------------------------------- cipher(Version, Fragment, - #connection_state{cipher_state = CipherS0, - security_parameters= - #security_parameters{bulk_cipher_algorithm = - BulkCipherAlgo} - } = WriteState0, MacHash) -> - + #{cipher_state := CipherS0, + security_parameters := + #security_parameters{bulk_cipher_algorithm = + BulkCipherAlgo} + } = WriteState0, MacHash) -> + {CipherFragment, CipherS1} = ssl_cipher:cipher(BulkCipherAlgo, CipherS0, MacHash, Fragment, Version), - {CipherFragment, WriteState0#connection_state{cipher_state = CipherS1}}. + {CipherFragment, WriteState0#{cipher_state => CipherS1}}. +%%-------------------------------------------------------------------- +-spec cipher_aead(ssl_version(), iodata(), connection_state(), AAD::binary()) -> + {CipherFragment::binary(), connection_state()}. + +%% Description: Payload encryption +%% %%-------------------------------------------------------------------- +cipher_aead(Version, Fragment, + #{cipher_state := CipherS0, + security_parameters := + #security_parameters{bulk_cipher_algorithm = + BulkCipherAlgo} + } = WriteState0, AAD) -> + {CipherFragment, CipherS1} = + cipher_aead(BulkCipherAlgo, CipherS0, AAD, Fragment, Version), + {CipherFragment, WriteState0#{cipher_state => CipherS1}}. %%-------------------------------------------------------------------- --spec decipher(ssl_version(), binary(), #connection_state{}, boolean()) -> {binary(), binary(), #connection_state{}} | #alert{}. +-spec decipher(ssl_version(), binary(), connection_state(), boolean()) -> + {binary(), binary(), connection_state()} | #alert{}. %% %% Description: Payload decryption %%-------------------------------------------------------------------- decipher(Version, CipherFragment, - #connection_state{security_parameters = - #security_parameters{bulk_cipher_algorithm = - BulkCipherAlgo, - hash_size = HashSz}, - cipher_state = CipherS0 - } = ReadState, PaddingCheck) -> + #{security_parameters := + #security_parameters{bulk_cipher_algorithm = + BulkCipherAlgo, + hash_size = HashSz}, + cipher_state := CipherS0 + } = ReadState, PaddingCheck) -> case ssl_cipher:decipher(BulkCipherAlgo, HashSz, CipherS0, CipherFragment, Version, PaddingCheck) of {PlainFragment, Mac, CipherS1} -> - CS1 = ReadState#connection_state{cipher_state = CipherS1}, + CS1 = ReadState#{cipher_state => CipherS1}, {PlainFragment, Mac, CS1}; #alert{} = Alert -> Alert end. %%-------------------------------------------------------------------- +-spec decipher_aead(ssl_cipher:cipher_enum(), #cipher_state{}, + binary(), binary(), ssl_record:ssl_version()) -> + {binary(), #cipher_state{}} | #alert{}. +%% +%% Description: Decrypts the data and checks the associated data (AAD) MAC using +%% cipher described by cipher_enum() and updating the cipher state. +%% Use for suites that use authenticated encryption with associated data (AEAD) +%%------------------------------------------------------------------- +decipher_aead(Type, #cipher_state{key = Key} = CipherState, AAD0, CipherFragment, _) -> + try + Nonce = decrypt_nonce(Type, CipherState, CipherFragment), + {AAD, CipherText, CipherTag} = aead_ciphertext_split(Type, CipherState, CipherFragment, AAD0), + case ssl_cipher:aead_decrypt(Type, Key, Nonce, CipherText, CipherTag, AAD) of + Content when is_binary(Content) -> + {Content, CipherState}; + _ -> + ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC, decryption_failed) + end + catch + _:_ -> + ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC, decryption_failed) + end. + +nonce_seed(?CHACHA20_POLY1305, Seed, CipherState) -> + ssl_cipher:nonce_seed(Seed, CipherState); +nonce_seed(_,_, CipherState) -> + CipherState. + +%%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- -empty_connection_state(ConnectionEnd) -> + +empty_connection_state(ConnectionEnd, BeastMitigation) -> SecParams = empty_security_params(ConnectionEnd), - #connection_state{security_parameters = SecParams}. + #{security_parameters => SecParams, + beast_mitigation => BeastMitigation, + compression_state => undefined, + cipher_state => undefined, + mac_secret => undefined, + secure_renegotiation => undefined, + client_verify_data => undefined, + server_verify_data => undefined + }. empty_security_params(ConnectionEnd = ?CLIENT) -> #security_parameters{connection_end = ConnectionEnd, @@ -412,14 +396,9 @@ 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 - undefined; -dtls_next_epoch(#connection_state{epoch = Epoch}) -> %% DTLS - Epoch + 1. - is_correct_mac(Mac, Mac) -> true; is_correct_mac(_M,_H) -> @@ -430,52 +409,55 @@ record_protocol_role(client) -> record_protocol_role(server) -> ?SERVER. -initial_connection_state(ConnectionEnd) -> - #connection_state{security_parameters = - initial_security_params(ConnectionEnd), - sequence_number = 0 - }. +initial_connection_state(ConnectionEnd, BeastMitigation) -> + #{security_parameters => + initial_security_params(ConnectionEnd), + sequence_number => 0, + beast_mitigation => BeastMitigation, + compression_state => undefined, + cipher_state => undefined, + mac_secret => undefined, + secure_renegotiation => undefined, + client_verify_data => undefined, + server_verify_data => undefined + }. initial_security_params(ConnectionEnd) -> SecParams = #security_parameters{connection_end = ConnectionEnd, compression_algorithm = ?NULL}, ssl_cipher:security_parameters(?TLS_NULL_WITH_NULL_NULL, SecParams). - -encode_plain_text(Type, Version, Data, ConnectionStates) -> - RecordCB = protocol_module(Version), - RecordCB:encode_plain_text(Type, Version, Data, ConnectionStates). - -encode_iolist(Type, Data, Version, ConnectionStates0) -> - RecordCB = protocol_module(Version), - {ConnectionStates, EncodedMsg} = - lists:foldl(fun(Text, {CS0, Encoded}) -> - {Enc, CS1} = - RecordCB:encode_plain_text(Type, Version, Text, CS0), - {CS1, [Enc | Encoded]} - end, {ConnectionStates0, []}, Data), - {lists:reverse(EncodedMsg), ConnectionStates}. - -%% 1/n-1 splitting countermeasure Rizzo/Duong-Beast, RC4 chiphers are -%% not vulnerable to this attack. -split_bin(<<FirstByte:8, Rest/binary>>, ChunkSize, Version, BCA) when - BCA =/= ?RC4 andalso ({3, 1} == Version orelse - {3, 0} == Version) -> - do_split_bin(Rest, ChunkSize, [[FirstByte]]); -split_bin(Bin, ChunkSize, _, _) -> - do_split_bin(Bin, ChunkSize, []). - -do_split_bin(<<>>, _, Acc) -> - lists:reverse(Acc); -do_split_bin(Bin, ChunkSize, Acc) -> - case Bin of - <<Chunk:ChunkSize/binary, Rest/binary>> -> - do_split_bin(Rest, ChunkSize, [Chunk | Acc]); - _ -> - lists:reverse(Acc, [Bin]) - end. - -protocol_module({3, _}) -> - tls_record; -protocol_module({254, _}) -> - dtls_record. +cipher_aead(?CHACHA20_POLY1305 = Type, #cipher_state{key=Key} = CipherState, AAD0, Fragment, _Version) -> + AAD = end_additional_data(AAD0, erlang:iolist_size(Fragment)), + Nonce = encrypt_nonce(Type, CipherState), + {Content, CipherTag} = ssl_cipher:aead_encrypt(Type, Key, Nonce, Fragment, AAD), + {<<Content/binary, CipherTag/binary>>, CipherState}; +cipher_aead(Type, #cipher_state{key=Key, nonce = ExplicitNonce} = CipherState, AAD0, Fragment, _Version) -> + AAD = end_additional_data(AAD0, erlang:iolist_size(Fragment)), + Nonce = encrypt_nonce(Type, CipherState), + {Content, CipherTag} = ssl_cipher:aead_encrypt(Type, Key, Nonce, Fragment, AAD), + {<<ExplicitNonce:64/integer, Content/binary, CipherTag/binary>>, CipherState#cipher_state{nonce = ExplicitNonce + 1}}. + +encrypt_nonce(?CHACHA20_POLY1305, #cipher_state{nonce = Nonce, iv = IV}) -> + crypto:exor(<<?UINT32(0), Nonce/binary>>, IV); +encrypt_nonce(?AES_GCM, #cipher_state{iv = IV, nonce = ExplicitNonce}) -> + <<Salt:4/bytes, _/binary>> = IV, + <<Salt/binary, ExplicitNonce:64/integer>>. + +decrypt_nonce(?CHACHA20_POLY1305, #cipher_state{nonce = Nonce, iv = IV}, _) -> + crypto:exor(<<Nonce:96/unsigned-big-integer>>, IV); +decrypt_nonce(?AES_GCM, #cipher_state{iv = <<Salt:4/bytes, _/binary>>}, <<ExplicitNonce:8/bytes, _/binary>>) -> + <<Salt/binary, ExplicitNonce/binary>>. + +aead_ciphertext_split(?CHACHA20_POLY1305, #cipher_state{tag_len = Len}, CipherTextFragment, AAD) -> + CipherLen = size(CipherTextFragment) - Len, + <<CipherText:CipherLen/bytes, CipherTag:Len/bytes>> = CipherTextFragment, + {end_additional_data(AAD, CipherLen), CipherText, CipherTag}; +aead_ciphertext_split(?AES_GCM, #cipher_state{tag_len = Len}, CipherTextFragment, AAD) -> + CipherLen = size(CipherTextFragment) - (Len + 8), %% 8 is length of explicit Nonce + << _:8/bytes, CipherText:CipherLen/bytes, CipherTag:Len/bytes>> = CipherTextFragment, + {end_additional_data(AAD, CipherLen), CipherText, CipherTag}. + +end_additional_data(AAD, Len) -> + <<AAD/binary, ?UINT16(Len)>>. + diff --git a/lib/ssl/src/ssl_record.hrl b/lib/ssl/src/ssl_record.hrl index 6aab35d6da..ed007f58d7 100644 --- a/lib/ssl/src/ssl_record.hrl +++ b/lib/ssl/src/ssl_record.hrl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2014. All Rights Reserved. +%% Copyright Ericsson AB 2007-2016. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. +%% 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 %% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% 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% %% @@ -29,25 +30,27 @@ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%% Connection states - RFC 4346 section 6.1 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% --record(connection_state, { - security_parameters, - compression_state, - cipher_state, - mac_secret, - epoch, %% Only used by DTLS - sequence_number, - %% RFC 5746 - secure_renegotiation, - client_verify_data, - server_verify_data - }). +%% For documentation purposes are now maps in implementation +%% -record(connection_state, { +%% security_parameters, +%% compression_state, +%% cipher_state, +%% mac_secret, +%% sequence_number, +%% %% RFC 5746 +%% secure_renegotiation, +%% client_verify_data, +%% server_verify_data, +%% %% How to do BEAST mitigation? +%% beast_mitigation +%% }). --record(connection_states, { - current_read, - pending_read, - current_write, - pending_write - }). +%% -record(connection_states, { +%% current_read, +%% pending_read, +%% current_write, +%% pending_write, +%% }). -record(security_parameters, { cipher_suite, @@ -90,11 +93,14 @@ -define('3DES', 4). -define(DES40, 5). -define(IDEA, 6). --define(AES, 7). +-define(AES_CBC, 7). +-define(AES_GCM, 8). +-define(CHACHA20_POLY1305, 9). %% CipherType -define(STREAM, 0). -define(BLOCK, 1). +-define(AEAD, 2). %% IsExportable %-define(TRUE, 0). %% Already defined by ssl_internal.hrl diff --git a/lib/ssl/src/ssl_session.erl b/lib/ssl/src/ssl_session.erl index a24b2d9444..c9607489e9 100644 --- a/lib/ssl/src/ssl_session.erl +++ b/lib/ssl/src/ssl_session.erl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2012. All Rights Reserved. +%% Copyright Ericsson AB 2007-2016. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% 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% %% @@ -30,8 +31,6 @@ %% Internal application API -export([is_new/2, client_id/4, server_id/6, valid_session/2]). --define('24H_in_sec', 8640). - -type seconds() :: integer(). %%-------------------------------------------------------------------- @@ -62,13 +61,16 @@ client_id(ClientInfo, Cache, CacheCb, OwnCert) -> SessionId end. --spec valid_session(#session{}, seconds()) -> boolean(). +-spec valid_session(#session{}, seconds() | {invalidate_before, integer()}) -> boolean(). %% %% Description: Check that the session has not expired %%-------------------------------------------------------------------- +valid_session(#session{time_stamp = TimeStamp}, {invalidate_before, Before}) -> + TimeStamp > Before; valid_session(#session{time_stamp = TimeStamp}, LifeTime) -> - Now = calendar:datetime_to_gregorian_seconds({date(), time()}), - Now - TimeStamp < LifeTime. + Now = erlang:monotonic_time(), + Lived = erlang:convert_time_unit(Now-TimeStamp, native, seconds), + Lived < LifeTime. server_id(Port, <<>>, _SslOpts, _Cert, _, _) -> {ssl_manager:new_session_id(Port), undefined}; @@ -99,14 +101,14 @@ select_session([], _, _) -> no_session; select_session(Sessions, #ssl_options{ciphers = Ciphers}, OwnCert) -> IsNotResumable = - fun([_Id, Session]) -> + fun(Session) -> not (resumable(Session#session.is_resumable) andalso lists:member(Session#session.cipher_suite, Ciphers) andalso (OwnCert == Session#session.own_certificate)) end, case lists:dropwhile(IsNotResumable, Sessions) of [] -> no_session; - [[Id, _]|_] -> Id + [Session | _] -> Session#session.session_id end. is_resumable(_, _, #ssl_options{reuse_sessions = false}, _, _, _, _) -> diff --git a/lib/ssl/src/ssl_session_cache.erl b/lib/ssl/src/ssl_session_cache.erl index 5c6ee3c54c..c79ad1523b 100644 --- a/lib/ssl/src/ssl_session_cache.erl +++ b/lib/ssl/src/ssl_session_cache.erl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2012. All Rights Reserved. +%% Copyright Ericsson AB 2008-2016. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. +%% 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 %% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% 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% %% @@ -26,13 +27,13 @@ -include("ssl_internal.hrl"). -export([init/1, terminate/1, lookup/2, update/3, delete/2, foldl/3, - select_session/2]). + select_session/2, size/1]). %%-------------------------------------------------------------------- %% Description: Return table reference. Called by ssl_manager process. %%-------------------------------------------------------------------- -init(_) -> - ets:new(cache_name(), [ordered_set, protected]). +init(Options) -> + ets:new(cache_name(proplists:get_value(role, Options)), [ordered_set, protected]). %%-------------------------------------------------------------------- %% Description: Handles cache table at termination of ssl manager. @@ -82,10 +83,16 @@ foldl(Fun, Acc0, Cache) -> %%-------------------------------------------------------------------- select_session(Cache, PartialKey) -> ets:select(Cache, - [{{{PartialKey,'$1'}, '$2'},[],['$$']}]). + [{{{PartialKey,'_'}, '$1'},[],['$1']}]). + +%%-------------------------------------------------------------------- +%% Description: Returns the cache size +%%-------------------------------------------------------------------- +size(Cache) -> + ets:info(Cache, size). %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- -cache_name() -> - ssl_otp_session_cache. +cache_name(Name) -> + list_to_atom(atom_to_list(Name) ++ "_ssl_otp_session_cache"). diff --git a/lib/ssl/src/ssl_session_cache_api.erl b/lib/ssl/src/ssl_session_cache_api.erl index f2b22b0f1b..b68c75a09b 100644 --- a/lib/ssl/src/ssl_session_cache_api.erl +++ b/lib/ssl/src/ssl_session_cache_api.erl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2011. All Rights Reserved. +%% Copyright Ericsson AB 2008-2016. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. +%% 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 %% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% 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% %% @@ -32,3 +33,4 @@ -callback delete(db_handle(), key()) -> any(). -callback foldl(fun(), term(), db_handle()) -> term(). -callback select_session(db_handle(), {host(), inet:port_number()} | inet:port_number()) -> [#session{}]. +-callback size(db_handle()) -> integer(). diff --git a/lib/ssl/src/ssl_srp.hrl b/lib/ssl/src/ssl_srp.hrl index af56a91194..d6e45adeee 100644 --- a/lib/ssl/src/ssl_srp.hrl +++ b/lib/ssl/src/ssl_srp.hrl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2013. All Rights Reserved. -%% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% 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. +%% 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% %% diff --git a/lib/ssl/src/ssl_sup.erl b/lib/ssl/src/ssl_sup.erl index 7cccf8d5a5..05a7aaaa82 100644 --- a/lib/ssl/src/ssl_sup.erl +++ b/lib/ssl/src/ssl_sup.erl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1998-2014. All Rights Reserved. +%% Copyright Ericsson AB 1998-2016. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. +%% 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 %% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% 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% %% @@ -24,7 +25,7 @@ -behaviour(supervisor). %% API --export([start_link/0, manager_opts/0]). +-export([start_link/0]). %% Supervisor callback -export([init/1]). @@ -43,78 +44,28 @@ start_link() -> %%%========================================================================= init([]) -> - SessionCertManager = session_and_cert_manager_child_spec(), - TLSConnetionManager = tls_connection_manager_child_spec(), - %% Not supported yet - %%DTLSConnetionManager = tls_connection_manager_child_spec(), - %% Handles emulated options so that they inherited by the accept socket, even when setopts is performed on - %% the listen socket - ListenOptionsTracker = listen_options_tracker_child_spec(), - {ok, {{one_for_all, 10, 3600}, [SessionCertManager, TLSConnetionManager, ListenOptionsTracker]}}. - + {ok, {{rest_for_one, 10, 3600}, [ssl_admin_child_spec(), + ssl_connection_sup() + ]}}. -manager_opts() -> - CbOpts = case application:get_env(ssl, session_cb) of - {ok, Cb} when is_atom(Cb) -> - InitArgs = session_cb_init_args(), - [{session_cb, Cb}, {session_cb_init_args, InitArgs}]; - _ -> - [] - end, - case application:get_env(ssl, session_lifetime) of - {ok, Time} when is_integer(Time) -> - [{session_lifetime, Time}| CbOpts]; - _ -> - CbOpts - end. - %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- - -session_and_cert_manager_child_spec() -> - Opts = manager_opts(), - Name = ssl_manager, - StartFunc = {ssl_manager, start_link, [Opts]}, +ssl_admin_child_spec() -> + Name = ssl_admin_sup, + StartFunc = {ssl_admin_sup, start_link, []}, Restart = permanent, Shutdown = 4000, - Modules = [ssl_manager], - Type = worker, - {Name, StartFunc, Restart, Shutdown, Type, Modules}. - -tls_connection_manager_child_spec() -> - Name = tls_connection, - StartFunc = {tls_connection_sup, start_link, []}, - Restart = permanent, - Shutdown = 4000, - Modules = [tls_connection_sup], + Modules = [ssl_admin_sup], Type = supervisor, {Name, StartFunc, Restart, Shutdown, Type, Modules}. -%% dtls_connection_manager_child_spec() -> -%% Name = dtls_connection, -%% StartFunc = {dtls_connection_sup, start_link, []}, -%% Restart = permanent, -%% Shutdown = 4000, -%% Modules = [dtls_connection, ssl_connection], -%% Type = supervisor, -%% {Name, StartFunc, Restart, Shutdown, Type, Modules}. - - -listen_options_tracker_child_spec() -> - Name = ssl_socket, - StartFunc = {ssl_listen_tracker_sup, start_link, []}, - Restart = permanent, +ssl_connection_sup() -> + Name = ssl_connection_sup, + StartFunc = {ssl_connection_sup, start_link, []}, + Restart = permanent, Shutdown = 4000, - Modules = [ssl_socket], + Modules = [ssl_connection_sup], Type = supervisor, {Name, StartFunc, Restart, Shutdown, Type, Modules}. - -session_cb_init_args() -> - case application:get_env(ssl, session_cb_init_args) of - {ok, Args} when is_list(Args) -> - Args; - _ -> - [] - end. diff --git a/lib/ssl/src/ssl_tls_dist_proxy.erl b/lib/ssl/src/ssl_tls_dist_proxy.erl deleted file mode 100644 index a22af6b960..0000000000 --- a/lib/ssl/src/ssl_tls_dist_proxy.erl +++ /dev/null @@ -1,329 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2011-2013. All Rights Reserved. -%% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. -%% -%% %CopyrightEnd% -%% --module(ssl_tls_dist_proxy). - - --export([listen/1, accept/1, connect/2, get_tcp_address/1]). --export([init/1, start_link/0, handle_call/3, handle_cast/2, handle_info/2, - terminate/2, code_change/3, ssl_options/2]). - --include_lib("kernel/include/net_address.hrl"). - --record(state, - {listen, - accept_loop - }). - --define(PPRE, 4). --define(PPOST, 4). - - -%%==================================================================== -%% Internal application API -%%==================================================================== - -listen(Name) -> - gen_server:call(?MODULE, {listen, Name}, infinity). - -accept(Listen) -> - gen_server:call(?MODULE, {accept, Listen}, infinity). - -connect(Ip, Port) -> - gen_server:call(?MODULE, {connect, Ip, Port}, infinity). - -%%==================================================================== -%% gen_server callbacks -%%==================================================================== - -start_link() -> - gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). - -init([]) -> - process_flag(priority, max), - {ok, #state{}}. - -handle_call({listen, Name}, _From, State) -> - case gen_tcp:listen(0, [{active, false}, {packet,?PPRE}]) of - {ok, Socket} -> - {ok, World} = gen_tcp:listen(0, [{active, false}, binary, {packet,?PPRE}]), - {ok, TcpAddress} = get_tcp_address(Socket), - {ok, WorldTcpAddress} = get_tcp_address(World), - {_,Port} = WorldTcpAddress#net_address.address, - {ok, Creation} = erl_epmd:register_node(Name, Port), - {reply, {ok, {Socket, TcpAddress, Creation}}, - State#state{listen={Socket, World}}}; - Error -> - {reply, Error, State} - end; - -handle_call({accept, Listen}, {From, _}, State = #state{listen={_, World}}) -> - Self = self(), - ErtsPid = spawn_link(fun() -> accept_loop(Self, erts, Listen, From) end), - WorldPid = spawn_link(fun() -> accept_loop(Self, world, World, Listen) end), - {reply, ErtsPid, State#state{accept_loop={ErtsPid, WorldPid}}}; - -handle_call({connect, Ip, Port}, {From, _}, State) -> - Me = self(), - Pid = spawn_link(fun() -> setup_proxy(Ip, Port, Me) end), - receive - {Pid, go_ahead, LPort} -> - Res = {ok, Socket} = try_connect(LPort), - case gen_tcp:controlling_process(Socket, From) of - {error, badarg} = Error -> {reply, Error, State}; % From is dead anyway. - ok -> - flush_old_controller(From, Socket), - {reply, Res, State} - end; - {Pid, Error} -> - {reply, Error, State} - end; - -handle_call(_What, _From, State) -> - {reply, ok, State}. - -handle_cast(_What, State) -> - {noreply, State}. - -handle_info(_What, State) -> - {noreply, State}. - -terminate(_Reason, _St) -> - ok. - -code_change(_OldVsn, St, _Extra) -> - {ok, St}. - -%%-------------------------------------------------------------------- -%%% Internal functions -%%-------------------------------------------------------------------- -get_tcp_address(Socket) -> - case inet:sockname(Socket) of - {ok, Address} -> - {ok, Host} = inet:gethostname(), - NetAddress = #net_address{ - address = Address, - host = Host, - protocol = proxy, - family = inet - }, - {ok, NetAddress}; - {error, _} = Error -> Error - end. - -accept_loop(Proxy, erts = Type, Listen, Extra) -> - process_flag(priority, max), - case gen_tcp:accept(Listen) of - {ok, Socket} -> - Extra ! {accept,self(),Socket,inet,proxy}, - receive - {_Kernel, controller, Pid} -> - ok = gen_tcp:controlling_process(Socket, Pid), - flush_old_controller(Pid, Socket), - Pid ! {self(), controller}; - {_Kernel, unsupported_protocol} -> - exit(unsupported_protocol) - end; - Error -> - exit(Error) - end, - accept_loop(Proxy, Type, Listen, Extra); - -accept_loop(Proxy, world = Type, Listen, Extra) -> - process_flag(priority, max), - case gen_tcp:accept(Listen) of - {ok, Socket} -> - Opts = get_ssl_options(server), - case ssl:ssl_accept(Socket, Opts) of - {ok, SslSocket} -> - PairHandler = - spawn_link(fun() -> - setup_connection(SslSocket, Extra) - end), - ok = ssl:controlling_process(SslSocket, PairHandler), - flush_old_controller(PairHandler, SslSocket); - _ -> - gen_tcp:close(Socket) - end; - Error -> - exit(Error) - end, - accept_loop(Proxy, Type, Listen, Extra). - -try_connect(Port) -> - case gen_tcp:connect({127,0,0,1}, Port, [{active, false}, {packet,?PPRE}]) of - R = {ok, _S} -> - R; - {error, _R} -> - try_connect(Port) - end. - -setup_proxy(Ip, Port, Parent) -> - process_flag(trap_exit, true), - Opts = get_ssl_options(client), - case ssl:connect(Ip, Port, [{active, true}, binary, {packet,?PPRE}] ++ Opts) of - {ok, World} -> - {ok, ErtsL} = gen_tcp:listen(0, [{active, true}, {ip, {127,0,0,1}}, binary, {packet,?PPRE}]), - {ok, #net_address{address={_,LPort}}} = get_tcp_address(ErtsL), - Parent ! {self(), go_ahead, LPort}, - case gen_tcp:accept(ErtsL) of - {ok, Erts} -> - %% gen_tcp:close(ErtsL), - loop_conn_setup(World, Erts); - Err -> - Parent ! {self(), Err} - end; - Err -> - Parent ! {self(), Err} - end. - -setup_connection(World, ErtsListen) -> - process_flag(trap_exit, true), - {ok, TcpAddress} = get_tcp_address(ErtsListen), - {_Addr,Port} = TcpAddress#net_address.address, - {ok, Erts} = gen_tcp:connect({127,0,0,1}, Port, [{active, true}, binary, {packet,?PPRE}]), - ssl:setopts(World, [{active,true}, {packet,?PPRE}]), - loop_conn_setup(World, Erts). - -loop_conn_setup(World, Erts) -> - receive - {ssl, World, Data = <<$a, _/binary>>} -> - gen_tcp:send(Erts, Data), - ssl:setopts(World, [{packet,?PPOST}]), - inet:setopts(Erts, [{packet,?PPOST}]), - loop_conn(World, Erts); - {tcp, Erts, Data = <<$a, _/binary>>} -> - ssl:send(World, Data), - ssl:setopts(World, [{packet,?PPOST}]), - inet:setopts(Erts, [{packet,?PPOST}]), - loop_conn(World, Erts); - {ssl, World, Data = <<_, _/binary>>} -> - gen_tcp:send(Erts, Data), - loop_conn_setup(World, Erts); - {tcp, Erts, Data = <<_, _/binary>>} -> - ssl:send(World, Data), - loop_conn_setup(World, Erts); - {ssl, World, Data} -> - gen_tcp:send(Erts, Data), - loop_conn_setup(World, Erts); - {tcp, Erts, Data} -> - ssl:send(World, Data), - loop_conn_setup(World, Erts); - {tcp_closed, Erts} -> - ssl:close(World); - {ssl_closed, World} -> - gen_tcp:close(Erts) - end. - -loop_conn(World, Erts) -> - receive - {ssl, World, Data} -> - gen_tcp:send(Erts, Data), - loop_conn(World, Erts); - {tcp, Erts, Data} -> - ssl:send(World, Data), - loop_conn(World, Erts); - {tcp_closed, Erts} -> - ssl:close(World); - {ssl_closed, World} -> - gen_tcp:close(Erts) - end. - -get_ssl_options(Type) -> - case init:get_argument(ssl_dist_opt) of - {ok, Args} -> - [{erl_dist, true} | ssl_options(Type, lists:append(Args))]; - _ -> - [{erl_dist, true}] - end. - -ssl_options(_,[]) -> - []; -ssl_options(server, ["client_" ++ _, _Value |T]) -> - ssl_options(server,T); -ssl_options(client, ["server_" ++ _, _Value|T]) -> - ssl_options(client,T); -ssl_options(server, ["server_certfile", Value|T]) -> - [{certfile, Value} | ssl_options(server,T)]; -ssl_options(client, ["client_certfile", Value | T]) -> - [{certfile, Value} | ssl_options(client,T)]; -ssl_options(server, ["server_cacertfile", Value|T]) -> - [{cacertfile, Value} | ssl_options(server,T)]; -ssl_options(client, ["client_cacertfile", Value|T]) -> - [{cacertfile, Value} | ssl_options(client,T)]; -ssl_options(server, ["server_keyfile", Value|T]) -> - [{keyfile, Value} | ssl_options(server,T)]; -ssl_options(client, ["client_keyfile", Value|T]) -> - [{keyfile, Value} | ssl_options(client,T)]; -ssl_options(server, ["server_password", Value|T]) -> - [{password, Value} | ssl_options(server,T)]; -ssl_options(client, ["client_password", Value|T]) -> - [{password, Value} | ssl_options(client,T)]; -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_reuse_sessions", Value|T]) -> - [{reuse_sessions, atomize(Value)} | ssl_options(server,T)]; -ssl_options(client, ["client_reuse_sessions", Value|T]) -> - [{reuse_sessions, atomize(Value)} | ssl_options(client,T)]; -ssl_options(server, ["server_secure_renegotiate", Value|T]) -> - [{secure_renegotiate, atomize(Value)} | ssl_options(server,T)]; -ssl_options(client, ["client_secure_renegotiate", Value|T]) -> - [{secure_renegotiate, atomize(Value)} | ssl_options(client,T)]; -ssl_options(server, ["server_depth", Value|T]) -> - [{depth, list_to_integer(Value)} | ssl_options(server,T)]; -ssl_options(client, ["client_depth", Value|T]) -> - [{depth, list_to_integer(Value)} | ssl_options(client,T)]; -ssl_options(server, ["server_hibernate_after", Value|T]) -> - [{hibernate_after, list_to_integer(Value)} | ssl_options(server,T)]; -ssl_options(client, ["client_hibernate_after", Value|T]) -> - [{hibernate_after, list_to_integer(Value)} | ssl_options(client,T)]; -ssl_options(server, ["server_ciphers", Value|T]) -> - [{ciphers, Value} | ssl_options(server,T)]; -ssl_options(client, ["client_ciphers", Value|T]) -> - [{ciphers, Value} | ssl_options(client,T)]; -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). - -atomize(List) when is_list(List) -> - list_to_atom(List); -atomize(Atom) when is_atom(Atom) -> - Atom. - -flush_old_controller(Pid, Socket) -> - receive - {tcp, Socket, Data} -> - Pid ! {tcp, Socket, Data}, - flush_old_controller(Pid, Socket); - {tcp_closed, Socket} -> - Pid ! {tcp_closed, Socket}, - flush_old_controller(Pid, Socket); - {ssl, Socket, Data} -> - Pid ! {ssl, Socket, Data}, - flush_old_controller(Pid, Socket); - {ssl_closed, Socket} -> - Pid ! {ssl_closed, Socket}, - flush_old_controller(Pid, Socket) - after 0 -> - ok - end. diff --git a/lib/ssl/src/ssl_v2.erl b/lib/ssl/src/ssl_v2.erl deleted file mode 100644 index 07876366f1..0000000000 --- a/lib/ssl/src/ssl_v2.erl +++ /dev/null @@ -1,37 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2007-2013. All Rights Reserved. -%% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. -%% -%% %CopyrightEnd% -%% - -%% -%%---------------------------------------------------------------------- -%% Purpose: Handles sslv2 hello as clients supporting sslv2 and higher -%% will send an sslv2 hello. -%%---------------------------------------------------------------------- - --module(ssl_v2). - --export([client_random/2]). - -client_random(ChallengeData, 32) -> - ChallengeData; -client_random(ChallengeData, N) when N > 32 -> - <<NewChallengeData:32/binary, _/binary>> = ChallengeData, - NewChallengeData; -client_random(ChallengeData, N) -> - Pad = list_to_binary(lists:duplicate(N, 0)), - <<Pad/binary, ChallengeData/binary>>. diff --git a/lib/ssl/src/ssl_v3.erl b/lib/ssl/src/ssl_v3.erl index 68f7f5dee2..4eab60b440 100644 --- a/lib/ssl/src/ssl_v3.erl +++ b/lib/ssl/src/ssl_v3.erl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2014. All Rights Reserved. +%% Copyright Ericsson AB 2007-2018. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. +%% 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 %% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% 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% %% @@ -130,7 +131,7 @@ setup_keys(MasterSecret, ServerRandom, ClientRandom, HS, KML, _EKML, IVS) -> {ClientWriteMacSecret, ServerWriteMacSecret, ClientWriteKey, ServerWriteKey, ClientIV, ServerIV}. --spec suites() -> [ssl_cipher:cipher_suite()]. +-spec suites() -> [ssl_cipher_format:cipher_suite()]. suites() -> [ @@ -142,11 +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_RSA_WITH_IDEA_CBC_SHA, - ?TLS_RSA_WITH_RC4_128_SHA, - ?TLS_RSA_WITH_RC4_128_MD5, - ?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 deleted file mode 100644 index c829129250..0000000000 --- a/lib/ssl/src/tls.erl +++ /dev/null @@ -1,111 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 1999-2013. All Rights Reserved. -%% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. -%% -%% %CopyrightEnd% -%% - -%% - -%%% Purpose : Reflect TLS specific API options (fairly simple wrapper at the moment) - --module(tls). - --include("ssl_api.hrl"). --include("ssl_internal.hrl"). - --export([connect/2, connect/3, listen/2, accept/1, accept/2, - handshake/1, handshake/2, handshake/3]). - -%%-------------------------------------------------------------------- -%% -%% Description: Connect to an TLS server. -%%-------------------------------------------------------------------- - --spec connect(host() | port(), [connect_option()]) -> {ok, #sslsocket{}} | - {error, reason()}. - -connect(Socket, Options) when is_port(Socket) -> - connect(Socket, Options, infinity). - --spec connect(host() | port(), [connect_option()] | inet:port_number(), - timeout() | list()) -> - {ok, #sslsocket{}} | {error, reason()}. - -connect(Socket, SslOptions, Timeout) when is_port(Socket) -> - TLSOpts = [{protocol, tls} | SslOptions], - ssl:connect(Socket, TLSOpts, Timeout); -connect(Host, Port, Options) -> - connect(Host, Port, Options, infinity). - --spec connect(host() | port(), inet:port_number(), list(), timeout()) -> - {ok, #sslsocket{}} | {error, reason()}. - -connect(Host, Port, Options, Timeout) -> - TLSOpts = [{protocol, tls} | Options], - ssl:connect(Host, Port, TLSOpts, Timeout). - -%%-------------------------------------------------------------------- --spec listen(inet:port_number(), [listen_option()]) ->{ok, #sslsocket{}} | {error, reason()}. - -%% -%% Description: Creates an ssl listen socket. -%%-------------------------------------------------------------------- -listen(Port, Options) -> - TLSOpts = [{protocol, tls} | Options], - ssl:listen(Port, TLSOpts). - -%%-------------------------------------------------------------------- -%% -%% Description: Performs transport accept on an ssl listen socket -%%-------------------------------------------------------------------- --spec accept(#sslsocket{}) -> {ok, #sslsocket{}} | - {error, reason()}. -accept(ListenSocket) -> - accept(ListenSocket, infinity). - --spec accept(#sslsocket{}, timeout()) -> {ok, #sslsocket{}} | - {error, reason()}. -accept(Socket, Timeout) -> - ssl:transport_accept(Socket, Timeout). - -%%-------------------------------------------------------------------- -%% -%% Description: Performs accept on an ssl listen socket. e.i. performs -%% ssl handshake. -%%-------------------------------------------------------------------- - --spec handshake(#sslsocket{}) -> ok | {error, reason()}. - -handshake(ListenSocket) -> - handshake(ListenSocket, infinity). - - --spec handshake(#sslsocket{} | port(), timeout()| [ssl_option() - | transport_option()]) -> - ok | {ok, #sslsocket{}} | {error, reason()}. - -handshake(#sslsocket{} = Socket, Timeout) -> - ssl:ssl_accept(Socket, Timeout); - -handshake(ListenSocket, SslOptions) when is_port(ListenSocket) -> - handshake(ListenSocket, SslOptions, infinity). - - --spec handshake(port(), [ssl_option()| transport_option()], timeout()) -> - {ok, #sslsocket{}} | {error, reason()}. - -handshake(Socket, SslOptions, Timeout) when is_port(Socket) -> - ssl:ssl_accept(Socket, SslOptions, Timeout). diff --git a/lib/ssl/src/tls_connection.erl b/lib/ssl/src/tls_connection.erl index 887fb8fda1..009ba0600d 100644 --- a/lib/ssl/src/tls_connection.erl +++ b/lib/ssl/src/tls_connection.erl @@ -1,22 +1,22 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2015. All Rights Reserved. +%% Copyright Ericsson AB 2007-2018. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. +%% 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 %% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% 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% %% - %% %%---------------------------------------------------------------------- %% Purpose: Handles an ssl connection, e.i. both the setup @@ -27,7 +27,7 @@ -module(tls_connection). --behaviour(gen_fsm). +-behaviour(gen_statem). -include("tls_connection.hrl"). -include("tls_handshake.hrl"). @@ -42,44 +42,52 @@ %% Internal application API %% Setup --export([start_fsm/8]). +-export([start_fsm/8, start_link/8, init/1, pids/1]). %% State transition handling --export([next_record/1, next_state/4, next_state_connection/2]). +-export([next_event/3, next_event/4, + handle_protocol_record/3]). %% Handshake handling --export([renegotiate/1, send_handshake/2, send_change_cipher/2]). +-export([renegotiation/2, renegotiate/2, send_handshake/2, + queue_handshake/2, queue_change_cipher/2, + reinit/1, reinit_handshake_data/1, select_sni_extension/1, + empty_connection_state/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, - workaround_transport_delivery_problems/2, alert_user/6, alert_user/9 - ]). +-export([send_alert/2, send_alert_in_connection/2, + send_sync_alert/2, + encode_alert/3, close/5, protocol_name/0]). %% Data handling --export([write_application_data/3, read_application_data/2, - passive_receive/2, next_record_if_active/1]). +-export([encode_data/3, next_record/1, + send/3, socket/5, setopts/3, getopts/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, user_hello/3, certify/3, cipher/3, abbreviated/3, %% Handshake states + connection/3]). +%% gen_statem callbacks +-export([callback_mode/0, 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, format_status/2]). +-define(DIST_CNTRL_SPAWN_OPTS, [{priority, max}]). %%==================================================================== %% Internal application API %%==================================================================== +%%==================================================================== +%% Setup +%%==================================================================== start_fsm(Role, Host, Port, Socket, {#ssl_options{erl_dist = false},_, Tracker} = Opts, User, {CbModule, _,_, _} = CbInfo, Timeout) -> try - {ok, Pid} = tls_connection_sup:start_child([Role, Host, Port, Socket, + {ok, Sender} = tls_sender:start(), + {ok, Pid} = tls_connection_sup:start_child([Role, Sender, Host, Port, Socket, Opts, User, CbInfo]), - {ok, SslSocket} = ssl_connection:socket_control(?MODULE, Socket, Pid, CbModule, Tracker), - ok = ssl_connection:handshake(SslSocket, Timeout), - {ok, SslSocket} + {ok, SslSocket} = ssl_connection:socket_control(?MODULE, Socket, [Pid, Sender], CbModule, Tracker), + ssl_connection:handshake(SslSocket, Timeout) catch error:{badmatch, {error, _} = Error} -> Error @@ -89,274 +97,619 @@ start_fsm(Role, Host, Port, Socket, {#ssl_options{erl_dist = true},_, Tracker} = User, {CbModule, _,_, _} = CbInfo, Timeout) -> try - {ok, Pid} = tls_connection_sup:start_child_dist([Role, Host, Port, Socket, + {ok, Sender} = tls_sender:start([{spawn_opt, ?DIST_CNTRL_SPAWN_OPTS}]), + {ok, Pid} = tls_connection_sup:start_child_dist([Role, Sender, Host, Port, Socket, Opts, User, CbInfo]), - {ok, SslSocket} = ssl_connection:socket_control(?MODULE, Socket, Pid, CbModule, Tracker), - ok = ssl_connection:handshake(SslSocket, Timeout), - {ok, SslSocket} + {ok, SslSocket} = ssl_connection:socket_control(?MODULE, Socket, [Pid, Sender], CbModule, Tracker), + ssl_connection:handshake(SslSocket, Timeout) catch error:{badmatch, {error, _} = Error} -> Error end. -send_handshake(Handshake, #state{negotiated_version = Version, - socket = Socket, - transport_cb = Transport, - tls_handshake_history = Hist0, - connection_states = ConnectionStates0} = State0) -> +%%-------------------------------------------------------------------- +-spec start_link(atom(), pid(), host(), inet:port_number(), port(), list(), pid(), tuple()) -> + {ok, pid()} | ignore | {error, reason()}. +%% +%% Description: Creates a gen_statem process which calls Module:init/1 to +%% initialize. +%%-------------------------------------------------------------------- +start_link(Role, Sender, Host, Port, Socket, Options, User, CbInfo) -> + {ok, proc_lib:spawn_link(?MODULE, init, [[Role, Sender, Host, Port, Socket, Options, User, CbInfo]])}. + +init([Role, Sender, Host, Port, Socket, {SslOpts, _, _} = Options, User, CbInfo]) -> + process_flag(trap_exit, true), + link(Sender), + case SslOpts#ssl_options.erl_dist of + true -> + process_flag(priority, max); + _ -> + ok + end, + State0 = #state{protocol_specific = Map} = initial_state(Role, Sender, + Host, Port, Socket, Options, User, CbInfo), + try + State = ssl_connection:ssl_config(State0#state.ssl_options, Role, State0), + initialize_tls_sender(State), + gen_statem:enter_loop(?MODULE, [], init, State) + catch throw:Error -> + EState = State0#state{protocol_specific = Map#{error => Error}}, + gen_statem:enter_loop(?MODULE, [], error, EState) + end. + +pids(#state{protocol_specific = #{sender := Sender}}) -> + [self(), Sender]. + +%%==================================================================== +%% State transition handling +%%==================================================================== +next_record(#state{unprocessed_handshake_events = N} = State) when N > 0 -> + {no_record, State#state{unprocessed_handshake_events = N-1}}; + +next_record(#state{protocol_buffers = + #protocol_buffers{tls_packets = [], tls_cipher_texts = [CT | Rest]} + = Buffers, + connection_states = ConnStates0, + ssl_options = #ssl_options{padding_check = Check}} = State) -> + case tls_record:decode_cipher_text(CT, ConnStates0, Check) of + {Plain, ConnStates} -> + {Plain, State#state{protocol_buffers = + Buffers#protocol_buffers{tls_cipher_texts = Rest}, + connection_states = ConnStates}}; + #alert{} = Alert -> + {Alert, State} + end; +next_record(#state{protocol_buffers = #protocol_buffers{tls_packets = [], tls_cipher_texts = []}, + protocol_specific = #{active_n_toggle := true, active_n := N} = ProtocolSpec, + static_env = #static_env{socket = Socket, + close_tag = CloseTag, + transport_cb = Transport} + } = State) -> + case tls_socket:setopts(Transport, Socket, [{active, N}]) of + ok -> + {no_record, State#state{protocol_specific = ProtocolSpec#{active_n_toggle => false}}}; + _ -> + self() ! {CloseTag, Socket}, + {no_record, State} + end; +next_record(State) -> + {no_record, State}. + +next_event(StateName, Record, State) -> + next_event(StateName, Record, State, []). +next_event(StateName, no_record, State0, Actions) -> + case next_record(State0) of + {no_record, State} -> + {next_state, StateName, State, Actions}; + {#ssl_tls{} = Record, State} -> + {next_state, StateName, State, [{next_event, internal, {protocol_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, {protocol_record, Record}} | Actions]}; + #alert{} = Alert -> + {next_state, StateName, State, [{next_event, internal, Alert} | Actions]} + end. + +%%% TLS record protocol level application data messages + +handle_protocol_record(#ssl_tls{type = ?APPLICATION_DATA, fragment = Data}, StateName, State0) -> + case ssl_connection:read_application_data(Data, State0) of + {stop, _, _} = Stop-> + Stop; + {Record, State1} -> + {next_state, StateName, State, Actions} = next_event(StateName, Record, State1), + ssl_connection:hibernate_after(StateName, State, Actions) + end; +%%% TLS record protocol level handshake messages +handle_protocol_record(#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}}, + case Packets of + [] -> + assert_buffer_sanity(Buf, Options), + next_event(StateName, no_record, State); + _ -> + Events = tls_handshake_events(Packets), + case StateName of + connection -> + ssl_connection:hibernate_after(StateName, State, Events); + _ -> + {next_state, StateName, + State#state{unprocessed_handshake_events = unprocessed_events(Events)}, Events} + end + end + catch throw:#alert{} = Alert -> + ssl_connection:handle_own_alert(Alert, Version, StateName, State0) + end; +%%% TLS record protocol level change cipher messages +handle_protocol_record(#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_protocol_record(#ssl_tls{type = ?ALERT, fragment = EncAlerts}, StateName, + #state{negotiated_version = Version} = State) -> + try decode_alerts(EncAlerts) of + Alerts = [_|_] -> + handle_alerts(Alerts, {next_state, StateName, State}); + [] -> + ssl_connection:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, empty_alert), + Version, StateName, State); + #alert{} = Alert -> + ssl_connection:handle_own_alert(Alert, Version, StateName, State) + catch + _:_ -> + ssl_connection:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, alert_decode_error), + Version, StateName, State) + + end; +%% Ignore unknown TLS record level protocol messages +handle_protocol_record(#ssl_tls{type = _Unknown}, StateName, State) -> + {next_state, StateName, State, []}. +%%==================================================================== +%% Handshake handling +%%==================================================================== +renegotiation(Pid, WriteState) -> + gen_statem:call(Pid, {user_renegotiate, WriteState}). + +renegotiate(#state{static_env = #static_env{role = client}} = State, Actions) -> + %% Handle same way as if server requested + %% the renegotiation + Hs0 = ssl_handshake:init_handshake_history(), + {next_state, connection, State#state{tls_handshake_history = Hs0}, + [{next_event, internal, #hello_request{}} | Actions]}; +renegotiate(#state{static_env = #static_env{role = server, + socket = Socket, + transport_cb = Transport}, + negotiated_version = Version, + 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} = + tls_record:encode_handshake(Frag, Version, ConnectionStates0), + send(Transport, Socket, BinMsg), + State = State0#state{connection_states = + ConnectionStates, + tls_handshake_history = Hs0}, + next_event(hello, no_record, State, Actions). + +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 - }. - -send_alert(Alert, #state{negotiated_version = Version, - socket = Socket, - transport_cb = Transport, - connection_states = ConnectionStates0} = State0) -> - {BinMsg, ConnectionStates} = - ssl_alert:encode(Alert, Version, ConnectionStates0), - 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) -> + tls_handshake_history = Hist, + flight_buffer = Flight0 ++ [BinHandshake]}. + +send_handshake_flight(#state{static_env = #static_env{socket = Socket, + transport_cb = Transport}, + flight_buffer = Flight} = State0) -> + send(Transport, 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), - Transport:send(Socket, BinChangeCipher), - State0#state{connection_states = ConnectionStates}. + State0#state{connection_states = ConnectionStates, + flight_buffer = Flight0 ++ [BinChangeCipher]}. + +reinit(#state{protocol_specific = #{sender := Sender}, + negotiated_version = Version, + connection_states = #{current_write := Write}} = State) -> + tls_sender:update_connection_state(Sender, Write, Version), + reinit_handshake_data(State). + +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() + }. + +select_sni_extension(#client_hello{extensions = HelloExtensions}) -> + HelloExtensions#hello_extensions.sni; +select_sni_extension(_) -> + undefined. + +empty_connection_state(ConnectionEnd, BeastMitigation) -> + ssl_record:empty_connection_state(ConnectionEnd, BeastMitigation). %%==================================================================== -%% tls_connection_sup API -%%==================================================================== - +%% Alert and close handling +%%==================================================================== %%-------------------------------------------------------------------- --spec start_link(atom(), host(), inet:port_number(), port(), list(), pid(), tuple()) -> - {ok, pid()} | ignore | {error, reason()}. +-spec encode_alert(#alert{}, ssl_record:ssl_version(), ssl_record:connection_states()) -> + {iolist(), ssl_record:connection_states()}. %% -%% Description: Creates a gen_fsm process which calls Module:init/1 to -%% initialize. To ensure a synchronized start-up procedure, this function -%% does not return until Module:init/1 has returned. +%% Description: Encodes an alert %%-------------------------------------------------------------------- -start_link(Role, Host, Port, Socket, Options, User, CbInfo) -> - {ok, proc_lib:spawn_link(?MODULE, init, [[Role, Host, Port, Socket, Options, User, CbInfo]])}. +encode_alert(#alert{} = Alert, Version, ConnectionStates) -> + tls_record:encode_alert_record(Alert, Version, ConnectionStates). -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)). +send_alert(Alert, #state{negotiated_version = Version, + static_env = #static_env{socket = Socket, + transport_cb = Transport}, + connection_states = ConnectionStates0} = StateData0) -> + {BinMsg, ConnectionStates} = + encode_alert(Alert, Version, ConnectionStates0), + send(Transport, Socket, BinMsg), + StateData0#state{connection_states = ConnectionStates}. + +%% If an ALERT sent in the connection state, should cause the TLS +%% connection to end, we need to synchronize with the tls_sender +%% process so that the ALERT if possible (that is the tls_sender process is +%% not blocked) is sent before the connection process terminates and +%% thereby closes the transport socket. +send_alert_in_connection(#alert{level = ?FATAL} = Alert, State) -> + send_sync_alert(Alert, State); +send_alert_in_connection(#alert{description = ?CLOSE_NOTIFY} = Alert, State) -> + send_sync_alert(Alert, State); +send_alert_in_connection(Alert, + #state{protocol_specific = #{sender := Sender}}) -> + tls_sender:send_alert(Sender, Alert). +send_sync_alert( + Alert, #state{protocol_specific = #{sender := Sender}} = State) -> + try tls_sender:send_and_ack_alert(Sender, Alert) + catch + _:_ -> + throw({stop, {shutdown, own_alert}, State}) + end. + +%% User closes or recursive call! +close({close, Timeout}, Socket, Transport = gen_tcp, _,_) -> + tls_socket:setopts(Transport, Socket, [{active, false}]), + Transport:shutdown(Socket, write), + _ = Transport:recv(Socket, 0, Timeout), + ok; +%% Peer closed socket +close({shutdown, transport_closed}, Socket, Transport = gen_tcp, ConnectionStates, Check) -> + close({close, 0}, Socket, Transport, ConnectionStates, Check); +%% We generate fatal alert +close({shutdown, own_alert}, Socket, Transport = gen_tcp, ConnectionStates, Check) -> + %% Standard trick to try to make sure all + %% data sent to the tcp port is really delivered to the + %% peer application before tcp port is closed so that the peer will + %% get the correct TLS alert message and not only a transport close. + %% Will return when other side has closed or after timout millisec + %% e.g. we do not want to hang if something goes wrong + %% 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). +protocol_name() -> + "TLS". + +%%==================================================================== +%% Data handling +%%==================================================================== +encode_data(Data, Version, ConnectionStates0)-> + tls_record:encode_data(Data, Version, ConnectionStates0). + +send(Transport, Socket, Data) -> + tls_socket:send(Transport, Socket, Data). + +socket(Pids, Transport, Socket, Connection, Tracker) -> + tls_socket:socket(Pids, Transport, Socket, Connection, Tracker). + +setopts(Transport, Socket, Other) -> + tls_socket:setopts(Transport, Socket, Other). + +getopts(Transport, Socket, Tag) -> + tls_socket:getopts(Transport, Socket, Tag). %%-------------------------------------------------------------------- -%% 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{static_env = #static_env{role = client, + host = Host, + port = Port, + transport_cb = Transport, + socket = Socket, + session_cache = Cache, + session_cache_cb = CacheCb}, + ssl_options = SslOpts, + session = #session{own_certificate = Cert} = Session0, + connection_states = ConnectionStates0, + renegotiation = {Renegotiation, _} + } = State0) -> + Timer = ssl_connection:start_or_recv_cancel_timer(Timeout, From), Hello = tls_handshake:client_hello(Host, Port, ConnectionStates0, SslOpts, Cache, CacheCb, Renegotiation, Cert), Version = Hello#client_hello.client_version, + HelloVersion = tls_record:hello_version(Version, SslOpts#ssl_options.versions), Handshake0 = ssl_handshake:init_handshake_history(), {BinMsg, ConnectionStates, Handshake} = - encode_handshake(Hello, Version, 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}, - {Record, State} = next_record(State1), - next_state(hello, hello, Record, State); - -hello(Hello = #client_hello{client_version = ClientVersion, - extensions = #hello_extensions{hash_signs = HashSigns, - 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, - ssl_options = SslOpts}) -> + encode_handshake(Hello, HelloVersion, ConnectionStates0, Handshake0), + send(Transport, Socket, BinMsg), + State = State0#state{connection_states = ConnectionStates, + negotiated_version = Version, %% Requested version + session = + Session0#session{session_id = Hello#client_hello.session_id}, + tls_handshake_history = Handshake, + start_or_recv_from = From, + timer = Timer}, + next_event(hello, no_record, State); +init(Type, Event, State) -> + gen_handshake(?FUNCTION_NAME, Type, Event, State). + +%%-------------------------------------------------------------------- +-spec error(gen_statem:event_type(), + {start, timeout()} | term(), #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +error({call, From}, {start, _Timeout}, + #state{protocol_specific = #{error := Error}} = State) -> + {stop_and_reply, {shutdown, normal}, + [{reply, From, {error, Error}}], State}; + +error({call, _} = Call, Msg, State) -> + gen_handshake(?FUNCTION_NAME, Call, Msg, 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{extensions = Extensions} = Hello, + #state{ssl_options = #ssl_options{handshake = hello}, + start_or_recv_from = From} = State) -> + {next_state, user_hello, State#state{start_or_recv_from = undefined, + hello = Hello}, + [{reply, From, {ok, ssl_connection:map_extensions(Extensions)}}]}; +hello(internal, #server_hello{extensions = Extensions} = Hello, + #state{ssl_options = #ssl_options{handshake = hello}, + start_or_recv_from = From} = State) -> + {next_state, user_hello, State#state{start_or_recv_from = undefined, + hello = Hello}, + [{reply, From, {ok, ssl_connection:map_extensions(Extensions)}}]}; +hello(internal, #client_hello{client_version = ClientVersion} = Hello, + #state{connection_states = ConnectionStates0, + static_env = #static_env{ + port = Port, + session_cache = Cache, + session_cache_cb = CacheCb}, + session = #session{own_certificate = Cert} = Session0, + renegotiation = {Renegotiation, _}, + negotiated_protocol = CurrentProtocol, + key_algorithm = KeyExAlg, + ssl_options = SslOpts} = State) -> case tls_handshake:hello(Hello, SslOpts, {Port, Session0, Cache, CacheCb, - ConnectionStates0, Cert}, Renegotiation) of - {Version, {Type, Session}, - ConnectionStates, ServerHelloExt} -> - HashSign = ssl_handshake:select_hashsign(HashSigns, Cert, Version), - ssl_connection:hello({common_client_hello, Type, ServerHelloExt, HashSign}, - State#state{connection_states = ConnectionStates, - negotiated_version = Version, - session = Session, - client_ecc = {EllipticCurves, EcPointFormats}}, ?MODULE); + ConnectionStates0, Cert, KeyExAlg}, Renegotiation) of #alert{} = Alert -> - handle_own_alert(Alert, ClientVersion, hello, State) + ssl_connection:handle_own_alert(Alert, ClientVersion, hello, + State#state{negotiated_version + = ClientVersion}); + {Version, {Type, Session}, + ConnectionStates, Protocol0, ServerHelloExt, HashSign} -> + Protocol = case Protocol0 of + undefined -> CurrentProtocol; + _ -> Protocol0 + end, + gen_handshake(?FUNCTION_NAME, internal, {common_client_hello, Type, ServerHelloExt}, + State#state{connection_states = ConnectionStates, + negotiated_version = Version, + hashsign_algorithm = HashSign, + client_hello_version = ClientVersion, + session = Session, + negotiated_protocol = Protocol}) end; -hello(Hello = #server_hello{}, +hello(internal, #server_hello{} = Hello, #state{connection_states = ConnectionStates0, negotiated_version = ReqVersion, - role = client, + static_env = #static_env{role = client}, renegotiation = {Renegotiation, _}, ssl_options = SslOptions} = State) -> case tls_handshake:hello(Hello, SslOptions, ConnectionStates0, Renegotiation) of #alert{} = Alert -> - handle_own_alert(Alert, ReqVersion, hello, State); - {Version, NewId, ConnectionStates, NextProtocol} -> + ssl_connection:handle_own_alert(Alert, ReqVersion, hello, + State#state{negotiated_version = ReqVersion}); + {Version, NewId, ConnectionStates, ProtoExt, Protocol} -> ssl_connection:handle_session(Hello, - Version, NewId, ConnectionStates, NextProtocol, State) + Version, NewId, ConnectionStates, ProtoExt, Protocol, State) end; +hello(info, Event, State) -> + gen_info(Event, ?FUNCTION_NAME, State); +hello(Type, Event, State) -> + gen_handshake(?FUNCTION_NAME, Type, Event, State). -hello(Msg, State) -> - ssl_connection:hello(Msg, State, ?MODULE). +user_hello(Type, Event, State) -> + gen_handshake(?FUNCTION_NAME, Type, Event, State). -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) -> + gen_info(Event, ?FUNCTION_NAME, State); +abbreviated(Type, Event, State) -> + gen_handshake(?FUNCTION_NAME, Type, Event, State). -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) -> + gen_info(Event, ?FUNCTION_NAME, State); +certify(Type, Event, State) -> + gen_handshake(?FUNCTION_NAME, Type, Event, State). -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) -> + gen_info(Event, ?FUNCTION_NAME, State); +cipher(Type, Event, State) -> + gen_handshake(?FUNCTION_NAME, Type, Event, State). -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) -> - Hello = tls_handshake:client_hello(Host, Port, ConnectionStates0, SslOpts, +%%-------------------------------------------------------------------- +-spec connection(gen_statem:event_type(), + #hello_request{} | #client_hello{}| term(), #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +connection(info, Event, State) -> + gen_info(Event, ?FUNCTION_NAME, State); +connection({call, From}, {user_renegotiate, WriteState}, + #state{connection_states = ConnectionStates} = State) -> + {next_state, ?FUNCTION_NAME, State#state{connection_states = ConnectionStates#{current_write => WriteState}}, + [{next_event,{call, From}, renegotiate}]}; +connection({call, From}, + {close, {Pid, _Timeout}}, + #state{terminated = closed} = State) -> + {next_state, downgrade, State#state{terminated = true, downgrade = {Pid, From}}, + [{next_event, internal, ?ALERT_REC(?WARNING, ?CLOSE_NOTIFY)}]}; +connection({call, From}, + {close,{Pid, Timeout}}, + #state{connection_states = ConnectionStates, + protocol_specific = #{sender := Sender} + } = State0) -> + case tls_sender:downgrade(Sender, Timeout) of + {ok, Write} -> + %% 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. + State = send_alert(?ALERT_REC(?WARNING, ?CLOSE_NOTIFY), + State0#state{connection_states = + ConnectionStates#{current_write => Write}}), + {next_state, downgrade, State#state{downgrade = {Pid, From}, + terminated = true}, [{timeout, Timeout, downgrade}]}; + {error, timeout} -> + {stop_and_reply, {shutdown, downgrade_fail}, [{reply, From, {error, timeout}}]} + end; +connection(internal, #hello_request{}, + #state{static_env = #static_env{role = client, + host = Host, + port = Port, + session_cache = Cache, + session_cache_cb = CacheCb}, + renegotiation = {Renegotiation, _}, + session = #session{own_certificate = Cert} = Session0, + ssl_options = SslOpts, + connection_states = ConnectionStates} = State0) -> + Hello = tls_handshake:client_hello(Host, Port, ConnectionStates, SslOpts, Cache, CacheCb, Renegotiation, Cert), - 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); - -connection(#client_hello{} = Hello, #state{role = server, allow_renegotiate = true} = State) -> + {State, Actions} = send_handshake(Hello, State0), + next_event(hello, no_record, State#state{session = Session0#session{session_id + = Hello#client_hello.session_id}}, Actions); +connection(internal, #client_hello{} = Hello, + #state{static_env = #static_env{role = server}, + allow_renegotiate = true, + connection_states = CS, + protocol_specific = #{sender := Sender} + } = 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}); - -connection(#client_hello{}, #state{role = server, allow_renegotiate = false} = State0) -> + {ok, Write} = tls_sender:renegotiate(Sender), + next_event(hello, no_record, State#state{connection_states = CS#{current_write => Write}, + allow_renegotiate = false, + renegotiation = {true, peer} + }, + [{next_event, internal, Hello}]); +connection(internal, #client_hello{}, + #state{static_env = #static_env{role = server, + protocol_cb = Connection}, + 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)}. - -%%-------------------------------------------------------------------- -%% 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). + send_alert_in_connection(Alert, State0), + State = Connection:reinit_handshake_data(State0), + next_event(?FUNCTION_NAME, no_record, State); +connection(Type, Event, State) -> + ssl_connection:?FUNCTION_NAME(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). +-spec downgrade(gen_statem:event_type(), term(), #state{}) -> + gen_statem:state_function_result(). %%-------------------------------------------------------------------- - -%% 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); - #alert{} = Alert -> - handle_normal_shutdown(Alert, StateName, State0), - {stop, {shutdown, own_alert}, State0} - end; - -handle_info({CloseTag, Socket}, StateName, - #state{socket = Socket, close_tag = CloseTag, - negotiated_version = Version} = State) -> - %% Note that as of TLS 1.1, - %% failure to properly close a connection no longer requires that a - %% session not be resumed. This is a change from TLS 1.0 to conform - %% with widespread implementation practice. - case Version of - {1, N} when N >= 1 -> - ok; - _ -> - %% As invalidate_sessions here causes performance issues, - %% we will conform to the widespread implementation - %% practice and go aginst the spec - %%invalidate_session(Role, Host, Port, Session) - ok - end, - handle_normal_shutdown(?ALERT_REC(?FATAL, ?CLOSE_NOTIFY), StateName, State), - {stop, {shutdown, transport_closed}, State}; - -handle_info(Msg, StateName, State) -> - ssl_connection:handle_info(Msg, 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. +downgrade(internal, #alert{description = ?CLOSE_NOTIFY}, + #state{static_env = #static_env{transport_cb = Transport, + socket = Socket}, + downgrade = {Pid, From}} = State) -> + tls_socket:setopts(Transport, Socket, [{active, false}, {packet, 0}, {mode, binary}]), + Transport:controlling_process(Socket, Pid), + {stop_and_reply, {shutdown, downgrade},[{reply, From, {ok, Socket}}], State}; +downgrade(timeout, downgrade, #state{downgrade = {_, From}} = State) -> + {stop_and_reply, {shutdown, normal},[{reply, From, {error, timeout}}], State}; +downgrade(info, {CloseTag, Socket}, + #state{static_env = #static_env{socket = Socket, + close_tag = CloseTag}, downgrade = {_, From}} = + State) -> + {stop_and_reply, {shutdown, normal},[{reply, From, {error, CloseTag}}], State}; +downgrade(info, Info, State) -> + handle_info(Info, ?FUNCTION_NAME, State); +downgrade(Type, Event, State) -> + ssl_connection:?FUNCTION_NAME(Type, Event, State, ?MODULE). +%-------------------------------------------------------------------- +%% gen_statem callbacks %%-------------------------------------------------------------------- +callback_mode() -> + state_functions. + +terminate({shutdown, sender_died, Reason}, _StateName, + #state{static_env = #static_env{socket = Socket, + transport_cb = Transport}} + = State) -> + ssl_connection:handle_trusted_certs_db(State), + close(Reason, Socket, Transport, undefined, undefined); terminate(Reason, StateName, State) -> - catch ssl_connection:terminate(Reason, StateName, State). - -%%-------------------------------------------------------------------- -%% 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}; -code_change(_OldVsn, StateName, State, _) -> - {ok, StateName, State}. + catch ssl_connection:terminate(Reason, StateName, State), + ensure_sender_terminate(Reason, State). format_status(Type, Data) -> ssl_connection:format_status(Type, Data). +code_change(_OldVsn, StateName, State, _) -> + {ok, StateName, State}. + %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- -encode_handshake(Handshake, Version, ConnectionStates0, Hist0) -> - Frag = tls_handshake:encode_handshake(Handshake, Version), - Hist = ssl_handshake:update_handshake_history(Hist0, Frag), - {Encoded, ConnectionStates} = - ssl_record:encode_handshake(Frag, Version, ConnectionStates0), - {Encoded, ConnectionStates, Hist}. - -encode_change_cipher(#change_cipher_spec{}, Version, ConnectionStates) -> - ssl_record:encode_change_cipher_spec(Version, ConnectionStates). - -decode_alerts(Bin) -> - ssl_alert:decode(Bin). - -initial_state(Role, Host, Port, Socket, {SSLOptions, SocketOptions, Tracker}, User, +initial_state(Role, Sender, Host, Port, Socket, {SSLOptions, SocketOptions, Tracker}, User, {CbModule, DataTag, CloseTag, ErrorTag}) -> - ConnectionStates = ssl_record:init_connection_states(Role), + #ssl_options{beast_mitigation = BeastMitigation, + erl_dist = IsErlDist} = SSLOptions, + ConnectionStates = tls_record:init_connection_states(Role, BeastMitigation), SessionCacheCb = case application:get_env(ssl, session_cb) of {ok, Cb} when is_atom(Cb) -> @@ -364,612 +717,251 @@ initial_state(Role, Host, Port, Socket, {SSLOptions, SocketOptions, Tracker}, Us _ -> ssl_session_cache end, + InternalActiveN = case application:get_env(ssl, internal_active_n) of + {ok, N} when is_integer(N) andalso (not IsErlDist) -> + N; + _ -> + ?INTERNAL_ACTIVE_N + end, + UserMonitor = erlang:monitor(process, User), + InitStatEnv = #static_env{ + role = Role, + transport_cb = CbModule, + protocol_cb = ?MODULE, + data_tag = DataTag, + close_tag = CloseTag, + error_tag = ErrorTag, + host = Host, + port = Port, + socket = Socket, + session_cache_cb = SessionCacheCb, + tracker = Tracker + }, + #state{ + static_env = InitStatEnv, + socket_options = SocketOptions, + ssl_options = SSLOptions, + session = #session{is_resumable = new}, + connection_states = ConnectionStates, + protocol_buffers = #protocol_buffers{}, + user_application = {UserMonitor, User}, + user_data_buffer = <<>>, + renegotiation = {false, first}, + allow_renegotiate = SSLOptions#ssl_options.client_renegotiation, + start_or_recv_from = undefined, + flight_buffer = [], + protocol_specific = #{sender => Sender, + active_n => InternalActiveN, + active_n_toggle => true + } + }. + +initialize_tls_sender(#state{static_env = #static_env{ + role = Role, + transport_cb = Transport, + protocol_cb = Connection, + socket = Socket, + tracker = Tracker + }, + socket_options = SockOpts, + negotiated_version = Version, + ssl_options = #ssl_options{renegotiate_at = RenegotiateAt}, + connection_states = #{current_write := ConnectionWriteState}, + protocol_specific = #{sender := Sender}}) -> + Init = #{current_write => ConnectionWriteState, + role => Role, + socket => Socket, + socket_options => SockOpts, + tracker => Tracker, + protocol_cb => Connection, + transport_cb => Transport, + negotiated_version => Version, + renegotiate_at => RenegotiateAt}, + tls_sender:initialize(Sender, Init). - Monitor = erlang:monitor(process, User), - - #state{socket_options = SocketOptions, - ssl_options = SSLOptions, - session = #session{is_resumable = new}, - transport_cb = CbModule, - data_tag = DataTag, - close_tag = CloseTag, - error_tag = ErrorTag, - role = Role, - host = Host, - port = Port, - socket = Socket, - connection_states = ConnectionStates, - protocol_buffers = #protocol_buffers{}, - user_application = {Monitor, User}, - user_data_buffer = <<>>, - session_cache_cb = SessionCacheCb, - renegotiation = {false, first}, - start_or_recv_from = undefined, - send_queue = queue:new(), - protocol_cb = ?MODULE, - tracker = Tracker - }. - -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, 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{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 +next_tls_record(Data, StateName, #state{protocol_buffers = + #protocol_buffers{tls_record_buffer = Buf0, + tls_cipher_texts = CT0} = Buffers} + = State0) -> + case tls_record:get_tls_records(Data, + acceptable_record_versions(StateName, State0), + Buf0) of {Records, Buf1} -> CT1 = CT0 ++ Records, next_record(State0#state{protocol_buffers = Buffers#protocol_buffers{tls_record_buffer = Buf1, tls_cipher_texts = CT1}}); #alert{} = Alert -> - Alert + handle_record_alert(Alert, State0) 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, - connection_states = ConnStates0, - ssl_options = #ssl_options{padding_check = Check}} = State) -> - case tls_record:decode_cipher_text(CT, ConnStates0, Check) of - {Plain, ConnStates} -> - {Plain, State#state{protocol_buffers = - Buffers#protocol_buffers{tls_cipher_texts = Rest}, - connection_states = ConnStates}}; - #alert{} = Alert -> - {Alert, State} - end; -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). - -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. +acceptable_record_versions(StateName, #state{negotiated_version = Version}) when StateName =/= hello-> + Version; +acceptable_record_versions(hello, _) -> + [tls_record:protocol_version(Vsn) || Vsn <- ?ALL_AVAILABLE_VERSIONS]. -%% 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); - _ -> - case read_application_data(<<>>, State0) of - Stop = {stop, _, _} -> - Stop; - {Record, State} -> - next_state(StateName, StateName, Record, State) - end - end. +handle_record_alert(Alert, _) -> + Alert. -read_application_data(Data, #state{user_application = {_Mon, Pid}, - socket = Socket, - transport_cb = Transport, - socket_options = SOpts, - bytes_to_read = BytesToRead, - start_or_recv_from = RecvFrom, - timer = Timer, - user_data_buffer = Buffer0, - tracker = Tracker} = State0) -> - Buffer1 = if - Buffer0 =:= <<>> -> Data; - Data =:= <<>> -> Buffer0; - true -> <<Buffer0/binary, Data/binary>> - end, - case get_data(SOpts, BytesToRead, Buffer1) of - {ok, ClientData, Buffer} -> % Send data - SocketOpt = deliver_app_data(Transport, Socket, SOpts, ClientData, Pid, RecvFrom, Tracker), - cancel_timer(Timer), - State = State0#state{user_data_buffer = Buffer, - start_or_recv_from = undefined, - timer = undefined, - bytes_to_read = undefined, - socket_options = SocketOpt - }, - if - SocketOpt#socket_options.active =:= false; Buffer =:= <<>> -> - %% Passive mode, wait for active once or recv - %% Active and empty, get more data - next_record_if_active(State); - true -> %% We have more data - read_application_data(<<>>, State) - end; - {more, Buffer} -> % no reply, we need more data - next_record(State0#state{user_data_buffer = Buffer}); - {passive, Buffer} -> - next_record_if_active(State0#state{user_data_buffer = Buffer}); - {error,_Reason} -> %% Invalid packet in packet mode - deliver_packet_error(Transport, Socket, SOpts, Buffer1, Pid, RecvFrom, Tracker), - {stop, normal, State0} - end. +tls_handshake_events(Packets) -> + lists:map(fun(Packet) -> + {next_event, internal, {handshake, Packet}} + end, Packets). -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, <<>>}; -%% Recv timed out save buffer data until next recv -get_data(#socket_options{active=false}, undefined, Buffer) -> - {passive, Buffer}; -get_data(#socket_options{active=Active, packet=Raw}, BytesToRead, Buffer) - when Raw =:= raw; Raw =:= 0 -> %% Raw Mode - if - Active =/= false orelse BytesToRead =:= 0 -> - %% Active true or once, or passive mode recv(0) - {ok, Buffer, <<>>}; - byte_size(Buffer) >= BytesToRead -> - %% Passive Mode, recv(Bytes) - <<Data:BytesToRead/binary, Rest/binary>> = Buffer, - {ok, Data, Rest}; - true -> - %% Passive Mode not enough data - {more, Buffer} +%% raw data from socket, upack records +handle_info({Protocol, _, Data}, StateName, + #state{static_env = #static_env{data_tag = Protocol}} = State0) -> + case next_tls_record(Data, StateName, State0) of + {Record, State} -> + next_event(StateName, Record, State); + #alert{} = Alert -> + ssl_connection:handle_normal_shutdown(Alert, StateName, State0), + {stop, {shutdown, own_alert}, State0} end; -get_data(#socket_options{packet=Type, packet_size=Size}, _, Buffer) -> - PacketOpts = [{packet_size, Size}], - case decode_packet(Type, Buffer, PacketOpts) of - {more, _} -> - {more, Buffer}; - Decoded -> - Decoded - end. +handle_info({tcp_passive, Socket}, StateName, + #state{static_env = #static_env{socket = Socket}, + protocol_specific = PS + } = State) -> + next_event(StateName, no_record, + State#state{protocol_specific = PS#{active_n_toggle => true}}); +handle_info({CloseTag, Socket}, StateName, + #state{static_env = #static_env{socket = Socket, close_tag = CloseTag}, + socket_options = #socket_options{active = Active}, + protocol_buffers = #protocol_buffers{tls_cipher_texts = CTs}, + user_data_buffer = Buffer, + protocol_specific = PS, + negotiated_version = Version} = State) -> -decode_packet({http, headers}, Buffer, PacketOpts) -> - decode_packet(httph, Buffer, PacketOpts); -decode_packet({http_bin, headers}, Buffer, PacketOpts) -> - decode_packet(httph_bin, Buffer, PacketOpts); -decode_packet(Type, Buffer, PacketOpts) -> - erlang:decode_packet(Type, Buffer, PacketOpts). - -%% Just like with gen_tcp sockets, an ssl socket that has been configured with -%% {packet, http} (or {packet, http_bin}) will automatically switch to expect -%% HTTP headers after it sees a HTTP Request or HTTP Response line. We -%% represent the current state as follows: -%% #socket_options.packet =:= http: Expect a HTTP Request/Response line -%% #socket_options.packet =:= {http, headers}: Expect HTTP Headers -%% Note that if the user has explicitly configured the socket to expect -%% HTTP headers using the {packet, httph} option, we don't do any automatic -%% switching of states. -deliver_app_data(Transport, Socket, SOpts = #socket_options{active=Active, packet=Type}, - Data, Pid, From, Tracker) -> - send_or_reply(Active, Pid, From, format_reply(Transport, Socket, SOpts, Data, Tracker)), - SO = case Data of - {P, _, _, _} when ((P =:= http_request) or (P =:= http_response)), - ((Type =:= http) or (Type =:= http_bin)) -> - SOpts#socket_options{packet={Type, headers}}; - http_eoh when tuple_size(Type) =:= 2 -> - % End of headers - expect another Request/Response line - {Type1, headers} = Type, - SOpts#socket_options{packet=Type1}; - _ -> - SOpts - end, - case Active of - once -> - SO#socket_options{active=false}; - _ -> - SO - end. + %% Note that as of TLS 1.1, + %% failure to properly close a connection no longer requires that a + %% session not be resumed. This is a change from TLS 1.0 to conform + %% with widespread implementation practice. -format_reply(_, _,#socket_options{active = false, mode = Mode, packet = Packet, - header = Header}, Data, _) -> - {ok, do_format_reply(Mode, Packet, Header, Data)}; -format_reply(Transport, Socket, #socket_options{active = _, mode = Mode, packet = Packet, - header = Header}, Data, Tracker) -> - {ssl, ssl_socket:socket(self(), Transport, Socket, ?MODULE, Tracker), - do_format_reply(Mode, Packet, Header, Data)}. - -deliver_packet_error(Transport, Socket, SO= #socket_options{active = Active}, Data, Pid, From, Tracker) -> - send_or_reply(Active, Pid, From, format_packet_error(Transport, Socket, SO, Data, Tracker)). - -format_packet_error(_, _,#socket_options{active = false, mode = Mode}, Data, _) -> - {error, {invalid_packet, do_format_reply(Mode, raw, 0, Data)}}; -format_packet_error(Transport, Socket, #socket_options{active = _, mode = Mode}, Data, Tracker) -> - {ssl_error, ssl_socket:socket(self(), Transport, Socket, ?MODULE, Tracker), - {invalid_packet, do_format_reply(Mode, raw, 0, Data)}}. - -do_format_reply(binary, _, N, Data) when N > 0 -> % Header mode - header(N, Data); -do_format_reply(binary, _, _, Data) -> - Data; -do_format_reply(list, Packet, _, Data) - when Packet == http; Packet == {http, headers}; - Packet == http_bin; Packet == {http_bin, headers}; - Packet == httph; Packet == httph_bin -> - Data; -do_format_reply(list, _,_, Data) -> - binary_to_list(Data). - -header(0, <<>>) -> - <<>>; -header(_, <<>>) -> - []; -header(0, Binary) -> - Binary; -header(N, Binary) -> - <<?BYTE(ByteN), NewBinary/binary>> = Binary, - [ByteN | header(N-1, NewBinary)]. - -send_or_reply(false, _Pid, From, Data) when From =/= undefined -> - gen_fsm:reply(From, Data); -%% Can happen when handling own alert or tcp error/close and there is -%% no outstanding gen_fsm sync events -send_or_reply(false, no_pid, _, _) -> - ok; -send_or_reply(_, Pid, _From, Data) -> - send_user(Pid, Data). - -send_user(Pid, Msg) -> - Pid ! Msg. - -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); - {stop, _,_} = Stop -> - Stop + case (Active == false) andalso ((CTs =/= []) or (Buffer =/= <<>>)) of + false -> + case Version of + {1, N} when N >= 1 -> + ok; + _ -> + %% As invalidate_sessions here causes performance issues, + %% we will conform to the widespread implementation + %% practice and go aginst the spec + %%invalidate_session(Role, Host, Port, Session) + ok + end, + + ssl_connection:handle_normal_shutdown(?ALERT_REC(?FATAL, ?CLOSE_NOTIFY), StateName, State), + {stop, {shutdown, transport_closed}, State}; + true -> + %% Fixes non-delivery of final TLS record in {active, once}. + %% Basically allows the application the opportunity to set {active, once} again + %% and then receive the final message. Set internal active_n to zero + %% to ensure socket close message is sent if there is not enough data to deliver. + next_event(StateName, no_record, State#state{protocol_specific = PS#{active_n_toggle => true}}) end; - -handle_tls_handshake(_Handle, _StateName, #state{}) -> - throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE)). - -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}}); - 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)} - end. - -encode_packet(Data, #socket_options{packet=Packet}) -> - case Packet of - 1 -> encode_size_packet(Data, 8, (1 bsl 8) - 1); - 2 -> encode_size_packet(Data, 16, (1 bsl 16) - 1); - 4 -> encode_size_packet(Data, 32, (1 bsl 32) - 1); - _ -> Data - end. - -encode_size_packet(Bin, Size, Max) -> - Len = erlang:byte_size(Bin), - case Len > Max of - true -> throw({error, {badarg, {packet_to_large, Len, Max}}}); - false -> <<Len:Size, Bin/binary>> - end. - -time_to_renegotiate(_Data, - #connection_states{current_write = - #connection_state{sequence_number = Num}}, - RenegotiateAt) -> - - %% We could do test: - %% is_time_to_renegotiate((erlang:byte_size(_Data) div ?MAX_PLAIN_TEXT_LENGTH) + 1, RenegotiateAt), - %% but we chose to have a some what lower renegotiateAt and a much cheaper test - is_time_to_renegotiate(Num, RenegotiateAt). - -is_time_to_renegotiate(N, M) when N < M-> - false; -is_time_to_renegotiate(_,_) -> - true. -renegotiate(#state{role = client} = State) -> - %% Handle same way as if server requested - %% the renegotiation - Hs0 = ssl_handshake:init_handshake_history(), - connection(#hello_request{}, State#state{tls_handshake_history = Hs0}); -renegotiate(#state{role = server, - socket = Socket, - transport_cb = Transport, - negotiated_version = Version, - connection_states = ConnectionStates0} = State0) -> - 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}). +handle_info({'EXIT', Sender, Reason}, _, + #state{protocol_specific = #{sender := Sender}} = State) -> + {stop, {shutdown, sender_died, Reason}, State}; +handle_info(Msg, StateName, State) -> + ssl_connection:StateName(info, Msg, State, ?MODULE). handle_alerts([], Result) -> Result; handle_alerts(_, {stop, _, _} = Stop) -> - %% If it is a fatal alert immediately close Stop; -handle_alerts([Alert | Alerts], {next_state, StateName, State, _Timeout}) -> - 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) -> - 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}; - -handle_alert(#alert{level = ?WARNING, description = ?CLOSE_NOTIFY} = Alert, - StateName, State) -> - handle_normal_shutdown(Alert, StateName, State), - {stop, {shutdown, peer_close}, State}; - -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}; - -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}), - {Record, State} = next_record(State0), - next_state(StateName, 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). - -alert_user(Transport, Tracker, Socket, connection, Opts, Pid, From, Alert, Role) -> - alert_user(Transport, Tracker, Socket, Opts#socket_options.active, Pid, From, Alert, Role); -alert_user(Transport, Tracker, Socket,_, _, _, From, Alert, Role) -> - alert_user(Transport, Tracker, Socket, From, Alert, Role). - -alert_user(Transport, Tracker, Socket, From, Alert, Role) -> - alert_user(Transport, Tracker, Socket, false, no_pid, From, Alert, Role). - -alert_user(_, _, _, false = Active, Pid, From, Alert, Role) -> - %% If there is an outstanding ssl_accept | recv - %% From will be defined and send_or_reply will - %% send the appropriate error message. - ReasonCode = ssl_alert:reason_code(Alert, Role), - send_or_reply(Active, Pid, From, {error, ReasonCode}); -alert_user(Transport, Tracker, Socket, Active, Pid, From, Alert, Role) -> - case ssl_alert:reason_code(Alert, Role) of - closed -> - send_or_reply(Active, Pid, From, - {ssl_closed, ssl_socket:socket(self(), - Transport, Socket, ?MODULE, Tracker)}); - ReasonCode -> - send_or_reply(Active, Pid, From, - {ssl_error, ssl_socket:socket(self(), - Transport, Socket, ?MODULE, Tracker), ReasonCode}) - end. +handle_alerts([Alert | Alerts], {next_state, StateName, State}) -> + handle_alerts(Alerts, ssl_connection:handle_alert(Alert, StateName, State)); +handle_alerts([Alert | Alerts], {next_state, StateName, State, _Actions}) -> + handle_alerts(Alerts, ssl_connection:handle_alert(Alert, StateName, State)). -log_alert(true, Info, Alert) -> - Txt = ssl_alert:alert_txt(Alert), - error_logger:format("SSL: ~p: ~s\n", [Info, Txt]); -log_alert(false, _, _) -> - ok. - -handle_own_alert(Alert, Version, StateName, - #state{transport_cb = Transport, - socket = Socket, - connection_states = ConnectionStates, - ssl_options = SslOpts} = State) -> - try %% Try to tell the other side - {BinMsg, _} = - ssl_alert:encode(Alert, Version, ConnectionStates), - Transport:send(Socket, BinMsg), - workaround_transport_delivery_problems(Socket, Transport) - catch _:_ -> %% Can crash if we are in a uninitialized state - ignore - end, - try %% Try to tell the local user - log_alert(SslOpts#ssl_options.log_alert, StateName, Alert), - handle_normal_shutdown(Alert,StateName, State) - catch _:_ -> - ok - end, - {stop, {shutdown, own_alert}, State}. - -handle_normal_shutdown(Alert, _, #state{socket = Socket, - transport_cb = Transport, - start_or_recv_from = StartFrom, - tracker = Tracker, - role = Role, renegotiation = {false, first}}) -> - alert_user(Transport, Tracker,Socket, StartFrom, Alert, Role); - -handle_normal_shutdown(Alert, StateName, #state{socket = Socket, - socket_options = Opts, - transport_cb = Transport, - user_application = {_Mon, Pid}, - tracker = Tracker, - start_or_recv_from = RecvFrom, role = Role}) -> - alert_user(Transport, Tracker, Socket, StateName, Opts, Pid, RecvFrom, Alert, Role). - -handle_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} -> - [Alert|_] = decode_alerts(EncAlerts), - handle_normal_shutdown(Alert, StateName, State); - _ -> - ok - end. +encode_handshake(Handshake, Version, ConnectionStates0, Hist0) -> + Frag = tls_handshake:encode_handshake(Handshake, Version), + Hist = ssl_handshake:update_handshake_history(Hist0, Frag), + {Encoded, ConnectionStates} = + tls_record:encode_handshake(Frag, Version, ConnectionStates0), + {Encoded, ConnectionStates, Hist}. -cancel_timer(undefined) -> - ok; -cancel_timer(Timer) -> - erlang:cancel_timer(Timer), - ok. +encode_change_cipher(#change_cipher_spec{}, Version, ConnectionStates) -> + tls_record:encode_change_cipher_spec(Version, ConnectionStates). -invalidate_session(client, Host, Port, Session) -> - ssl_manager:invalidate_session(Host, Port, Session); -invalidate_session(server, _, Port, Session) -> - ssl_manager:invalidate_session(Port, Session). +decode_alerts(Bin) -> + ssl_alert:decode(Bin). -workaround_transport_delivery_problems(Socket, gen_tcp = Transport) -> - %% Standard trick to try to make sure all - %% data sent to the tcp port is really delivered to the - %% peer application before tcp port is closed so that the peer will - %% get the correct TLS alert message and not only a transport close. - ssl_socket:setopts(Transport, Socket, [{active, false}]), - Transport:shutdown(Socket, write), - %% Will return when other side has closed or after 30 s - %% e.g. we do not want to hang if something goes wrong - %% with the network but we want to maximise the odds that - %% peer application gets all data sent on the tcp connection. - Transport:recv(Socket, 0, 30000); -workaround_transport_delivery_problems(Socket, Transport) -> - Transport:close(Socket). +gen_handshake(StateName, Type, Event, + #state{negotiated_version = Version} = State) -> + try ssl_connection:StateName(Type, Event, State, ?MODULE) of + Result -> + Result + catch + _:_ -> + ssl_connection:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, + malformed_handshake_data), + Version, StateName, State) + end. -convert_state(#state{ssl_options = Options} = State, up, "5.3.5", "5.3.6") -> - State#state{ssl_options = convert_options_partial_chain(Options, up)}; -convert_state(#state{ssl_options = Options} = State, down, "5.3.6", "5.3.5") -> - State#state{ssl_options = convert_options_partial_chain(Options, down)}. +gen_info(Event, connection = StateName, #state{negotiated_version = Version} = State) -> + try handle_info(Event, StateName, State) of + Result -> + Result + catch + _:_ -> + ssl_connection:handle_own_alert(?ALERT_REC(?FATAL, ?INTERNAL_ERROR, + malformed_data), + Version, StateName, State) + end; -convert_options_partial_chain(Options, up) -> - {Head, Tail} = lists:split(5, tuple_to_list(Options)), - list_to_tuple(Head ++ [{partial_chain, fun(_) -> unknown_ca end}] ++ Tail); -convert_options_partial_chain(Options, down) -> - list_to_tuple(proplists:delete(partial_chain, tuple_to_list(Options))). +gen_info(Event, StateName, #state{negotiated_version = Version} = State) -> + try handle_info(Event, StateName, State) of + Result -> + Result + catch + _:_ -> + ssl_connection:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, + malformed_handshake_data), + Version, StateName, State) + end. + +unprocessed_events(Events) -> + %% The first handshake event will be processed immediately + %% as it is entered first in the event queue and + %% when it is processed there will be length(Events)-1 + %% handshake events left to process before we should + %% process more TLS-records received on the socket. + erlang:length(Events)-1. + + +assert_buffer_sanity(<<?BYTE(_Type), ?UINT24(Length), Rest/binary>>, + #ssl_options{max_handshake_size = Max}) when + Length =< Max -> + case size(Rest) of + N when N < Length -> + true; + N when N > Length -> + throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, + too_big_handshake_data)); + _ -> + throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, + malformed_handshake_data)) + end; +assert_buffer_sanity(Bin, _) -> + case size(Bin) of + N when N < 3 -> + true; + _ -> + throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, + malformed_handshake_data)) + end. + +ensure_sender_terminate(downgrade, _) -> + ok; %% Do not terminate sender during downgrade phase +ensure_sender_terminate(_, #state{protocol_specific = #{sender := Sender}}) -> + %% Make sure TLS sender dies when connection process is terminated normally + %% This is needed if the tls_sender is blocked in prim_inet:send + Kill = fun() -> + receive + after 5000 -> + catch (exit(Sender, kill)) + end + end, + spawn(Kill). diff --git a/lib/ssl/src/tls_connection.hrl b/lib/ssl/src/tls_connection.hrl index 2beecbb84d..0af2258932 100644 --- a/lib/ssl/src/tls_connection.hrl +++ b/lib/ssl/src/tls_connection.hrl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2013-2013. All Rights Reserved. -%% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% 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. +%% 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% %% diff --git a/lib/ssl/src/tls_connection_sup.erl b/lib/ssl/src/tls_connection_sup.erl index 7a637c212a..d5b228dc94 100644 --- a/lib/ssl/src/tls_connection_sup.erl +++ b/lib/ssl/src/tls_connection_sup.erl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2014. All Rights Reserved. +%% Copyright Ericsson AB 2007-2016. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% 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% %% diff --git a/lib/ssl/src/tls_handshake.erl b/lib/ssl/src/tls_handshake.erl index b0b6d5a8e3..19a5eb0348 100644 --- a/lib/ssl/src/tls_handshake.erl +++ b/lib/ssl/src/tls_handshake.erl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2015. All Rights Reserved. +%% Copyright Ericsson AB 2007-2018. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. +%% 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 %% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% 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% %% @@ -31,16 +32,22 @@ -include("ssl_cipher.hrl"). -include_lib("public_key/include/public_key.hrl"). --export([client_hello/8, hello/4, - get_tls_handshake/3, encode_handshake/2, decode_handshake/3]). +%% Handshake handling +-export([client_hello/8, hello/4]). + +%% Handshake encoding +-export([encode_handshake/2]). + +%% Handshake decodeing +-export([get_tls_handshake/4, decode_handshake/3]). -type tls_handshake() :: #client_hello{} | ssl_handshake:ssl_handshake(). %%==================================================================== -%% Internal application API +%% Handshake handling %%==================================================================== %%-------------------------------------------------------------------- --spec client_hello(host(), inet:port_number(), #connection_states{}, +-spec client_hello(host(), inet:port_number(), ssl_record:connection_states(), #ssl_options{}, integer(), atom(), boolean(), der_cert()) -> #client_hello{}. %% @@ -53,19 +60,14 @@ client_hello(Host, Port, ConnectionStates, } = SslOpts, Cache, CacheCb, Renegotiation, OwnCert) -> Version = tls_record:highest_protocol_version(Versions), - Pending = ssl_record:pending_connection_state(ConnectionStates, read), - SecParams = Pending#connection_state.security_parameters, - AvailableCipherSuites = ssl_handshake:available_suites(UserSuites, Version), - Extensions = ssl_handshake:client_hello_extensions(Host, Version, + #{security_parameters := SecParams} = + ssl_record:pending_connection_state(ConnectionStates, read), + AvailableCipherSuites = ssl_handshake:available_suites(UserSuites, Version), + Extensions = ssl_handshake:client_hello_extensions(Version, AvailableCipherSuites, - SslOpts, ConnectionStates, Renegotiation), - CipherSuites = - case Fallback of - true -> - [?TLS_FALLBACK_SCSV | ssl_handshake:cipher_suites(AvailableCipherSuites, Renegotiation)]; - false -> - ssl_handshake:cipher_suites(AvailableCipherSuites, Renegotiation) - end, + SslOpts, ConnectionStates, + Renegotiation), + CipherSuites = ssl_handshake:cipher_suites(AvailableCipherSuites, Renegotiation, Fallback), Id = ssl_session:client_id({Host, Port, SslOpts}, Cache, CacheCb, OwnCert), #client_hello{session_id = Id, client_version = Version, @@ -77,16 +79,18 @@ client_hello(Host, Port, ConnectionStates, %%-------------------------------------------------------------------- -spec hello(#server_hello{} | #client_hello{}, #ssl_options{}, - #connection_states{} | {inet:port_number(), #session{}, db_handle(), - atom(), #connection_states{}, binary() | undefined}, + ssl_record:connection_states() | {inet:port_number(), #session{}, db_handle(), + atom(), ssl_record:connection_states(), + binary() | undefined, ssl_cipher_format:key_algo()}, boolean()) -> - {tls_record:tls_version(), session_id(), #connection_states{}, binary() | undefined}| - {tls_record:tls_version(), {resumed | new, #session{}}, #connection_states{}, - [binary()] | undefined, - [ssl_handshake:oid()] | undefined, [ssl_handshake:oid()] | undefined} | - #alert{}. + {tls_record:tls_version(), session_id(), + ssl_record:connection_states(), alpn | npn, binary() | undefined}| + {tls_record:tls_version(), {resumed | new, #session{}}, + ssl_record:connection_states(), binary() | undefined, + #hello_extensions{}, {ssl_cipher_format:hash(), ssl_cipher_format:sign_algo()} | + undefined} | #alert{}. %% -%% Description: Handles a recieved hello message +%% Description: Handles a received hello message %%-------------------------------------------------------------------- hello(#server_hello{server_version = Version, random = Random, cipher_suite = CipherSuite, @@ -97,7 +101,8 @@ hello(#server_hello{server_version = Version, random = Random, case tls_record:is_acceptable_version(Version, SupportedVersions) of true -> handle_server_hello_extensions(Version, SessionId, Random, CipherSuite, - Compression, HelloExt, SslOpt, ConnectionStates0, Renegotiation); + Compression, HelloExt, SslOpt, + ConnectionStates0, Renegotiation); false -> ?ALERT_REC(?FATAL, ?PROTOCOL_VERSION) end; @@ -106,117 +111,140 @@ hello(#client_hello{client_version = ClientVersion, cipher_suites = CipherSuites} = Hello, #ssl_options{versions = Versions} = SslOpts, Info, Renegotiation) -> - Version = ssl_handshake:select_version(tls_record, ClientVersion, Versions), - case ssl_cipher:is_fallback(CipherSuites) of + try + Version = ssl_handshake:select_version(tls_record, ClientVersion, Versions), + case ssl_cipher:is_fallback(CipherSuites) of true -> - Highest = tls_record:highest_protocol_version(Versions), - case tls_record:is_higher(Highest, Version) of - true -> - ?ALERT_REC(?FATAL, ?INAPPROPRIATE_FALLBACK); - false -> - handle_client_hello(Version, Hello, SslOpts, Info, Renegotiation) - end; - false -> - handle_client_hello(Version, Hello, SslOpts, Info, Renegotiation) - end. + Highest = tls_record:highest_protocol_version(Versions), + case tls_record:is_higher(Highest, Version) of + true -> + ?ALERT_REC(?FATAL, ?INAPPROPRIATE_FALLBACK); + false -> + handle_client_hello(Version, Hello, SslOpts, Info, Renegotiation) + end; + false -> + handle_client_hello(Version, Hello, SslOpts, Info, Renegotiation) + end + catch + error:{case_clause,{asn1, Asn1Reason}} -> + %% ASN-1 decode of certificate somehow failed + ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, {failed_to_decode_own_certificate, Asn1Reason}); + _:_ -> + ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, malformed_handshake_data) + end. + + +%%-------------------------------------------------------------------- +%%% Handshake encodeing +%%-------------------------------------------------------------------- + %%-------------------------------------------------------------------- -spec encode_handshake(tls_handshake(), tls_record:tls_version()) -> iolist(). %% %% Description: Encode a handshake packet -%%--------------------------------------------------------------------x +%%-------------------------------------------------------------------- encode_handshake(Package, Version) -> {MsgType, Bin} = enc_handshake(Package, Version), Len = byte_size(Bin), [MsgType, ?uint24(Len), Bin]. + +%%-------------------------------------------------------------------- +%%% Handshake decodeing %%-------------------------------------------------------------------- --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 %%-------------------------------------------------------------------- -handle_client_hello(Version, #client_hello{session_id = SugesstedId, - cipher_suites = CipherSuites, - compression_methods = Compressions, - random = Random, - extensions = #hello_extensions{elliptic_curves = Curves} = HelloExt}, - #ssl_options{versions = Versions} = SslOpts, - {Port, Session0, Cache, CacheCb, ConnectionStates0, Cert}, Renegotiation) -> +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, + eccs = SupportedECCs, + honor_ecc_order = ECCOrder} = SslOpts, + {Port, Session0, Cache, CacheCb, ConnectionStates0, Cert, _}, + Renegotiation) -> case tls_record:is_acceptable_version(Version, Versions) of true -> - ECCCurve = ssl_handshake:select_curve(Curves, ssl_handshake:supported_ecc(Version)), + AvailableHashSigns = ssl_handshake:available_signature_algs( + ClientHashSigns, SupportedHashSigns, Cert, Version), + ECCCurve = ssl_handshake:select_curve(Curves, SupportedECCs, ECCOrder), {Type, #session{cipher_suite = CipherSuite} = Session1} - = ssl_handshake:select_session(SugesstedId, CipherSuites, Compressions, - Port, Session0#session{ecc = ECCCurve}, Version, - SslOpts, Cache, CacheCb, Cert), + = 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); + ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_ciphers); _ -> - handle_client_hello_extensions(Version, Type, Random, CipherSuites, HelloExt, - SslOpts, Session1, ConnectionStates0, - Renegotiation) + #{key_exchange := KeyExAlg} = ssl_cipher_format: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. -get_tls_handshake_aux(Version, <<?BYTE(Type), ?UINT24(Length), - Body:Length/binary,Rest/binary>>, 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) -> - {lists:reverse(Acc), Data}. - -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>>) -> - #client_hello{client_version = {Major, Minor}, - random = ssl_v2:client_random(ChallengeData, CDLength), - session_id = 0, - cipher_suites = ssl_handshake:decode_suites('3_bytes', CipherSuites), - compression_methods = [?NULL], - extensions = #hello_extensions{} - }; -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>>) -> - - DecodedExtensions = ssl_handshake:decode_hello_extensions({client, Extensions}), - - #client_hello{ - client_version = {Major,Minor}, - random = Random, - session_id = Session_ID, - cipher_suites = ssl_handshake:decode_suites('2_bytes', CipherSuites), - compression_methods = Comp_methods, - extensions = DecodedExtensions - }; +handle_client_hello_extensions(Version, Type, Random, CipherSuites, + HelloExt, SslOpts, Session0, ConnectionStates0, + Renegotiation, HashSign) -> + try ssl_handshake:handle_client_hello_extensions(tls_record, Random, CipherSuites, + HelloExt, 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. -decode_handshake(Version, Tag, Msg) -> - ssl_handshake:decode_handshake(Version, Tag, Msg). +handle_server_hello_extensions(Version, SessionId, Random, CipherSuite, + Compression, HelloExt, SslOpt, ConnectionStates0, Renegotiation) -> + case ssl_handshake:handle_server_hello_extensions(tls_record, Random, CipherSuite, + Compression, HelloExt, Version, + SslOpt, ConnectionStates0, + Renegotiation) of + #alert{} = Alert -> + Alert; + {ConnectionStates, ProtoExt, Protocol} -> + {Version, SessionId, ConnectionStates, ProtoExt, Protocol} + end. +%%-------------------------------------------------------------------- enc_handshake(#hello_request{}, _Version) -> {?HELLO_REQUEST, <<>>}; enc_handshake(#client_hello{client_version = {Major, Minor}, @@ -240,27 +268,39 @@ enc_handshake(#client_hello{client_version = {Major, Minor}, enc_handshake(HandshakeMsg, Version) -> ssl_handshake:encode_handshake(HandshakeMsg, Version). +%%-------------------------------------------------------------------- +get_tls_handshake_aux(Version, <<?BYTE(Type), ?UINT24(Length), + Body:Length/binary,Rest/binary>>, + Opts, Acc) -> + Raw = <<?BYTE(Type), ?UINT24(Length), Body/binary>>, + try decode_handshake(Version, Type, Body) of + Handshake -> + get_tls_handshake_aux(Version, Rest, Opts, [{Handshake,Raw} | Acc]) + catch + _:_ -> + throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, handshake_decode_error)) + end; +get_tls_handshake_aux(_Version, Data, _, Acc) -> + {lists:reverse(Acc), Data}. -handle_client_hello_extensions(Version, Type, Random, CipherSuites, - HelloExt, SslOpts, Session0, ConnectionStates0, Renegotiation) -> - try ssl_handshake:handle_client_hello_extensions(tls_record, Random, CipherSuites, - HelloExt, Version, SslOpts, - Session0, ConnectionStates0, Renegotiation) of - {Session, ConnectionStates, ServerHelloExt} -> - {Version, {Type, Session}, ConnectionStates, ServerHelloExt} - catch throw:Alert -> - Alert - end. - +decode_handshake(_, ?HELLO_REQUEST, <<>>) -> + #hello_request{}; +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>>) -> + DecodedExtensions = ssl_handshake:decode_hello_extensions({client, Extensions}), + #client_hello{ + client_version = {Major,Minor}, + random = Random, + session_id = Session_ID, + cipher_suites = ssl_handshake:decode_suites('2_bytes', CipherSuites), + compression_methods = Comp_methods, + extensions = DecodedExtensions + }; +decode_handshake(Version, Tag, Msg) -> + ssl_handshake:decode_handshake(Version, Tag, Msg). -handle_server_hello_extensions(Version, SessionId, Random, CipherSuite, - Compression, HelloExt, SslOpt, ConnectionStates0, Renegotiation) -> - case ssl_handshake:handle_server_hello_extensions(tls_record, Random, CipherSuite, - Compression, HelloExt, Version, - SslOpt, ConnectionStates0, Renegotiation) of - #alert{} = Alert -> - Alert; - {ConnectionStates, Protocol} -> - {Version, SessionId, ConnectionStates, Protocol} - end. diff --git a/lib/ssl/src/tls_handshake.hrl b/lib/ssl/src/tls_handshake.hrl index 1646e5b6f2..f6644f64af 100644 --- a/lib/ssl/src/tls_handshake.hrl +++ b/lib/ssl/src/tls_handshake.hrl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2013-2014. All Rights Reserved. -%% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% 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. +%% 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% %% diff --git a/lib/ssl/src/tls_record.erl b/lib/ssl/src/tls_record.erl index 168b2c8fd3..1776ec2627 100644 --- a/lib/ssl/src/tls_record.erl +++ b/lib/ssl/src/tls_record.erl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2015. All Rights Reserved. +%% Copyright Ericsson AB 2007-2018. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. +%% 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 %% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% 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% %% @@ -31,18 +32,21 @@ -include("ssl_cipher.hrl"). %% Handling of incoming data --export([get_tls_records/2]). +-export([get_tls_records/3, init_connection_states/2]). + +%% Encoding TLS records +-export([encode_handshake/3, encode_alert_record/3, + encode_change_cipher_spec/2, encode_data/3]). +-export([encode_plain_text/4]). %% Decoding -export([decode_cipher_text/3]). -%% Encoding --export([encode_plain_text/4]). - %% Protocol version handling --export([protocol_version/1, lowest_protocol_version/2, - highest_protocol_version/1, is_higher/2, supported_protocol_versions/0, - is_acceptable_version/1, is_acceptable_version/2]). +-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/1, is_acceptable_version/2, hello_version/2]). -export_type([tls_version/0, tls_atom_version/0]). @@ -52,128 +56,162 @@ -compile(inline). %%==================================================================== -%% Internal application API +%% Handling of incoming data %%==================================================================== +%%-------------------------------------------------------------------- +-spec init_connection_states(client | server, one_n_minus_one | zero_n | disabled) -> + ssl_record:connection_states(). +%% +%% Description: Creates a connection_states record with appropriate +%% values for the initial SSL connection setup. +%%-------------------------------------------------------------------- +init_connection_states(Role, BeastMitigation) -> + ConnectionEnd = ssl_record:record_protocol_role(Role), + Current = initial_connection_state(ConnectionEnd, BeastMitigation), + Pending = ssl_record:empty_connection_state(ConnectionEnd, BeastMitigation), + #{current_read => Current, + pending_read => Pending, + current_write => Current, + pending_write => Pending}. %%-------------------------------------------------------------------- --spec get_tls_records(binary(), binary()) -> {[binary()], binary()} | #alert{}. +-spec get_tls_records(binary(), [tls_version()] | tls_version(), binary()) -> {[binary()], binary()} | #alert{}. %% -%% Description: Given old buffer and new data from TCP, packs up a records %% and returns it as a list of tls_compressed binaries also returns leftover +%% Description: Given old buffer and new data from TCP, packs up a records %% data %%-------------------------------------------------------------------- -get_tls_records(Data, <<>>) -> - get_tls_records_aux(Data, []); -get_tls_records(Data, Buffer) -> - get_tls_records_aux(list_to_binary([Buffer, Data]), []). +get_tls_records(Data, Version, Buffer) -> + get_tls_records_aux(Version, <<Buffer/binary, Data/binary>>, []). + +%%==================================================================== +%% Encoding +%%==================================================================== -get_tls_records_aux(<<?BYTE(?APPLICATION_DATA),?BYTE(MajVer),?BYTE(MinVer), - ?UINT16(Length), Data:Length/binary, Rest/binary>>, - Acc) -> - get_tls_records_aux(Rest, [#ssl_tls{type = ?APPLICATION_DATA, - version = {MajVer, MinVer}, - fragment = Data} | Acc]); -get_tls_records_aux(<<?BYTE(?HANDSHAKE),?BYTE(MajVer),?BYTE(MinVer), - ?UINT16(Length), - Data:Length/binary, Rest/binary>>, Acc) -> - get_tls_records_aux(Rest, [#ssl_tls{type = ?HANDSHAKE, - version = {MajVer, MinVer}, - fragment = Data} | Acc]); -get_tls_records_aux(<<?BYTE(?ALERT),?BYTE(MajVer),?BYTE(MinVer), - ?UINT16(Length), Data:Length/binary, - Rest/binary>>, Acc) -> - get_tls_records_aux(Rest, [#ssl_tls{type = ?ALERT, - version = {MajVer, MinVer}, - fragment = Data} | Acc]); -get_tls_records_aux(<<?BYTE(?CHANGE_CIPHER_SPEC),?BYTE(MajVer),?BYTE(MinVer), - ?UINT16(Length), Data:Length/binary, Rest/binary>>, - Acc) -> - get_tls_records_aux(Rest, [#ssl_tls{type = ?CHANGE_CIPHER_SPEC, - version = {MajVer, MinVer}, - fragment = Data} | Acc]); -%% Matches an ssl v2 client hello message. -%% 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. -get_tls_records_aux(<<1:1, Length0:15, Data0:Length0/binary, Rest/binary>>, - Acc) -> - case Data0 of - <<?BYTE(?CLIENT_HELLO), ?BYTE(MajVer), ?BYTE(MinVer), _/binary>> -> - Length = Length0-1, - <<?BYTE(_), Data1:Length/binary>> = Data0, - Data = <<?BYTE(?CLIENT_HELLO), ?UINT24(Length), Data1/binary>>, - get_tls_records_aux(Rest, [#ssl_tls{type = ?HANDSHAKE, - version = {MajVer, MinVer}, - fragment = Data} | Acc]); - _ -> - ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE) - - end; +%%-------------------------------------------------------------------- +-spec encode_handshake(iolist(), tls_version(), ssl_record:connection_states()) -> + {iolist(), ssl_record:connection_states()}. +% +%% Description: Encodes a handshake message to send on the ssl-socket. +%%-------------------------------------------------------------------- +encode_handshake(Frag, Version, + #{current_write := + #{beast_mitigation := BeastMitigation, + security_parameters := + #security_parameters{bulk_cipher_algorithm = BCA}}} = + ConnectionStates) -> + case iolist_size(Frag) of + N when N > ?MAX_PLAIN_TEXT_LENGTH -> + Data = split_bin(iolist_to_binary(Frag), Version, BCA, BeastMitigation), + encode_iolist(?HANDSHAKE, Data, Version, ConnectionStates); + _ -> + encode_plain_text(?HANDSHAKE, Version, Frag, ConnectionStates) + end. -get_tls_records_aux(<<0:1, _CT:7, ?BYTE(_MajVer), ?BYTE(_MinVer), - ?UINT16(Length), _/binary>>, - _Acc) when Length > ?MAX_CIPHER_TEXT_LENGTH -> - ?ALERT_REC(?FATAL, ?RECORD_OVERFLOW); +%%-------------------------------------------------------------------- +-spec encode_alert_record(#alert{}, tls_version(), ssl_record:connection_states()) -> + {iolist(), ssl_record:connection_states()}. +%% +%% Description: Encodes an alert message to send on the ssl-socket. +%%-------------------------------------------------------------------- +encode_alert_record(#alert{level = Level, description = Description}, + Version, ConnectionStates) -> + encode_plain_text(?ALERT, Version, <<?BYTE(Level), ?BYTE(Description)>>, + ConnectionStates). -get_tls_records_aux(<<1:1, Length0:15, _/binary>>,_Acc) - when Length0 > ?MAX_CIPHER_TEXT_LENGTH -> - ?ALERT_REC(?FATAL, ?RECORD_OVERFLOW); +%%-------------------------------------------------------------------- +-spec encode_change_cipher_spec(tls_version(), ssl_record:connection_states()) -> + {iolist(), ssl_record:connection_states()}. +%% +%% Description: Encodes a change_cipher_spec-message to send on the ssl socket. +%%-------------------------------------------------------------------- +encode_change_cipher_spec(Version, ConnectionStates) -> + encode_plain_text(?CHANGE_CIPHER_SPEC, Version, ?byte(?CHANGE_CIPHER_SPEC_PROTO), ConnectionStates). -get_tls_records_aux(Data, Acc) -> - case size(Data) =< ?MAX_CIPHER_TEXT_LENGTH + ?INITIAL_BYTES of - true -> - {lists:reverse(Acc), Data}; - false -> - ?ALERT_REC(?FATAL, ?UNEXPECTED_MESSAGE) - end. - -encode_plain_text(Type, Version, Data, - #connection_states{current_write = - #connection_state{ - sequence_number = Seq, - compression_state=CompS0, - security_parameters= - #security_parameters{compression_algorithm=CompAlg} - }= WriteState0} = ConnectionStates) -> - {Comp, CompS1} = ssl_record:compress(CompAlg, Data, CompS0), - WriteState1 = WriteState0#connection_state{compression_state = CompS1}, - MacHash = calc_mac_hash(Type, Version, Comp, WriteState1), - {CipherFragment, WriteState} = ssl_record:cipher(Version, Comp, WriteState1, MacHash), - CipherText = encode_tls_cipher_text(Type, Version, CipherFragment), - {CipherText, ConnectionStates#connection_states{current_write = WriteState#connection_state{sequence_number = Seq +1}}}. +%%-------------------------------------------------------------------- +-spec encode_data(binary(), tls_version(), ssl_record:connection_states()) -> + {iolist(), ssl_record:connection_states()}. +%% +%% Description: Encodes data to send on the ssl-socket. +%%-------------------------------------------------------------------- +encode_data(Frag, Version, + #{current_write := #{beast_mitigation := BeastMitigation, + security_parameters := + #security_parameters{bulk_cipher_algorithm = BCA}}} = + ConnectionStates) -> + Data = split_bin(Frag, Version, BCA, BeastMitigation), + encode_iolist(?APPLICATION_DATA, Data, Version, ConnectionStates). + +%%==================================================================== +%% Decoding +%%==================================================================== %%-------------------------------------------------------------------- --spec decode_cipher_text(#ssl_tls{}, #connection_states{}, boolean()) -> - {#ssl_tls{}, #connection_states{}}| #alert{}. +-spec decode_cipher_text(#ssl_tls{}, ssl_record:connection_states(), boolean()) -> + {#ssl_tls{}, ssl_record:connection_states()}| #alert{}. %% %% Description: Decode cipher text %%-------------------------------------------------------------------- decode_cipher_text(#ssl_tls{type = Type, version = Version, fragment = CipherFragment} = CipherText, - #connection_states{current_read = - #connection_state{ - compression_state = CompressionS0, - sequence_number = Seq, - security_parameters= - #security_parameters{compression_algorithm = CompressAlg} - } = ReadState0} = ConnnectionStates0, PaddingCheck) -> + #{current_read := + #{compression_state := CompressionS0, + sequence_number := Seq, + cipher_state := CipherS0, + security_parameters := + #security_parameters{ + cipher_type = ?AEAD, + bulk_cipher_algorithm = + BulkCipherAlgo, + compression_algorithm = CompAlg} + } = ReadState0} = ConnnectionStates0, _) -> + AAD = start_additional_data(Type, Version, ReadState0), + CipherS1 = ssl_record:nonce_seed(BulkCipherAlgo, <<?UINT64(Seq)>>, CipherS0), + case ssl_record:decipher_aead(BulkCipherAlgo, CipherS1, AAD, CipherFragment, Version) of + {PlainFragment, CipherState} -> + {Plain, CompressionS1} = ssl_record:uncompress(CompAlg, + PlainFragment, CompressionS0), + ConnnectionStates = ConnnectionStates0#{ + current_read => ReadState0#{ + cipher_state => CipherState, + sequence_number => Seq + 1, + compression_state => CompressionS1}}, + {CipherText#ssl_tls{fragment = Plain}, ConnnectionStates}; + #alert{} = Alert -> + Alert + end; + +decode_cipher_text(#ssl_tls{type = Type, version = Version, + fragment = CipherFragment} = CipherText, + #{current_read := + #{compression_state := CompressionS0, + sequence_number := Seq, + security_parameters := + #security_parameters{compression_algorithm = CompAlg} + } = ReadState0} = ConnnectionStates0, PaddingCheck) -> case ssl_record:decipher(Version, CipherFragment, ReadState0, PaddingCheck) of {PlainFragment, Mac, ReadState1} -> - MacHash = calc_mac_hash(Type, Version, PlainFragment, ReadState1), + MacHash = ssl_cipher:calc_mac_hash(Type, Version, PlainFragment, ReadState1), case ssl_record:is_correct_mac(Mac, MacHash) of true -> - {Plain, CompressionS1} = ssl_record:uncompress(CompressAlg, + {Plain, CompressionS1} = ssl_record:uncompress(CompAlg, PlainFragment, CompressionS0), - ConnnectionStates = ConnnectionStates0#connection_states{ - current_read = ReadState1#connection_state{ - sequence_number = Seq + 1, - compression_state = CompressionS1}}, + ConnnectionStates = ConnnectionStates0#{ + current_read => ReadState1#{ + sequence_number => Seq + 1, + compression_state => CompressionS1}}, {CipherText#ssl_tls{fragment = Plain}, ConnnectionStates}; false -> ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC) end; #alert{} = Alert -> Alert - end. + end. + +%%==================================================================== +%% Protocol version handling +%%==================================================================== + %%-------------------------------------------------------------------- -spec protocol_version(tls_atom_version() | tls_version()) -> tls_version() | tls_atom_version(). @@ -214,6 +252,18 @@ lowest_protocol_version(Version = {M,_}, Version; lowest_protocol_version(_,Version) -> Version. + +%%-------------------------------------------------------------------- +-spec lowest_protocol_version([tls_version()]) -> tls_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([tls_version()]) -> tls_version(). %% @@ -223,19 +273,29 @@ highest_protocol_version([]) -> highest_protocol_version(); highest_protocol_version(Versions) -> [Ver | Vers] = Versions, - highest_protocol_version(Ver, Vers). + highest_list_protocol_version(Ver, Vers). -highest_protocol_version(Version, []) -> +%%-------------------------------------------------------------------- +-spec highest_protocol_version(tls_version(), tls_version()) -> tls_version(). +%% +%% Description: Highest protocol version of two given versions +%%-------------------------------------------------------------------- +highest_protocol_version(Version = {M, N}, {M, O}) when N > O -> 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({M, _}, + Version = {M, _}) -> + Version; +highest_protocol_version(Version = {M,_}, + {N, _}) when M > N -> + Version; +highest_protocol_version(_,Version) -> + Version. +%%-------------------------------------------------------------------- +-spec is_higher(V1 :: tls_version(), V2::tls_version()) -> boolean(). +%% +%% Description: Is V1 > V2 +%%-------------------------------------------------------------------- is_higher({M, N}, {M, O}) when N > O -> true; is_higher({M, _}, {N, _}) when M > N -> @@ -276,13 +336,18 @@ supported_protocol_versions([]) -> Vsns; supported_protocol_versions([_|_] = Vsns) -> - Vsns. + case sufficient_tlsv1_2_crypto_support() of + true -> + Vsns; + false -> + case Vsns -- ['tlsv1.2'] of + [] -> + ?MIN_SUPPORTED_VERSIONS; + NewVsns -> + NewVsns + end + end. -%%-------------------------------------------------------------------- -%% -%% Description: ssl version 2 is not acceptable security risks are too big. -%% -%%-------------------------------------------------------------------- -spec is_acceptable_version(tls_version()) -> boolean(). is_acceptable_version({N,_}) when N >= ?LOWEST_MAJOR_SUPPORTED_VERSION -> @@ -297,37 +362,165 @@ is_acceptable_version({N,_} = Version, Versions) is_acceptable_version(_,_) -> false. +-spec hello_version(tls_version(), [tls_version()]) -> tls_version(). +hello_version(Version, _) when Version >= {3, 3} -> + Version; +hello_version(_, Versions) -> + lowest_protocol_version(Versions). + %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- -encode_tls_cipher_text(Type, {MajVer, MinVer}, Fragment) -> +initial_connection_state(ConnectionEnd, BeastMitigation) -> + #{security_parameters => + ssl_record:initial_security_params(ConnectionEnd), + sequence_number => 0, + beast_mitigation => BeastMitigation, + compression_state => undefined, + cipher_state => undefined, + mac_secret => undefined, + secure_renegotiation => undefined, + client_verify_data => undefined, + server_verify_data => undefined + }. + +get_tls_records_aux({MajVer, MinVer} = Version, <<?BYTE(Type),?BYTE(MajVer),?BYTE(MinVer), + ?UINT16(Length), Data:Length/binary, Rest/binary>>, + Acc) when Type == ?APPLICATION_DATA; + Type == ?HANDSHAKE; + Type == ?ALERT; + Type == ?CHANGE_CIPHER_SPEC -> + get_tls_records_aux(Version, Rest, [#ssl_tls{type = Type, + version = Version, + fragment = Data} | Acc]); +get_tls_records_aux(Versions, <<?BYTE(Type),?BYTE(MajVer),?BYTE(MinVer), + ?UINT16(Length), Data:Length/binary, Rest/binary>>, + Acc) when is_list(Versions) andalso + ((Type == ?APPLICATION_DATA) + orelse + (Type == ?HANDSHAKE) + orelse + (Type == ?ALERT) + orelse + (Type == ?CHANGE_CIPHER_SPEC)) -> + case is_acceptable_version({MajVer, MinVer}, Versions) of + true -> + get_tls_records_aux(Versions, Rest, [#ssl_tls{type = Type, + version = {MajVer, MinVer}, + fragment = Data} | Acc]); + false -> + ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC) + end; +get_tls_records_aux(_, <<?BYTE(Type),?BYTE(_MajVer),?BYTE(_MinVer), + ?UINT16(Length), _:Length/binary, _Rest/binary>>, + _) when Type == ?APPLICATION_DATA; + Type == ?HANDSHAKE; + Type == ?ALERT; + Type == ?CHANGE_CIPHER_SPEC -> + ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC); +get_tls_records_aux(_, <<0:1, _CT:7, ?BYTE(_MajVer), ?BYTE(_MinVer), + ?UINT16(Length), _/binary>>, + _Acc) when Length > ?MAX_CIPHER_TEXT_LENGTH -> + ?ALERT_REC(?FATAL, ?RECORD_OVERFLOW); +get_tls_records_aux(_, Data, Acc) -> + case size(Data) =< ?MAX_CIPHER_TEXT_LENGTH + ?INITIAL_BYTES of + true -> + {lists:reverse(Acc), Data}; + false -> + ?ALERT_REC(?FATAL, ?UNEXPECTED_MESSAGE) + end. +%%-------------------------------------------------------------------- +encode_plain_text(Type, Version, Data, #{current_write := Write0} = ConnectionStates) -> + {CipherFragment, Write1} = do_encode_plain_text(Type, Version, Data, Write0), + {CipherText, Write} = encode_tls_cipher_text(Type, Version, CipherFragment, Write1), + {CipherText, ConnectionStates#{current_write => Write}}. + +encode_tls_cipher_text(Type, {MajVer, MinVer}, Fragment, #{sequence_number := Seq} = Write) -> Length = erlang:iolist_size(Fragment), - [<<?BYTE(Type), ?BYTE(MajVer), ?BYTE(MinVer), ?UINT16(Length)>>, Fragment]. + {[<<?BYTE(Type), ?BYTE(MajVer), ?BYTE(MinVer), ?UINT16(Length)>>, Fragment], + Write#{sequence_number => Seq +1}}. + +encode_iolist(Type, Data, Version, ConnectionStates0) -> + {ConnectionStates, EncodedMsg} = + lists:foldl(fun(Text, {CS0, Encoded}) -> + {Enc, CS1} = + encode_plain_text(Type, Version, Text, CS0), + {CS1, [Enc | Encoded]} + end, {ConnectionStates0, []}, Data), + {lists:reverse(EncodedMsg), ConnectionStates}. +%%-------------------------------------------------------------------- +do_encode_plain_text(Type, Version, Data, #{compression_state := CompS0, + cipher_state := CipherS0, + sequence_number := Seq, + security_parameters := + #security_parameters{ + cipher_type = ?AEAD, + bulk_cipher_algorithm = BCAlg, + compression_algorithm = CompAlg} + } = WriteState0) -> + {Comp, CompS1} = ssl_record:compress(CompAlg, Data, CompS0), + CipherS = ssl_record:nonce_seed(BCAlg, <<?UINT64(Seq)>>, CipherS0), + WriteState = WriteState0#{compression_state => CompS1, + cipher_state => CipherS}, + AAD = start_additional_data(Type, Version, WriteState), + ssl_record:cipher_aead(Version, Comp, WriteState, AAD); +do_encode_plain_text(Type, Version, Data, #{compression_state := CompS0, + security_parameters := + #security_parameters{compression_algorithm = CompAlg} + }= WriteState0) -> + {Comp, CompS1} = ssl_record:compress(CompAlg, Data, CompS0), + WriteState1 = WriteState0#{compression_state => CompS1}, + MacHash = ssl_cipher:calc_mac_hash(Type, Version, Comp, WriteState1), + ssl_record:cipher(Version, Comp, WriteState1, MacHash); +do_encode_plain_text(_,_,_,CS) -> + exit({cs, CS}). +%%-------------------------------------------------------------------- +start_additional_data(Type, {MajVer, MinVer}, + #{sequence_number := SeqNo}) -> + <<?UINT64(SeqNo), ?BYTE(Type), ?BYTE(MajVer), ?BYTE(MinVer)>>. + +%% 1/n-1 splitting countermeasure Rizzo/Duong-Beast, RC4 chiphers are +%% not vulnerable to this attack. +split_bin(<<FirstByte:8, Rest/binary>>, Version, BCA, one_n_minus_one) when + BCA =/= ?RC4 andalso ({3, 1} == Version orelse + {3, 0} == Version) -> + [[FirstByte]|do_split_bin(Rest)]; +%% 0/n splitting countermeasure for clients that are incompatible with 1/n-1 +%% splitting. +split_bin(Bin, Version, BCA, zero_n) when + BCA =/= ?RC4 andalso ({3, 1} == Version orelse + {3, 0} == Version) -> + [<<>>|do_split_bin(Bin)]; +split_bin(Bin, _, _, _) -> + do_split_bin(Bin). +do_split_bin(<<>>) -> []; +do_split_bin(Bin) -> + case Bin of + <<Chunk:?MAX_PLAIN_TEXT_LENGTH/binary, Rest/binary>> -> + [Chunk|do_split_bin(Rest)]; + _ -> + [Bin] + end. +%%-------------------------------------------------------------------- +lowest_list_protocol_version(Ver, []) -> + Ver; +lowest_list_protocol_version(Ver1, [Ver2 | Rest]) -> + lowest_list_protocol_version(lowest_protocol_version(Ver1, Ver2), Rest). -mac_hash({_,_}, ?NULL, _MacSecret, _SeqNo, _Type, - _Length, _Fragment) -> - <<>>; -mac_hash({3, 0}, MacAlg, MacSecret, SeqNo, Type, Length, Fragment) -> - ssl_v3:mac_hash(MacAlg, MacSecret, SeqNo, Type, Length, Fragment); -mac_hash({3, N} = Version, MacAlg, MacSecret, SeqNo, Type, Length, Fragment) - when N =:= 1; N =:= 2; N =:= 3 -> - tls_v1:mac_hash(MacAlg, MacSecret, SeqNo, Type, Version, - Length, Fragment). +highest_list_protocol_version(Ver, []) -> + Ver; +highest_list_protocol_version(Ver1, [Ver2 | Rest]) -> + highest_list_protocol_version(highest_protocol_version(Ver1, Ver2), Rest). highest_protocol_version() -> highest_protocol_version(supported_protocol_versions()). +lowest_protocol_version() -> + lowest_protocol_version(supported_protocol_versions()). + sufficient_tlsv1_2_crypto_support() -> CryptoSupport = crypto:supports(), proplists:get_bool(sha256, proplists:get_value(hashs, CryptoSupport)). -calc_mac_hash(Type, Version, - PlainFragment, #connection_state{sequence_number = SeqNo, - mac_secret = MacSecret, - security_parameters = - SecPars}) -> - Length = erlang:iolist_size(PlainFragment), - mac_hash(Version, SecPars#security_parameters.mac_algorithm, - MacSecret, SeqNo, Type, - Length, PlainFragment). + diff --git a/lib/ssl/src/tls_record.hrl b/lib/ssl/src/tls_record.hrl index 30d7343074..e296f23673 100644 --- a/lib/ssl/src/tls_record.hrl +++ b/lib/ssl/src/tls_record.hrl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2013-2013. All Rights Reserved. -%% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% 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. +%% 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% %% diff --git a/lib/ssl/src/tls_sender.erl b/lib/ssl/src/tls_sender.erl new file mode 100644 index 0000000000..7520832f39 --- /dev/null +++ b/lib/ssl/src/tls_sender.erl @@ -0,0 +1,473 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2018-2018. 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(tls_sender). + +-behaviour(gen_statem). + +-include("ssl_internal.hrl"). +-include("ssl_alert.hrl"). +-include("ssl_handshake.hrl"). +-include("ssl_api.hrl"). + +%% API +-export([start/0, start/1, initialize/2, send_data/2, send_alert/2, + send_and_ack_alert/2, setopts/2, renegotiate/1, downgrade/2, + update_connection_state/3, dist_tls_socket/1, dist_handshake_complete/3]). + +%% gen_statem callbacks +-export([callback_mode/0, init/1, terminate/3, code_change/4]). +-export([init/3, connection/3, handshake/3, death_row/3]). + +-define(SERVER, ?MODULE). + +-record(data, {connection_pid, + connection_states = #{}, + role, + socket, + socket_options, + tracker, + protocol_cb, + transport_cb, + negotiated_version, + renegotiate_at, + connection_monitor, + dist_handle + }). + +%%%=================================================================== +%%% API +%%%=================================================================== +%%-------------------------------------------------------------------- +-spec start() -> {ok, Pid :: pid()} | + ignore | + {error, Error :: term()}. +-spec start(list()) -> {ok, Pid :: pid()} | + ignore | + {error, Error :: term()}. + +%% Description: Start sender process to avoid dead lock that +%% may happen when a socket is busy (busy port) and the +%% same process is sending and receiving +%%-------------------------------------------------------------------- +start() -> + gen_statem:start(?MODULE, [], []). +start(SpawnOpts) -> + gen_statem:start(?MODULE, [], SpawnOpts). + +%%-------------------------------------------------------------------- +-spec initialize(pid(), map()) -> ok. +%% Description: So TLS connection process can initialize it sender +%% process. +%%-------------------------------------------------------------------- +initialize(Pid, InitMsg) -> + gen_statem:call(Pid, {self(), InitMsg}). + +%%-------------------------------------------------------------------- +-spec send_data(pid(), iodata()) -> ok | {error, term()}. +%% Description: Send application data +%%-------------------------------------------------------------------- +send_data(Pid, AppData) -> + %% Needs error handling for external API + call(Pid, {application_data, AppData}). + +%%-------------------------------------------------------------------- +-spec send_alert(pid(), #alert{}) -> _. +%% Description: TLS connection process wants to send an Alert +%% in the connection state. +%%-------------------------------------------------------------------- +send_alert(Pid, Alert) -> + gen_statem:cast(Pid, Alert). + +%%-------------------------------------------------------------------- +-spec send_and_ack_alert(pid(), #alert{}) -> _. +%% Description: TLS connection process wants to send an Alert +%% in the connection state and recive an ack. +%%-------------------------------------------------------------------- +send_and_ack_alert(Pid, Alert) -> + gen_statem:call(Pid, {ack_alert, Alert}, ?DEFAULT_TIMEOUT). +%%-------------------------------------------------------------------- +-spec setopts(pid(), [{packet, integer() | atom()}]) -> ok | {error, term()}. +%% Description: Send application data +%%-------------------------------------------------------------------- +setopts(Pid, Opts) -> + call(Pid, {set_opts, Opts}). + +%%-------------------------------------------------------------------- +-spec renegotiate(pid()) -> {ok, WriteState::map()} | {error, closed}. +%% Description: So TLS connection process can synchronize the +%% encryption state to be used when handshaking. +%%-------------------------------------------------------------------- +renegotiate(Pid) -> + %% Needs error handling for external API + call(Pid, renegotiate). +%%-------------------------------------------------------------------- +-spec update_connection_state(pid(), WriteState::map(), tls_record:tls_version()) -> ok. +%% Description: So TLS connection process can synchronize the +%% encryption state to be used when sending application data. +%%-------------------------------------------------------------------- +update_connection_state(Pid, NewState, Version) -> + gen_statem:cast(Pid, {new_write, NewState, Version}). + +%%-------------------------------------------------------------------- +-spec downgrade(pid(), integer()) -> {ok, ssl_record:connection_state()} + | {error, timeout}. +%% Description: So TLS connection process can synchronize the +%% encryption state to be used when sending application data. +%%-------------------------------------------------------------------- +downgrade(Pid, Timeout) -> + try gen_statem:call(Pid, downgrade, Timeout) of + Result -> + Result + catch + _:_ -> + {error, timeout} + end. +%%-------------------------------------------------------------------- +-spec dist_handshake_complete(pid(), node(), term()) -> ok. +%% Description: Erlang distribution callback +%%-------------------------------------------------------------------- +dist_handshake_complete(ConnectionPid, Node, DHandle) -> + gen_statem:call(ConnectionPid, {dist_handshake_complete, Node, DHandle}). +%%-------------------------------------------------------------------- +-spec dist_tls_socket(pid()) -> {ok, #sslsocket{}}. +%% Description: To enable distribution startup to get a proper "#sslsocket{}" +%%-------------------------------------------------------------------- +dist_tls_socket(Pid) -> + gen_statem:call(Pid, dist_get_tls_socket). + +%%%=================================================================== +%%% gen_statem callbacks +%%%=================================================================== +%%-------------------------------------------------------------------- +-spec callback_mode() -> gen_statem:callback_mode_result(). +%%-------------------------------------------------------------------- +callback_mode() -> + state_functions. + +%%-------------------------------------------------------------------- +-spec init(Args :: term()) -> + gen_statem:init_result(atom()). +%%-------------------------------------------------------------------- +init(_) -> + %% Note: Should not trap exits so that this process + %% will be terminated if tls_connection process is + %% killed brutally + {ok, init, #data{}}. + +%%-------------------------------------------------------------------- +-spec init(gen_statem:event_type(), + Msg :: term(), + StateData :: term()) -> + gen_statem:event_handler_result(atom()). +%%-------------------------------------------------------------------- +init({call, From}, {Pid, #{current_write := WriteState, + role := Role, + socket := Socket, + socket_options := SockOpts, + tracker := Tracker, + protocol_cb := Connection, + transport_cb := Transport, + negotiated_version := Version, + renegotiate_at := RenegotiateAt}}, + #data{connection_states = ConnectionStates} = StateData0) -> + Monitor = erlang:monitor(process, Pid), + StateData = + StateData0#data{connection_pid = Pid, + connection_monitor = Monitor, + connection_states = + ConnectionStates#{current_write => WriteState}, + role = Role, + socket = Socket, + socket_options = SockOpts, + tracker = Tracker, + protocol_cb = Connection, + transport_cb = Transport, + negotiated_version = Version, + renegotiate_at = RenegotiateAt}, + {next_state, handshake, StateData, [{reply, From, ok}]}; +init(info, Msg, StateData) -> + handle_info(Msg, ?FUNCTION_NAME, StateData). +%%-------------------------------------------------------------------- +-spec connection(gen_statem:event_type(), + Msg :: term(), + StateData :: term()) -> + gen_statem:event_handler_result(atom()). +%%-------------------------------------------------------------------- +connection({call, From}, renegotiate, + #data{connection_states = #{current_write := Write}} = StateData) -> + {next_state, handshake, StateData, [{reply, From, {ok, Write}}]}; +connection({call, From}, {application_data, AppData}, + #data{socket_options = #socket_options{packet = Packet}} = + StateData) -> + case encode_packet(Packet, AppData) of + {error, _} = Error -> + {next_state, ?FUNCTION_NAME, StateData, [{reply, From, Error}]}; + Data -> + send_application_data(Data, From, ?FUNCTION_NAME, StateData) + end; +connection({call, From}, {set_opts, _} = Call, StateData) -> + handle_call(From, Call, ?FUNCTION_NAME, StateData); +connection({call, From}, dist_get_tls_socket, + #data{protocol_cb = Connection, + transport_cb = Transport, + socket = Socket, + connection_pid = Pid, + tracker = Tracker} = StateData) -> + TLSSocket = Connection:socket([Pid, self()], Transport, Socket, Connection, Tracker), + {next_state, ?FUNCTION_NAME, StateData, [{reply, From, {ok, TLSSocket}}]}; +connection({call, From}, {dist_handshake_complete, _Node, DHandle}, + #data{connection_pid = Pid, + socket_options = #socket_options{packet = Packet}} = + StateData) -> + ok = erlang:dist_ctrl_input_handler(DHandle, Pid), + ok = ssl_connection:dist_handshake_complete(Pid, DHandle), + %% From now on we execute on normal priority + process_flag(priority, normal), + {next_state, ?FUNCTION_NAME, StateData#data{dist_handle = DHandle}, + [{reply, From, ok} + | case dist_data(DHandle, Packet) of + [] -> + []; + Data -> + [{next_event, internal, + {application_packets,{self(),undefined},Data}}] + end]}; +connection({call, From}, {ack_alert, #alert{} = Alert}, StateData0) -> + StateData = send_tls_alert(Alert, StateData0), + {next_state, ?FUNCTION_NAME, StateData, + [{reply,From,ok}]}; +connection({call, From}, downgrade, #data{connection_states = + #{current_write := Write}} = StateData) -> + {next_state, death_row, StateData, [{reply,From, {ok, Write}}]}; +connection(internal, {application_packets, From, Data}, StateData) -> + send_application_data(Data, From, ?FUNCTION_NAME, StateData); +%% +connection(cast, #alert{} = Alert, StateData0) -> + StateData = send_tls_alert(Alert, StateData0), + {next_state, ?FUNCTION_NAME, StateData}; +connection(cast, {new_write, WritesState, Version}, + #data{connection_states = ConnectionStates0} = StateData) -> + {next_state, connection, + StateData#data{connection_states = + ConnectionStates0#{current_write => WritesState}, + negotiated_version = Version}}; +%% +connection(info, dist_data, + #data{dist_handle = DHandle, + socket_options = #socket_options{packet = Packet}} = + StateData) -> + {next_state, ?FUNCTION_NAME, StateData, + case dist_data(DHandle, Packet) of + [] -> + []; + Data -> + [{next_event, internal, + {application_packets,{self(),undefined},Data}}] + end}; +connection(info, tick, StateData) -> + consume_ticks(), + {next_state, ?FUNCTION_NAME, StateData, + [{next_event, {call, {self(), undefined}}, + {application_data, <<>>}}]}; +connection(info, {send, From, Ref, Data}, _StateData) -> + %% This is for testing only! + %% + %% Needed by some OTP distribution + %% test suites... + From ! {Ref, ok}, + {keep_state_and_data, + [{next_event, {call, {self(), undefined}}, + {application_data, iolist_to_binary(Data)}}]}; +connection(info, Msg, StateData) -> + handle_info(Msg, ?FUNCTION_NAME, StateData). +%%-------------------------------------------------------------------- +-spec handshake(gen_statem:event_type(), + Msg :: term(), + StateData :: term()) -> + gen_statem:event_handler_result(atom()). +%%-------------------------------------------------------------------- +handshake({call, From}, {set_opts, _} = Call, StateData) -> + handle_call(From, Call, ?FUNCTION_NAME, StateData); +handshake({call, _}, _, _) -> + {keep_state_and_data, [postpone]}; +handshake(cast, {new_write, WritesState, Version}, + #data{connection_states = ConnectionStates0} = StateData) -> + {next_state, connection, + StateData#data{connection_states = + ConnectionStates0#{current_write => WritesState}, + negotiated_version = Version}}; +handshake(internal, {application_packets,_,_}, _) -> + {keep_state_and_data, [postpone]}; +handshake(info, Msg, StateData) -> + handle_info(Msg, ?FUNCTION_NAME, StateData). + +%%-------------------------------------------------------------------- +-spec death_row(gen_statem:event_type(), + Msg :: term(), + StateData :: term()) -> + gen_statem:event_handler_result(atom()). +%%-------------------------------------------------------------------- +death_row(state_timeout, Reason, _State) -> + {stop, {shutdown, Reason}}; +death_row(_Type, _Msg, _State) -> + %% Waste all other events + keep_state_and_data. + +%%-------------------------------------------------------------------- +-spec terminate(Reason :: term(), State :: term(), Data :: term()) -> + any(). +%%-------------------------------------------------------------------- +terminate(_Reason, _State, _Data) -> + void. + +%%-------------------------------------------------------------------- +-spec code_change( + OldVsn :: term() | {down,term()}, + State :: term(), Data :: term(), Extra :: term()) -> + {ok, NewState :: term(), NewData :: term()} | + (Reason :: term()). +%% Convert process state when code is changed +%%-------------------------------------------------------------------- +code_change(_OldVsn, State, Data, _Extra) -> + {ok, State, Data}. + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== +handle_call(From, {set_opts, Opts}, StateName, #data{socket_options = SockOpts} = StateData) -> + {next_state, StateName, StateData#data{socket_options = set_opts(SockOpts, Opts)}, [{reply, From, ok}]}. + +handle_info({'DOWN', Monitor, _, _, Reason}, _, + #data{connection_monitor = Monitor, + dist_handle = Handle} = StateData) when Handle =/= undefined-> + {next_state, death_row, StateData, [{state_timeout, 5000, Reason}]}; +handle_info({'DOWN', Monitor, _, _, _}, _, + #data{connection_monitor = Monitor} = StateData) -> + {stop, normal, StateData}; +handle_info(_,_,_) -> + keep_state_and_data. + +send_tls_alert(Alert, #data{negotiated_version = Version, + socket = Socket, + protocol_cb = Connection, + transport_cb = Transport, + connection_states = ConnectionStates0} = StateData0) -> + {BinMsg, ConnectionStates} = + Connection:encode_alert(Alert, Version, ConnectionStates0), + Connection:send(Transport, Socket, BinMsg), + StateData0#data{connection_states = ConnectionStates}. + +send_application_data(Data, From, StateName, + #data{connection_pid = Pid, + socket = Socket, + dist_handle = DistHandle, + negotiated_version = Version, + protocol_cb = Connection, + transport_cb = Transport, + connection_states = ConnectionStates0, + renegotiate_at = RenegotiateAt} = StateData0) -> + case time_to_renegotiate(Data, ConnectionStates0, RenegotiateAt) of + true -> + ssl_connection:internal_renegotiation(Pid, ConnectionStates0), + {next_state, handshake, StateData0, + [{next_event, internal, {application_packets, From, Data}}]}; + false -> + {Msgs, ConnectionStates} = + Connection:encode_data( + iolist_to_binary(Data), Version, ConnectionStates0), + StateData = StateData0#data{connection_states = ConnectionStates}, + case Connection:send(Transport, Socket, Msgs) of + ok when DistHandle =/= undefined -> + {next_state, StateName, StateData, []}; + Reason when DistHandle =/= undefined -> + {next_state, death_row, StateData, [{state_timeout, 5000, Reason}]}; + ok -> + {next_state, StateName, StateData, [{reply, From, ok}]}; + Result -> + {next_state, StateName, StateData, [{reply, From, Result}]} + end + end. + +-compile({inline, encode_packet/2}). +encode_packet(Packet, Data) -> + Len = iolist_size(Data), + case Packet of + 1 when Len < (1 bsl 8) -> [<<Len:8>>,Data]; + 2 when Len < (1 bsl 16) -> [<<Len:16>>,Data]; + 4 when Len < (1 bsl 32) -> [<<Len:32>>,Data]; + N when N =:= 1; N =:= 2; N =:= 4 -> + {error, + {badarg, {packet_to_large, Len, (1 bsl (Packet bsl 3)) - 1}}}; + _ -> + Data + end. + +set_opts(SocketOptions, [{packet, N}]) -> + SocketOptions#socket_options{packet = N}. + +time_to_renegotiate(_Data, + #{current_write := #{sequence_number := Num}}, + RenegotiateAt) -> + + %% We could do test: + %% is_time_to_renegotiate((erlang:byte_size(_Data) div + %% ?MAX_PLAIN_TEXT_LENGTH) + 1, RenegotiateAt), but we chose to + %% have a some what lower renegotiateAt and a much cheaper test + is_time_to_renegotiate(Num, RenegotiateAt). + +is_time_to_renegotiate(N, M) when N < M-> + false; +is_time_to_renegotiate(_,_) -> + true. + +call(FsmPid, Event) -> + try gen_statem:call(FsmPid, Event) + catch + exit:{noproc, _} -> + {error, closed}; + exit:{normal, _} -> + {error, closed}; + exit:{{shutdown, _},_} -> + {error, closed} + end. + +%%---------------Erlang distribution -------------------------------------- + +dist_data(DHandle, Packet) -> + case erlang:dist_ctrl_get_data(DHandle) of + none -> + erlang:dist_ctrl_get_data_notification(DHandle), + []; + Data -> + %% This is encode_packet(4, Data) without Len check + %% since the emulator will always deliver a Data + %% smaller than 4 GB, and the distribution will + %% therefore always have to use {packet,4} + Len = iolist_size(Data), + [<<Len:32>>,Data|dist_data(DHandle, Packet)] + end. + +consume_ticks() -> + receive tick -> + consume_ticks() + after 0 -> + ok + end. diff --git a/lib/ssl/src/ssl_socket.erl b/lib/ssl/src/tls_socket.erl index 55eb569b20..a391bc53de 100644 --- a/lib/ssl/src/ssl_socket.erl +++ b/lib/ssl/src/tls_socket.erl @@ -1,31 +1,35 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1998-2014. All Rights Reserved. +%% Copyright Ericsson AB 1998-2018. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. +%% 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 %% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% 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_socket). +-module(tls_socket). -behaviour(gen_server). -include("ssl_internal.hrl"). -include("ssl_api.hrl"). --export([socket/5, setopts/3, getopts/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, +-export([send/3, listen/3, accept/3, socket/5, connect/4, upgrade/3, + setopts/3, getopts/3, getstat/3, peername/2, sockname/2, port/2]). +-export([split_options/1, get_socket_opts/3]). +-export([emulated_options/0, emulated_options/1, internal_inet_values/0, default_inet_values/0, + init/1, start_link/3, terminate/2, inherit_tracker/3, + emulated_socket_options/2, get_emulated_opts/1, set_emulated_opts/2, get_all_opts/1, handle_call/3, handle_cast/2, handle_info/2, code_change/3]). @@ -38,8 +42,79 @@ %%-------------------------------------------------------------------- %%% Internal API %%-------------------------------------------------------------------- -socket(Pid, Transport, Socket, ConnectionCb, Tracker) -> - #sslsocket{pid = Pid, +send(Transport, Socket, Data) -> + Transport:send(Socket, Data). + +listen(Transport, Port, #config{transport_info = {Transport, _, _, _}, + inet_user = Options, + ssl = SslOpts, emulated = EmOpts} = Config) -> + case Transport:listen(Port, Options ++ internal_inet_values()) of + {ok, ListenSocket} -> + {ok, Tracker} = inherit_tracker(ListenSocket, EmOpts, SslOpts), + {ok, #sslsocket{pid = {ListenSocket, Config#config{emulated = Tracker}}}}; + Err = {error, _} -> + Err + end. + +accept(ListenSocket, #config{transport_info = {Transport,_,_,_} = CbInfo, + connection_cb = ConnectionCb, + ssl = SslOpts, + emulated = Tracker}, Timeout) -> + case Transport:accept(ListenSocket, Timeout) of + {ok, Socket} -> + {ok, EmOpts} = get_emulated_opts(Tracker), + {ok, Port} = tls_socket:port(Transport, Socket), + {ok, Sender} = tls_sender:start(), + ConnArgs = [server, Sender, "localhost", Port, Socket, + {SslOpts, emulated_socket_options(EmOpts, #socket_options{}), Tracker}, self(), CbInfo], + case tls_connection_sup:start_child(ConnArgs) of + {ok, Pid} -> + ssl_connection:socket_control(ConnectionCb, Socket, [Pid, Sender], Transport, Tracker); + {error, Reason} -> + {error, Reason} + end; + {error, Reason} -> + {error, Reason} + end. + +upgrade(Socket, #config{transport_info = {Transport,_,_,_}= CbInfo, + ssl = SslOptions, + emulated = EmOpts, connection_cb = ConnectionCb}, Timeout) -> + ok = setopts(Transport, Socket, tls_socket:internal_inet_values()), + case peername(Transport, Socket) of + {ok, {Address, Port}} -> + ssl_connection:connect(ConnectionCb, Address, Port, Socket, + {SslOptions, + emulated_socket_options(EmOpts, #socket_options{}), undefined}, + self(), CbInfo, Timeout); + {error, Error} -> + {error, Error} + end. + +connect(Address, Port, + #config{transport_info = CbInfo, inet_user = UserOpts, ssl = SslOpts, + emulated = EmOpts, inet_ssl = SocketOpts, connection_cb = ConnetionCb}, + Timeout) -> + {Transport, _, _, _} = CbInfo, + try Transport:connect(Address, Port, SocketOpts, Timeout) of + {ok, Socket} -> + ssl_connection:connect(ConnetionCb, Address, Port, Socket, + {SslOpts, + emulated_socket_options(EmOpts, #socket_options{}), undefined}, + self(), CbInfo, Timeout); + {error, Reason} -> + {error, Reason} + catch + exit:{function_clause, _} -> + {error, {options, {cb_info, CbInfo}}}; + exit:badarg -> + {error, {options, {socket_options, UserOpts}}}; + exit:{badarg, _} -> + {error, {options, {socket_options, UserOpts}}} + end. + +socket(Pids, Transport, Socket, ConnectionCb, Tracker) -> + #sslsocket{pid = Pids, %% "The name "fd" is keept for backwards compatibility fd = {Transport, Socket, ConnectionCb, Tracker}}. setopts(gen_tcp, #sslsocket{pid = {ListenSocket, #config{emulated = Tracker}}}, Options) -> @@ -73,6 +148,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) -> @@ -91,6 +171,9 @@ port(Transport, Socket) -> emulated_options() -> [mode, packet, active, header, packet_size]. +emulated_options(Opts) -> + emulated_options(Opts, internal_inet_values(), default_inet_values()). + internal_inet_values() -> [{packet_size,0}, {packet, 0}, {header, 0}, {active, false}, {mode,binary}]. @@ -235,3 +318,55 @@ get_emulated_opts(TrackerPid, EmOptNames) -> lists:map(fun(Name) -> {value, Value} = lists:keysearch(Name, 1, EmOpts), Value end, EmOptNames). + +emulated_socket_options(InetValues, #socket_options{ + mode = Mode, + header = Header, + active = Active, + packet = Packet, + packet_size = Size}) -> + #socket_options{ + mode = proplists:get_value(mode, InetValues, Mode), + header = proplists:get_value(header, InetValues, Header), + active = proplists:get_value(active, InetValues, Active), + packet = proplists:get_value(packet, InetValues, Packet), + packet_size = proplists:get_value(packet_size, InetValues, Size) + }. + +emulated_options([{mode, Value} = Opt |Opts], Inet, Emulated) -> + validate_inet_option(mode, Value), + emulated_options(Opts, Inet, [Opt | proplists:delete(mode, Emulated)]); +emulated_options([{header, Value} = Opt | Opts], Inet, Emulated) -> + validate_inet_option(header, Value), + emulated_options(Opts, Inet, [Opt | proplists:delete(header, Emulated)]); +emulated_options([{active, Value} = Opt |Opts], Inet, Emulated) -> + validate_inet_option(active, Value), + emulated_options(Opts, Inet, [Opt | proplists:delete(active, Emulated)]); +emulated_options([{packet, Value} = Opt |Opts], Inet, Emulated) -> + validate_inet_option(packet, Value), + emulated_options(Opts, Inet, [Opt | proplists:delete(packet, Emulated)]); +emulated_options([{packet_size, Value} = Opt | Opts], Inet, Emulated) -> + validate_inet_option(packet_size, Value), + emulated_options(Opts, Inet, [Opt | proplists:delete(packet_size, Emulated)]); +emulated_options([Opt|Opts], Inet, Emulated) -> + emulated_options(Opts, [Opt|Inet], Emulated); +emulated_options([], Inet,Emulated) -> + {Inet, Emulated}. + +validate_inet_option(mode, Value) + when Value =/= list, Value =/= binary -> + throw({error, {options, {mode,Value}}}); +validate_inet_option(packet, Value) + when not (is_atom(Value) orelse is_integer(Value)) -> + throw({error, {options, {packet,Value}}}); +validate_inet_option(packet_size, Value) + when not is_integer(Value) -> + throw({error, {options, {packet_size,Value}}}); +validate_inet_option(header, Value) + when not is_integer(Value) -> + throw({error, {options, {header,Value}}}); +validate_inet_option(active, Value) + when Value =/= true, Value =/= false, Value =/= once -> + throw({error, {options, {active,Value}}}); +validate_inet_option(_, _) -> + ok. diff --git a/lib/ssl/src/tls_v1.erl b/lib/ssl/src/tls_v1.erl index 7a5f9c1b38..1bfd9a8b6d 100644 --- a/lib/ssl/src/tls_v1.erl +++ b/lib/ssl/src/tls_v1.erl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2014. All Rights Reserved. +%% Copyright Ericsson AB 2007-2018. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. +%% 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 %% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% 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% %% @@ -28,9 +29,19 @@ -include("ssl_internal.hrl"). -include("ssl_record.hrl"). --export([master_secret/4, finished/5, certificate_verify/3, mac_hash/7, +-export([master_secret/4, finished/5, certificate_verify/3, mac_hash/7, hmac_hash/3, setup_keys/8, suites/1, prf/5, - ecc_curves/1, oid_to_enum/1, enum_to_oid/1]). + ecc_curves/1, ecc_curves/2, oid_to_enum/1, enum_to_oid/1, + default_signature_algs/1, signature_algs/2]). + +-type named_curve() :: sect571r1 | sect571k1 | secp521r1 | brainpoolP512r1 | + sect409k1 | sect409r1 | brainpoolP384r1 | secp384r1 | + sect283k1 | sect283r1 | brainpoolP256r1 | secp256k1 | secp256r1 | + sect239k1 | sect233k1 | sect233r1 | secp224k1 | secp224r1 | + sect193r1 | sect193r2 | secp192k1 | secp192r1 | sect163k1 | + sect163r1 | sect163r2 | secp160k1 | secp160r1 | secp160r2. +-type curves() :: [named_curve()]. +-export_type([curves/0, named_curve/0]). %%==================================================================== %% Internal application API @@ -181,7 +192,7 @@ mac_hash(Method, Mac_write_secret, Seq_num, Type, {Major, Minor}, Fragment]), Mac. --spec suites(1|2|3) -> [ssl_cipher:cipher_suite()]. +-spec suites(1|2|3) -> [ssl_cipher_format:cipher_suite()]. suites(Minor) when Minor == 1; Minor == 2 -> [ @@ -191,55 +202,95 @@ suites(Minor) when Minor == 1; Minor == 2 -> ?TLS_DHE_DSS_WITH_AES_256_CBC_SHA, ?TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA, ?TLS_ECDH_RSA_WITH_AES_256_CBC_SHA, - ?TLS_RSA_WITH_AES_256_CBC_SHA, - - ?TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA, - ?TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, - ?TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA, - ?TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA, - ?TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA, - ?TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA, - ?TLS_RSA_WITH_3DES_EDE_CBC_SHA, ?TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, ?TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, ?TLS_DHE_RSA_WITH_AES_128_CBC_SHA, ?TLS_DHE_DSS_WITH_AES_128_CBC_SHA, ?TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA, - ?TLS_ECDH_RSA_WITH_AES_128_CBC_SHA, - ?TLS_RSA_WITH_AES_128_CBC_SHA, - - ?TLS_ECDHE_ECDSA_WITH_RC4_128_SHA, - ?TLS_ECDHE_RSA_WITH_RC4_128_SHA, - ?TLS_RSA_WITH_RC4_128_SHA, - ?TLS_RSA_WITH_RC4_128_MD5, - ?TLS_DHE_RSA_WITH_DES_CBC_SHA, - ?TLS_ECDH_ECDSA_WITH_RC4_128_SHA, - ?TLS_ECDH_RSA_WITH_RC4_128_SHA, - - ?TLS_RSA_WITH_DES_CBC_SHA + ?TLS_ECDH_RSA_WITH_AES_128_CBC_SHA ]; suites(3) -> - [ + [?TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + ?TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, ?TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384, ?TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384, + ?TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384, + ?TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384, ?TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384, ?TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384, + ?TLS_DHE_RSA_WITH_AES_256_GCM_SHA384, + ?TLS_DHE_DSS_WITH_AES_256_GCM_SHA384, ?TLS_DHE_RSA_WITH_AES_256_CBC_SHA256, ?TLS_DHE_DSS_WITH_AES_256_CBC_SHA256, - ?TLS_RSA_WITH_AES_256_CBC_SHA256, + ?TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + ?TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, ?TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, ?TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, + ?TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256, + ?TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256, ?TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256, ?TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256, + ?TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, + ?TLS_DHE_DSS_WITH_AES_128_GCM_SHA256, ?TLS_DHE_RSA_WITH_AES_128_CBC_SHA256, - ?TLS_DHE_DSS_WITH_AES_128_CBC_SHA256, - ?TLS_RSA_WITH_AES_128_CBC_SHA256 + ?TLS_DHE_DSS_WITH_AES_128_CBC_SHA256 + + %% not supported + %% ?TLS_DH_RSA_WITH_AES_256_GCM_SHA384, + %% ?TLS_DH_DSS_WITH_AES_256_GCM_SHA384, + %% ?TLS_DH_RSA_WITH_AES_128_GCM_SHA256, + %% ?TLS_DH_DSS_WITH_AES_128_GCM_SHA256 ] ++ suites(2). +signature_algs({3, 3}, HashSigns) -> + CryptoSupports = crypto:supports(), + Hashes = proplists:get_value(hashs, CryptoSupports), + PubKeys = proplists:get_value(public_keys, CryptoSupports), + Supported = lists:foldl(fun({Hash, dsa = Sign} = Alg, Acc) -> + case proplists:get_bool(dss, PubKeys) + andalso proplists:get_bool(Hash, Hashes) + andalso is_pair(Hash, Sign, Hashes) + of + true -> + [Alg | Acc]; + false -> + Acc + end; + ({Hash, Sign} = Alg, Acc) -> + case proplists:get_bool(Sign, PubKeys) + andalso proplists:get_bool(Hash, Hashes) + andalso is_pair(Hash, Sign, Hashes) + of + true -> + [Alg | Acc]; + false -> + Acc + end + end, [], HashSigns), + lists:reverse(Supported). + +default_signature_algs({3, 3} = Version) -> + Default = [%% SHA2 + {sha512, ecdsa}, + {sha512, rsa}, + {sha384, ecdsa}, + {sha384, rsa}, + {sha256, ecdsa}, + {sha256, rsa}, + {sha224, ecdsa}, + {sha224, rsa}, + %% SHA + {sha, ecdsa}, + {sha, rsa}, + {sha, dsa}], + signature_algs(Version, Default); +default_signature_algs(_) -> + undefined. + %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- @@ -324,14 +375,32 @@ finished_label(client) -> finished_label(server) -> <<"server finished">>. -%% list ECC curves in prefered order -ecc_curves(_Minor) -> - TLSCurves = [sect571r1,sect571k1,secp521r1,brainpoolP512r1, - sect409k1,sect409r1,brainpoolP384r1,secp384r1, - sect283k1,sect283r1,brainpoolP256r1,secp256k1,secp256r1, - sect239k1,sect233k1,sect233r1,secp224k1,secp224r1, - sect193r1,sect193r2,secp192k1,secp192r1,sect163k1, - sect163r1,sect163r2,secp160k1,secp160r1,secp160r2], +is_pair(sha, dsa, _) -> + true; +is_pair(_, dsa, _) -> + false; +is_pair(Hash, ecdsa, Hashs) -> + AtLeastSha = Hashs -- [md2,md4,md5], + lists:member(Hash, AtLeastSha); +is_pair(Hash, rsa, Hashs) -> + AtLeastMd5 = Hashs -- [md2,md4], + lists:member(Hash, AtLeastMd5). + +%% list ECC curves in preferred order +-spec ecc_curves(1..3 | all) -> [named_curve()]. +ecc_curves(all) -> + [sect571r1,sect571k1,secp521r1,brainpoolP512r1, + sect409k1,sect409r1,brainpoolP384r1,secp384r1, + sect283k1,sect283r1,brainpoolP256r1,secp256k1,secp256r1, + sect239k1,sect233k1,sect233r1,secp224k1,secp224r1, + sect193r1,sect193r2,secp192k1,secp192r1,sect163k1, + sect163r1,sect163r2,secp160k1,secp160r1,secp160r2]; +ecc_curves(Minor) -> + TLSCurves = ecc_curves(all), + ecc_curves(Minor, TLSCurves). + +-spec ecc_curves(1..3, [named_curve()]) -> [named_curve()]. +ecc_curves(_Minor, TLSCurves) -> CryptoCurves = crypto:ec_curves(), lists:foldr(fun(Curve, Curves) -> case proplists:get_bool(Curve, CryptoCurves) of @@ -340,6 +409,7 @@ ecc_curves(_Minor) -> end end, [], TLSCurves). + %% ECC curves from draft-ietf-tls-ecc-12.txt (Oct. 17, 2005) oid_to_enum(?sect163k1) -> 1; oid_to_enum(?sect163r1) -> 2; |