diff options
Diffstat (limited to 'lib/ssl/src/ssl_connection.erl')
-rw-r--r-- | lib/ssl/src/ssl_connection.erl | 3182 |
1 files changed, 0 insertions, 3182 deletions
diff --git a/lib/ssl/src/ssl_connection.erl b/lib/ssl/src/ssl_connection.erl deleted file mode 100644 index a854f50ee9..0000000000 --- a/lib/ssl/src/ssl_connection.erl +++ /dev/null @@ -1,3182 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2013-2020. 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: Common handling of a TLS/SSL/DTLS connection, see also -%% tls_connection.erl and dtls_connection.erl -%%---------------------------------------------------------------------- - --module(ssl_connection). - --include("ssl_api.hrl"). --include("ssl_connection.hrl"). --include("ssl_handshake.hrl"). --include("ssl_alert.hrl"). --include("ssl_record.hrl"). --include("ssl_cipher.hrl"). --include("ssl_internal.hrl"). --include("ssl_srp.hrl"). --include_lib("public_key/include/public_key.hrl"). --include_lib("kernel/include/logger.hrl"). - -%% Setup - --export([connect/8, handshake/7, handshake/2, handshake/3, handle_common_event/5, - handshake_continue/3, handshake_cancel/1, - socket_control/4, socket_control/5]). - -%% User Events --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 - ]). - -%% Alert and close handling --export([handle_own_alert/4, handle_alert/3, - handle_normal_shutdown/3, - handle_trusted_certs_db/1, - maybe_invalidate_session/6]). - -%% 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]). - -%% 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, wait_ocsp_stapling/4, cipher/4, - connection/4, downgrade/4]). - -%% gen_statem callbacks --export([terminate/3, format_status/2]). - -%% Erlang Distribution export --export([dist_handshake_complete/2]). - -%%==================================================================== -%% Setup -%%==================================================================== -%%-------------------------------------------------------------------- --spec connect(tls_connection | dtls_connection, - ssl:host(), inet:port_number(), - port() | {tuple(), port()}, %% TLS | DTLS - {ssl_options(), #socket_options{}, - %% Tracker only needed on server side - undefined}, - pid(), tuple(), timeout()) -> - {ok, #sslsocket{}} | {error, reason()}. -%% -%% Description: Connect to an ssl server. -%%-------------------------------------------------------------------- -connect(Connection, Host, Port, Socket, Options, User, CbInfo, Timeout) -> - try Connection:start_fsm(client, Host, Port, Socket, Options, User, CbInfo, - Timeout) - catch - exit:{noproc, _} -> - {error, ssl_not_started} - end. -%%-------------------------------------------------------------------- --spec handshake(tls_connection | dtls_connection, - inet:port_number(), port(), - {ssl_options(), #socket_options{}, list()}, - pid(), tuple(), timeout()) -> - {ok, #sslsocket{}} | {error, reason()}. -%% -%% Description: Performs accept on an ssl listen socket. e.i. performs -%% ssl handshake. -%%-------------------------------------------------------------------- -handshake(Connection, Port, Socket, Opts, User, CbInfo, Timeout) -> - try Connection:start_fsm(server, "localhost", Port, Socket, Opts, User, - CbInfo, Timeout) - catch - exit:{noproc, _} -> - {error, ssl_not_started} - end. - -%%-------------------------------------------------------------------- --spec handshake(#sslsocket{}, timeout()) -> {ok, #sslsocket{}} | - {ok, #sslsocket{}, map()}| {error, reason()}. -%% -%% Description: Starts ssl handshake. -%%-------------------------------------------------------------------- -handshake(#sslsocket{pid = [Pid|_]} = Socket, Timeout) -> - case call(Pid, {start, Timeout}) of - connected -> - {ok, Socket}; - {ok, Ext} -> - {ok, Socket, no_records(Ext)}; - Error -> - Error - end. - -%%-------------------------------------------------------------------- --spec handshake(#sslsocket{}, {ssl_options(),#socket_options{}}, timeout()) -> - {ok, #sslsocket{}} | {ok, #sslsocket{}, map()} | {error, reason()}. -%% -%% Description: Starts ssl handshake with some new options -%%-------------------------------------------------------------------- -handshake(#sslsocket{pid = [Pid|_]} = Socket, SslOptions, Timeout) -> - case call(Pid, {start, SslOptions, Timeout}) of - connected -> - {ok, Socket}; - Error -> - Error - end. - -%%-------------------------------------------------------------------- --spec handshake_continue(#sslsocket{}, [ssl:tls_server_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()) -> - {ok, #sslsocket{}} | {error, reason()}. -%% -%% Description: Set the ssl process to own the accept socket -%%-------------------------------------------------------------------- -socket_control(Connection, Socket, Pid, Transport) -> - socket_control(Connection, Socket, Pid, Transport, undefined). - -%-------------------------------------------------------------------- --spec socket_control(tls_connection | dtls_connection, port(), [pid()], atom(), [pid()] | atom()) -> - {ok, #sslsocket{}} | {error, reason()}. -%%-------------------------------------------------------------------- -socket_control(Connection, Socket, Pids, Transport, udp_listener) -> - %% dtls listener process must have the socket control - {ok, Connection:socket(Pids, Transport, Socket, undefined)}; - -socket_control(tls_connection = Connection, Socket, [Pid|_] = Pids, Transport, Trackers) -> - case Transport:controlling_process(Socket, Pid) of - ok -> - {ok, Connection:socket(Pids, Transport, Socket, Trackers)}; - {error, Reason} -> - {error, Reason} - end; -socket_control(dtls_connection = Connection, {_, Socket}, [Pid|_] = Pids, Transport, Trackers) -> - case Transport:controlling_process(Socket, Pid) of - ok -> - {ok, Connection:socket(Pids, Transport, Socket, Trackers)}; - {error, Reason} -> - {error, Reason} - end. - - -%%==================================================================== -%% User events -%%==================================================================== - -%%-------------------------------------------------------------------- --spec send(pid(), iodata()) -> ok | {error, reason()}. -%% -%% Description: Sends data over the ssl connection -%%-------------------------------------------------------------------- -send(Pid, Data) -> - call(Pid, {application_data, - %% iolist_to_iovec should really - %% be called iodata_to_iovec() - erlang:iolist_to_iovec(Data)}). - -%%-------------------------------------------------------------------- --spec recv(pid(), integer(), timeout()) -> - {ok, binary() | list()} | {error, reason()}. -%% -%% Description: Receives data when active = false -%%-------------------------------------------------------------------- -recv(Pid, 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(), {close, Timeout::integer() | - {NewController::pid(), Timeout::integer()}}) -> - ok | {ok, port()} | {error, reason()}. -%% -%% Description: Close an ssl connection -%%-------------------------------------------------------------------- -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) -> - call(ConnectionPid, {shutdown, How}). - -%%-------------------------------------------------------------------- --spec new_user(pid(), pid()) -> ok | {error, reason()}. -%% -%% Description: Changes process that receives the messages when active = true -%% or once. -%%-------------------------------------------------------------------- -new_user(ConnectionPid, User) -> - call(ConnectionPid, {new_user, User}). - -%%-------------------------------------------------------------------- --spec negotiated_protocol(pid()) -> {ok, binary()} | {error, reason()}. -%% -%% Description: Returns the negotiated protocol -%%-------------------------------------------------------------------- -negotiated_protocol(ConnectionPid) -> - call(ConnectionPid, negotiated_protocol). - -%%-------------------------------------------------------------------- --spec get_opts(pid(), list()) -> {ok, list()} | {error, reason()}. -%% -%% Description: Same as inet:getopts/2 -%%-------------------------------------------------------------------- -get_opts(ConnectionPid, OptTags) -> - call(ConnectionPid, {get_opts, OptTags}). -%%-------------------------------------------------------------------- --spec set_opts(pid(), list()) -> ok | {error, reason()}. -%% -%% Description: Same as inet:setopts/2 -%%-------------------------------------------------------------------- -set_opts(ConnectionPid, Options) -> - call(ConnectionPid, {set_opts, Options}). - -%%-------------------------------------------------------------------- --spec peer_certificate(pid()) -> {ok, binary()| undefined} | {error, reason()}. -%% -%% Description: Returns the peer cert -%%-------------------------------------------------------------------- -peer_certificate(ConnectionPid) -> - call(ConnectionPid, peer_certificate). - -%%-------------------------------------------------------------------- --spec renegotiation(pid()) -> ok | {error, reason()}. -%% -%% Description: Starts a renegotiation of the ssl session. -%%-------------------------------------------------------------------- -renegotiation(ConnectionPid) -> - 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}). - -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()) -> - {ok, binary()} | {error, reason()} | {'EXIT', term()}. -%% -%% Description: use a ssl sessions TLS PRF to generate key material -%%-------------------------------------------------------------------- -prf(ConnectionPid, Secret, Label, Seed, WantedLength) -> - call(ConnectionPid, {prf, Secret, Label, Seed, WantedLength}). - - -%%==================================================================== -%% Alert and close handling -%%==================================================================== -handle_own_alert(Alert0, _, StateName, - #state{static_env = #static_env{role = Role, - protocol_cb = Connection}, - ssl_options = #{log_level := LogLevel}} = State) -> - try %% Try to tell the other side - send_alert(Alert0, StateName, State) - catch _:_ -> %% Can crash if we are in a uninitialized state - ignore - end, - try %% Try to tell the local user - Alert = Alert0#alert{role = Role}, - log_alert(LogLevel, Role, Connection:protocol_name(), StateName, Alert), - handle_normal_shutdown(Alert,StateName, State) - catch _:_ -> - ok - end, - {stop, {shutdown, own_alert}, State}. - -handle_normal_shutdown(Alert, StateName, #state{static_env = #static_env{role = Role, - socket = Socket, - transport_cb = Transport, - protocol_cb = Connection, - trackers = Trackers}, - handshake_env = #handshake_env{renegotiation = {false, first}}, - start_or_recv_from = StartFrom} = State) -> - Pids = Connection:pids(State), - alert_user(Pids, Transport, Trackers, Socket, StartFrom, Alert, Role, StateName, Connection); - -handle_normal_shutdown(Alert, StateName, #state{static_env = #static_env{role = Role, - socket = Socket, - transport_cb = Transport, - protocol_cb = Connection, - trackers = Trackers}, - connection_env = #connection_env{user_application = {_Mon, Pid}}, - socket_options = Opts, - start_or_recv_from = RecvFrom} = State) -> - Pids = Connection:pids(State), - alert_user(Pids, Transport, Trackers, Socket, StateName, Opts, Pid, RecvFrom, Alert, Role, StateName, Connection). - -handle_alert(#alert{level = ?FATAL} = Alert0, StateName, - #state{static_env = #static_env{role = Role, - socket = Socket, - host = Host, - port = Port, - trackers = Trackers, - transport_cb = Transport, - protocol_cb = Connection}, - connection_env = #connection_env{user_application = {_Mon, Pid}}, - ssl_options = #{log_level := LogLevel}, - start_or_recv_from = From, - session = Session, - socket_options = Opts} = State) -> - invalidate_session(Role, Host, Port, Session), - Alert = Alert0#alert{role = opposite_role(Role)}, - log_alert(LogLevel, Role, Connection:protocol_name(), - StateName, Alert), - Pids = Connection:pids(State), - alert_user(Pids, Transport, Trackers, Socket, StateName, Opts, Pid, From, Alert, Role, StateName, 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} = Alert0, - StateName, #state{static_env = #static_env{role = Role}} = State) -> - Alert = Alert0#alert{role = opposite_role(Role)}, - handle_normal_shutdown(Alert, StateName, State), - {stop,{shutdown, peer_close}, State}; -handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert0, StateName, - #state{static_env = #static_env{role = Role, - protocol_cb = Connection}, - handshake_env = #handshake_env{renegotiation = {true, internal}}, - ssl_options = #{log_level := LogLevel}} = State) -> - Alert = Alert0#alert{role = opposite_role(Role)}, - log_alert(LogLevel, Role, - Connection:protocol_name(), StateName, Alert), - 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}, - handshake_env = #handshake_env{renegotiation = {true, From}} = HsEnv, - ssl_options = #{log_level := LogLevel} - } = State0) -> - log_alert(LogLevel, 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{handshake_env = HsEnv#handshake_env{renegotiation = undefined}}); - -handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert, StateName, - #state{static_env = #static_env{role = Role, - protocol_cb = Connection}, - handshake_env = #handshake_env{renegotiation = {true, From}} = HsEnv, - ssl_options = #{log_level := LogLevel} - } = State0) -> - log_alert(LogLevel, 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{handshake_env = HsEnv#handshake_env{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 = #{log_level := LogLevel}} = State) -> - log_alert(LogLevel, Role, - Connection:protocol_name(), StateName, - Alert#alert{role = opposite_role(Role)}), - Connection:next_event(StateName, no_record, State). - -maybe_invalidate_session(undefined,_, _, _, _, _) -> - ok; -maybe_invalidate_session({3, 4},_, _, _, _, _) -> - ok; -maybe_invalidate_session({3, N}, Type, Role, Host, Port, Session) when N < 4 -> - maybe_invalidate_session(Type, Role, Host, Port, Session). - -%%==================================================================== -%% Data handling -%%==================================================================== -passive_receive(#state{user_data_buffer = {Front,BufferSize,Rear}, - %% Assert! Erl distribution uses active sockets - connection_env = #connection_env{erl_dist_handle = undefined}} - = State0, StateName, Connection, StartTimerAction) -> - case BufferSize of - 0 -> - Connection:next_event(StateName, no_record, State0, StartTimerAction); - _ -> - case read_application_data(State0, Front, BufferSize, Rear) of - {stop, _, _} = ShutdownError -> - ShutdownError; - {Record, State} -> - case State#state.start_or_recv_from of - undefined -> - %% Cancel recv timeout as data has been delivered - Connection:next_event(StateName, Record, State, - [{{timeout, recv}, infinity, timeout}]); - _ -> - Connection:next_event(StateName, Record, State, StartTimerAction) - end - end - end. - -read_application_data( - Data, - #state{ - user_data_buffer = {Front0,BufferSize0,Rear0}, - connection_env = #connection_env{erl_dist_handle = DHandle}} = State) -> - %% - Front = Front0, - BufferSize = BufferSize0 + byte_size(Data), - Rear = [Data|Rear0], - case DHandle of - undefined -> - read_application_data(State, Front, BufferSize, Rear); - _ -> - try read_application_dist_data(DHandle, Front, BufferSize, Rear) of - Buffer -> - {no_record, State#state{user_data_buffer = Buffer}} - catch error:_ -> - {stop,disconnect, - State#state{user_data_buffer = {Front,BufferSize,Rear}}} - end - end. - - -read_application_data(#state{ - socket_options = SocketOpts, - bytes_to_read = BytesToRead, - start_or_recv_from = RecvFrom} = State, Front, BufferSize, Rear) -> - read_application_data(State, Front, BufferSize, Rear, SocketOpts, RecvFrom, BytesToRead). - -%% Pick binary from queue front, if empty wait for more data -read_application_data(State, [Bin|Front], BufferSize, Rear, SocketOpts, RecvFrom, BytesToRead) -> - read_application_data_bin(State, Front, BufferSize, Rear, SocketOpts, RecvFrom, BytesToRead, Bin); -read_application_data(State, [] = Front, BufferSize, [] = Rear, SocketOpts, RecvFrom, BytesToRead) -> - 0 = BufferSize, % Assert - {no_record, State#state{socket_options = SocketOpts, - bytes_to_read = BytesToRead, - start_or_recv_from = RecvFrom, - user_data_buffer = {Front,BufferSize,Rear}}}; -read_application_data(State, [], BufferSize, Rear, SocketOpts, RecvFrom, BytesToRead) -> - [Bin|Front] = lists:reverse(Rear), - read_application_data_bin(State, Front, BufferSize, [], SocketOpts, RecvFrom, BytesToRead, Bin). - -read_application_data_bin(State, Front, BufferSize, Rear, SocketOpts, RecvFrom, BytesToRead, <<>>) -> - %% Done with this binary - get next - read_application_data(State, Front, BufferSize, Rear, SocketOpts, RecvFrom, BytesToRead); -read_application_data_bin(State, Front0, BufferSize0, Rear0, SocketOpts0, RecvFrom, BytesToRead, Bin0) -> - %% Decode one packet from a binary - case get_data(SocketOpts0, BytesToRead, Bin0) of - {ok, Data, Bin} -> % Send data - BufferSize = BufferSize0 - (byte_size(Bin0) - byte_size(Bin)), - read_application_data_deliver( - State, [Bin|Front0], BufferSize, Rear0, SocketOpts0, RecvFrom, Data); - {more, undefined} -> - %% We need more data, do not know how much - if - byte_size(Bin0) < BufferSize0 -> - %% We have more data in the buffer besides the first binary - concatenate all and retry - Bin = iolist_to_binary([Bin0,Front0|lists:reverse(Rear0)]), - read_application_data_bin( - State, [], BufferSize0, [], SocketOpts0, RecvFrom, BytesToRead, Bin); - true -> - %% All data is in the first binary, no use to retry - wait for more - {no_record, State#state{socket_options = SocketOpts0, - bytes_to_read = BytesToRead, - start_or_recv_from = RecvFrom, - user_data_buffer = {[Bin0|Front0],BufferSize0,Rear0}}} - end; - {more, Size} when Size =< BufferSize0 -> - %% We have a packet in the buffer - collect it in a binary and decode - {Data,Front,Rear} = iovec_from_front(Size - byte_size(Bin0), Front0, Rear0, [Bin0]), - Bin = iolist_to_binary(Data), - read_application_data_bin( - State, Front, BufferSize0, Rear, SocketOpts0, RecvFrom, BytesToRead, Bin); - {more, _Size} -> - %% We do not have a packet in the buffer - wait for more - {no_record, State#state{socket_options = SocketOpts0, - bytes_to_read = BytesToRead, - start_or_recv_from = RecvFrom, - user_data_buffer = {[Bin0|Front0],BufferSize0,Rear0}}}; - passive -> - {no_record, State#state{socket_options = SocketOpts0, - bytes_to_read = BytesToRead, - start_or_recv_from = RecvFrom, - user_data_buffer = {[Bin0|Front0],BufferSize0,Rear0}}}; - {error,_Reason} -> - %% Invalid packet in packet mode - #state{ - static_env = - #static_env{ - socket = Socket, - protocol_cb = Connection, - transport_cb = Transport, - trackers = Trackers}, - connection_env = - #connection_env{user_application = {_Mon, Pid}}} = State, - Buffer = iolist_to_binary([Bin0,Front0|lists:reverse(Rear0)]), - deliver_packet_error( - Connection:pids(State), Transport, Socket, SocketOpts0, - Buffer, Pid, RecvFrom, Trackers, Connection), - {stop, {shutdown, normal}, State#state{socket_options = SocketOpts0, - bytes_to_read = BytesToRead, - start_or_recv_from = RecvFrom, - user_data_buffer = {[Buffer],BufferSize0,[]}}} - end. - -read_application_data_deliver(State, Front, BufferSize, Rear, SocketOpts0, RecvFrom, Data) -> - #state{ - static_env = - #static_env{ - socket = Socket, - protocol_cb = Connection, - transport_cb = Transport, - trackers = Trackers}, - connection_env = - #connection_env{user_application = {_Mon, Pid}}} = State, - SocketOpts = - deliver_app_data( - Connection:pids(State), Transport, Socket, SocketOpts0, Data, Pid, RecvFrom, Trackers, Connection), - if - SocketOpts#socket_options.active =:= false -> - %% Passive mode, wait for active once or recv - {no_record, - State#state{ - user_data_buffer = {Front,BufferSize,Rear}, - start_or_recv_from = undefined, - bytes_to_read = undefined, - socket_options = SocketOpts - }}; - true -> %% Try to deliver more data - read_application_data(State, Front, BufferSize, Rear, SocketOpts, undefined, undefined) - end. - - -read_application_dist_data(DHandle, [Bin|Front], BufferSize, Rear) -> - read_application_dist_data(DHandle, Front, BufferSize, Rear, Bin); -read_application_dist_data(_DHandle, [] = Front, BufferSize, [] = Rear) -> - BufferSize = 0, - {Front,BufferSize,Rear}; -read_application_dist_data(DHandle, [], BufferSize, Rear) -> - [Bin|Front] = lists:reverse(Rear), - read_application_dist_data(DHandle, Front, BufferSize, [], Bin). -%% -read_application_dist_data(DHandle, Front0, BufferSize, Rear0, Bin0) -> - case Bin0 of - %% - %% START Optimization - %% It is cheaper to match out several packets in one match operation than to loop for each - <<SizeA:32, DataA:SizeA/binary, - SizeB:32, DataB:SizeB/binary, - SizeC:32, DataC:SizeC/binary, - SizeD:32, DataD:SizeD/binary, Rest/binary>> - when 0 < SizeA, 0 < SizeB, 0 < SizeC, 0 < SizeD -> - %% We have 4 complete packets in the first binary - erlang:dist_ctrl_put_data(DHandle, DataA), - erlang:dist_ctrl_put_data(DHandle, DataB), - erlang:dist_ctrl_put_data(DHandle, DataC), - erlang:dist_ctrl_put_data(DHandle, DataD), - read_application_dist_data( - DHandle, Front0, BufferSize - (4*4+SizeA+SizeB+SizeC+SizeD), Rear0, Rest); - <<SizeA:32, DataA:SizeA/binary, - SizeB:32, DataB:SizeB/binary, - SizeC:32, DataC:SizeC/binary, Rest/binary>> - when 0 < SizeA, 0 < SizeB, 0 < SizeC -> - %% We have 3 complete packets in the first binary - erlang:dist_ctrl_put_data(DHandle, DataA), - erlang:dist_ctrl_put_data(DHandle, DataB), - erlang:dist_ctrl_put_data(DHandle, DataC), - read_application_dist_data( - DHandle, Front0, BufferSize - (3*4+SizeA+SizeB+SizeC), Rear0, Rest); - <<SizeA:32, DataA:SizeA/binary, - SizeB:32, DataB:SizeB/binary, Rest/binary>> - when 0 < SizeA, 0 < SizeB -> - %% We have 2 complete packets in the first binary - erlang:dist_ctrl_put_data(DHandle, DataA), - erlang:dist_ctrl_put_data(DHandle, DataB), - read_application_dist_data( - DHandle, Front0, BufferSize - (2*4+SizeA+SizeB), Rear0, Rest); - %% END Optimization - %% - %% Basic one packet code path - <<Size:32, Data:Size/binary, Rest/binary>> -> - %% We have a complete packet in the first binary - 0 < Size andalso erlang:dist_ctrl_put_data(DHandle, Data), - read_application_dist_data(DHandle, Front0, BufferSize - (4+Size), Rear0, Rest); - <<Size:32, FirstData/binary>> when 4+Size =< BufferSize -> - %% We have a complete packet in the buffer - %% - fetch the missing content from the buffer front - {Data,Front,Rear} = iovec_from_front(Size - byte_size(FirstData), Front0, Rear0, [FirstData]), - 0 < Size andalso erlang:dist_ctrl_put_data(DHandle, Data), - read_application_dist_data(DHandle, Front, BufferSize - (4+Size), Rear); - <<Bin/binary>> -> - %% In OTP-21 the match context reuse optimization fails if we use Bin0 in recursion, so here we - %% match out the whole binary which will trick the optimization into keeping the match context - %% for the first binary contains complete packet code above - case Bin of - <<_Size:32, _InsufficientData/binary>> -> - %% We have a length field in the first binary but there is not enough data - %% in the buffer to form a complete packet - await more data - {[Bin|Front0],BufferSize,Rear0}; - <<IncompleteLengthField/binary>> when 4 < BufferSize -> - %% We do not have a length field in the first binary but the buffer - %% contains enough data to maybe form a packet - %% - fetch a tiny binary from the buffer front to complete the length field - {LengthField,Front,Rear} = - case IncompleteLengthField of - <<>> -> - iovec_from_front(4, Front0, Rear0, []); - _ -> - iovec_from_front( - 4 - byte_size(IncompleteLengthField), Front0, Rear0, [IncompleteLengthField]) - end, - LengthBin = iolist_to_binary(LengthField), - read_application_dist_data(DHandle, Front, BufferSize, Rear, LengthBin); - <<IncompleteLengthField/binary>> -> - %% We do not have enough data in the buffer to even form a length field - await more data - case IncompleteLengthField of - <<>> -> - {Front0,BufferSize,Rear0}; - _ -> - {[IncompleteLengthField|Front0],BufferSize,Rear0} - end - end - end. - -iovec_from_front(0, Front, Rear, Acc) -> - {lists:reverse(Acc),Front,Rear}; -iovec_from_front(Size, [], Rear, Acc) -> - case Rear of - %% Avoid lists:reverse/1 for simple cases. - %% Case clause for [] to avoid infinite loop. - [_] -> - iovec_from_front(Size, Rear, [], Acc); - [Bin2,Bin1] -> - iovec_from_front(Size, [Bin1,Bin2], [], Acc); - [Bin3,Bin2,Bin1] -> - iovec_from_front(Size, [Bin1,Bin2,Bin3], [], Acc); - [_,_,_|_] = Rear -> - iovec_from_front(Size, lists:reverse(Rear), [], Acc) - end; -iovec_from_front(Size, [Bin|Front], Rear, []) -> - case Bin of - <<Last:Size/binary>> -> % Just enough - {[Last],Front,Rear}; - <<Last:Size/binary, Rest/binary>> -> % More than enough, split here - {[Last],[Rest|Front],Rear}; - <<>> -> % Not enough, skip empty binaries - iovec_from_front(Size, Front, Rear, []); - <<_/binary>> -> % Not enough - BinSize = byte_size(Bin), - iovec_from_front(Size - BinSize, Front, Rear, [Bin]) - end; -iovec_from_front(Size, [Bin|Front], Rear, Acc) -> - case Bin of - <<Last:Size/binary>> -> % Just enough - {lists:reverse(Acc, [Last]),Front,Rear}; - <<Last:Size/binary, Rest/binary>> -> % More than enough, split here - {lists:reverse(Acc, [Last]),[Rest|Front],Rear}; - <<>> -> % Not enough, skip empty binaries - iovec_from_front(Size, Front, Rear, Acc); - <<_/binary>> -> % Not enough - BinSize = byte_size(Bin), - iovec_from_front(Size - BinSize, Front, Rear, [Bin|Acc]) - 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, ProtoExt, Protocol0, - #state{session = #session{session_id = OldId}, - handshake_env = #handshake_env{negotiated_protocol = CurrentProtocol} = HsEnv, - connection_env = #connection_env{negotiated_version = ReqVersion} = CEnv} = State0) -> - #{key_exchange := KeyAlgorithm} = - ssl_cipher_format:suite_bin_to_map(CipherSuite), - - PremasterSecret = make_premaster_secret(ReqVersion, KeyAlgorithm), - - {ExpectNPN, Protocol} = case Protocol0 of - undefined -> - - {false, CurrentProtocol}; - _ -> - {ProtoExt =:= npn, Protocol0} - end, - - State = State0#state{connection_states = ConnectionStates, - handshake_env = HsEnv#handshake_env{kex_algorithm = KeyAlgorithm, - premaster_secret = PremasterSecret, - expecting_next_protocol_negotiation = ExpectNPN, - negotiated_protocol = Protocol}, - connection_env = CEnv#connection_env{negotiated_version = Version}}, - - case ssl_session:is_new(OldId, NewId) of - true -> - handle_new_session(NewId, CipherSuite, Compression, - State#state{connection_states = ConnectionStates}); - false -> - handle_resumed_session(NewId, - State#state{connection_states = ConnectionStates}) - end. - -%%-------------------------------------------------------------------- --spec ssl_config(ssl_options(), client | server, #state{}) -> #state{}. -%%-------------------------------------------------------------------- -ssl_config(Opts, Role, #state{static_env = InitStatEnv0, - handshake_env = HsEnv, - connection_env = CEnv} = 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(Opts, Role), - TimeStamp = erlang:monotonic_time(), - Session = State0#state.session, - - 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 - }, - handshake_env = HsEnv#handshake_env{diffie_hellman_params = DHParams}, - connection_env = CEnv#connection_env{private_key = Key}, - ssl_options = Opts}. - -%%==================================================================== -%% 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(). -%%-------------------------------------------------------------------- - -init({call, From}, {start, Timeout}, State0, Connection) -> - Connection:next_event(hello, no_record, State0#state{start_or_recv_from = From}, - [{{timeout, handshake}, Timeout, close}]); -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, Role, 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}}]}. - -%%-------------------------------------------------------------------- --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{connection_env = #connection_env{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{static_env = #static_env{role = Role}, - handshake_env = #handshake_env{hello = Hello}, - ssl_options = Options0} = State0, _Connection) -> - Options = ssl:handle_options(NewOptions, Role, Options0#{handshake => full}), - State = ssl_config(Options, Role, State0), - {next_state, hello, State#state{start_or_recv_from = From}, - [{next_event, internal, Hello}, {{timeout, handshake}, Timeout, close}]}; -user_hello(_, _, _, _) -> - {keep_state_and_data, [postpone]}. - -%%-------------------------------------------------------------------- --spec abbreviated(gen_statem:event_type(), - #hello_request{} | #finished{} | term(), - #state{}, tls_connection | dtls_connection) -> - gen_statem:state_function_result(). -%%-------------------------------------------------------------------- -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}, - handshake_env = #handshake_env{tls_handshake_history = Hist, - expecting_finished = true} = HsEnv, - connection_env = #connection_env{negotiated_version = Version}, - session = #session{master_secret = MasterSecret}, - connection_states = ConnectionStates0} = - State0, Connection) -> - case ssl_handshake:verify_connection(ssl:tls_version(Version), Finished, client, - get_current_prf(ConnectionStates0, write), - MasterSecret, Hist) of - verified -> - ConnectionStates = - ssl_record:set_client_verify_data(current_both, Data, ConnectionStates0), - {Record, State} = prepare_connection(State0#state{connection_states = ConnectionStates, - handshake_env = HsEnv#handshake_env{expecting_finished = false}}, Connection), - Connection:next_event(connection, Record, State, [{{timeout, handshake}, infinity, close}]); - #alert{} = Alert -> - handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0) - end; -abbreviated(internal, #finished{verify_data = Data} = Finished, - #state{static_env = #static_env{role = client}, - handshake_env = #handshake_env{tls_handshake_history = Hist0}, - connection_env = #connection_env{negotiated_version = Version}, - session = #session{master_secret = MasterSecret}, - connection_states = ConnectionStates0} = State0, Connection) -> - case ssl_handshake:verify_connection(ssl:tls_version(Version), Finished, server, - get_pending_prf(ConnectionStates0, write), - MasterSecret, Hist0) of - verified -> - ConnectionStates1 = - ssl_record:set_server_verify_data(current_read, Data, ConnectionStates0), - {#state{handshake_env = HsEnv} = State1, Actions} = - finalize_handshake(State0#state{connection_states = ConnectionStates1}, - ?FUNCTION_NAME, Connection), - {Record, State} = prepare_connection(State1#state{handshake_env = HsEnv#handshake_env{expecting_finished = false}}, Connection), - Connection:next_event(connection, Record, State, [{{timeout, handshake}, infinity, close} | 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(internal, #next_protocol{selected_protocol = SelectedProtocol}, - #state{static_env = #static_env{role = server}, - handshake_env = #handshake_env{expecting_next_protocol_negotiation = true} = HsEnv} = State, - Connection) -> - Connection:next_event(?FUNCTION_NAME, no_record, - State#state{handshake_env = HsEnv#handshake_env{negotiated_protocol = SelectedProtocol, - expecting_next_protocol_negotiation = false}}); -abbreviated(internal, - #change_cipher_spec{type = <<1>>}, - #state{connection_states = ConnectionStates0, - handshake_env = HsEnv} = State, Connection) -> - ConnectionStates1 = - ssl_record:activate_pending_connection_state(ConnectionStates0, read, Connection), - Connection:next_event(?FUNCTION_NAME, no_record, State#state{connection_states = - ConnectionStates1, - handshake_env = HsEnv#handshake_env{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 wait_ocsp_stapling(gen_statem:event_type(), - #certificate{} | #certificate_status{} | term(), - #state{}, tls_connection | dtls_connection) -> - gen_statem:state_function_result(). -%%-------------------------------------------------------------------- -wait_ocsp_stapling(internal, #certificate{}, State, Connection) -> - %% Postpone message, should be handled in certify after receiving staple message - Connection:next_event(?FUNCTION_NAME, no_record, State, [{postpone, true}]); -%% Receive OCSP staple message -wait_ocsp_stapling(internal, #certificate_status{} = CertStatus, - #state{handshake_env = #handshake_env{ - ocsp_stapling_state = OcspState} = HsEnv} = State, - Connection) -> - Connection:next_event(certify, no_record, State#state{handshake_env = HsEnv#handshake_env{ocsp_stapling_state = - OcspState#{ocsp_expect => stapled, - ocsp_response => CertStatus}}}); -%% Server did not send OCSP staple message -wait_ocsp_stapling(internal, Msg, #state{handshake_env = #handshake_env{ - ocsp_stapling_state = OcspState} = HsEnv} = State, Connection) - when is_record(Msg, server_key_exchange) orelse - is_record(Msg, hello_request) orelse - is_record(Msg, certificate_request) orelse - is_record(Msg, server_hello_done) orelse - is_record(Msg, client_key_exchange) -> - Connection:next_event(certify, no_record, State#state{handshake_env = - HsEnv#handshake_env{ocsp_stapling_state = OcspState#{ocsp_expect => undetermined}}}, - [{postpone, true}]); -wait_ocsp_stapling(Type, Msg, State, Connection) -> - handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection). - -%%-------------------------------------------------------------------- --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_statem:state_function_result(). -%%-------------------------------------------------------------------- -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}, - connection_env = #connection_env{negotiated_version = Version}, - ssl_options = #{verify := verify_peer, - fail_if_no_peer_cert := true}} = - State, _) -> - Alert = ?ALERT_REC(?FATAL,?HANDSHAKE_FAILURE, no_client_certificate_provided), - handle_own_alert(Alert, Version, ?FUNCTION_NAME, State); -certify(internal, #certificate{asn1_certificates = []}, - #state{static_env = #static_env{role = server}, - ssl_options = #{verify := verify_peer, - fail_if_no_peer_cert := false}} = - State0, Connection) -> - Connection:next_event(?FUNCTION_NAME, no_record, State0#state{client_certificate_requested = false}); -certify(internal, #certificate{}, - #state{static_env = #static_env{role = server}, - connection_env = #connection_env{negotiated_version = Version}, - 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{}, - #state{handshake_env = #handshake_env{ - ocsp_stapling_state = #{ocsp_expect := staple}}} = State, Connection) -> - Connection:next_event(wait_ocsp_stapling, no_record, State, [{postpone, true}]); -certify(internal, #certificate{asn1_certificates = [Peer|_]} = Cert, #state{static_env = - #static_env{ - role = Role, - host = Host, - cert_db = CertDbHandle, - cert_db_ref = CertDbRef, - crl_db = CRLDbInfo}, - handshake_env = #handshake_env{ - ocsp_stapling_state = #{ocsp_expect := Status} = OcspState}, - connection_env = #connection_env{ - negotiated_version = Version}, - ssl_options = Opts} = State, Connection) when Status =/= staple -> - OcspInfo = ocsp_info(OcspState, Opts, Peer), - case ssl_handshake:certify(Cert, CertDbHandle, CertDbRef, - Opts, CRLDbInfo, Role, Host, - ensure_tls(Version), OcspInfo) of - {PeerCert, PublicKeyInfo} -> - handle_peer_cert(Role, PeerCert, PublicKeyInfo, - State#state{client_certificate_requested = false}, Connection, []); - #alert{} = Alert -> - handle_own_alert(Alert, Version, ?FUNCTION_NAME, State) - end; -certify(internal, #server_key_exchange{exchange_keys = Keys}, - #state{static_env = #static_env{role = client}, - handshake_env = #handshake_env{kex_algorithm = KexAlg, - public_key_info = PubKeyInfo} = HsEnv, - connection_env = #connection_env{negotiated_version = Version}, - session = Session, - connection_states = ConnectionStates} = State, Connection) - when KexAlg == dhe_dss; - KexAlg == dhe_rsa; - KexAlg == ecdhe_rsa; - KexAlg == ecdhe_ecdsa; - KexAlg == dh_anon; - KexAlg == ecdh_anon; - KexAlg == psk; - KexAlg == dhe_psk; - KexAlg == ecdhe_psk; - KexAlg == rsa_psk; - KexAlg == srp_dss; - KexAlg == srp_rsa; - KexAlg == srp_anon -> - - Params = ssl_handshake:decode_server_key(Keys, KexAlg, ssl:tls_version(Version)), - - %% Use negotiated value if TLS-1.2 otherwhise return default - HashSign = negotiated_hashsign(Params#server_key_params.hashsign, KexAlg, PubKeyInfo, ssl:tls_version(Version)), - - case is_anonymous(KexAlg) of - true -> - calculate_secret(Params#server_key_params.params, - State#state{handshake_env = HsEnv#handshake_env{hashsign_algorithm = HashSign}}, Connection); - false -> - 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{handshake_env = HsEnv#handshake_env{hashsign_algorithm = HashSign}, - session = session_handle_params(Params#server_key_params.params, Session)}, - Connection); - false -> - handle_own_alert(?ALERT_REC(?FATAL, ?DECRYPT_ERROR), - Version, ?FUNCTION_NAME, State) - end - end; -certify(internal, #certificate_request{}, - #state{static_env = #static_env{role = client}, - handshake_env = #handshake_env{kex_algorithm = KexAlg}, - connection_env = #connection_env{negotiated_version = Version}} = State, _) - when KexAlg == dh_anon; - KexAlg == ecdh_anon; - KexAlg == psk; - KexAlg == dhe_psk; - KexAlg == ecdhe_psk; - KexAlg == rsa_psk; - KexAlg == srp_dss; - KexAlg == srp_rsa; - KexAlg == 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}, - handshake_env = HsEnv, - connection_env = #connection_env{negotiated_version = Version}, - session = #session{own_certificate = Cert}, - ssl_options = #{signature_algs := SupportedHashSigns}} = 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, - handshake_env = HsEnv#handshake_env{cert_hashsign_algorithm = NegotiatedHashSign}}) - end; -%% PSK and RSA_PSK might bypass the Server-Key-Exchange -certify(internal, #server_hello_done{}, - #state{static_env = #static_env{role = client}, - session = #session{master_secret = undefined}, - connection_env = #connection_env{negotiated_version = Version}, - handshake_env = #handshake_env{kex_algorithm = KexAlg, - premaster_secret = undefined, - server_psk_identity = PSKIdentity} = HsEnv, - ssl_options = #{user_lookup_fun := PSKLookup}} = State0, Connection) - when KexAlg == psk -> - case ssl_handshake:premaster_secret({KexAlg, PSKIdentity}, PSKLookup) of - #alert{} = Alert -> - handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0); - PremasterSecret -> - State = master_secret(PremasterSecret, - State0#state{handshake_env = - HsEnv#handshake_env{premaster_secret = PremasterSecret}}), - client_certify_and_key_exchange(State, Connection) - end; -certify(internal, #server_hello_done{}, - #state{static_env = #static_env{role = client}, - connection_env = #connection_env{negotiated_version = {Major, Minor}} = Version, - handshake_env = #handshake_env{kex_algorithm = KexAlg, - premaster_secret = undefined, - server_psk_identity = PSKIdentity} = HsEnv, - session = #session{master_secret = undefined}, - ssl_options = #{user_lookup_fun := PSKLookup}} = State0, Connection) - when KexAlg == rsa_psk -> - Rand = ssl_cipher:random_bytes(?NUM_OF_PREMASTERSECRET_BYTES-2), - RSAPremasterSecret = <<?BYTE(Major), ?BYTE(Minor), Rand/binary>>, - case ssl_handshake:premaster_secret({KexAlg, PSKIdentity}, PSKLookup, - RSAPremasterSecret) of - #alert{} = Alert -> - handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0); - PremasterSecret -> - State = master_secret(PremasterSecret, - State0#state{handshake_env = - HsEnv#handshake_env{premaster_secret = RSAPremasterSecret}}), - client_certify_and_key_exchange(State, Connection) - end; -%% Master secret was determined with help of server-key exchange msg -certify(internal, #server_hello_done{}, - #state{static_env = #static_env{role = client}, - connection_env = #connection_env{negotiated_version = Version}, - handshake_env = #handshake_env{premaster_secret = undefined}, - session = #session{master_secret = MasterSecret} = Session, - connection_states = ConnectionStates0} = 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 -> - handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0) - end; -%% Master secret is calculated from premaster_secret -certify(internal, #server_hello_done{}, - #state{static_env = #static_env{role = client}, - connection_env = #connection_env{negotiated_version = Version}, - handshake_env = #handshake_env{premaster_secret = PremasterSecret}, - session = Session0, - connection_states = ConnectionStates0} = State0, Connection) -> - case ssl_handshake:master_secret(ssl:tls_version(Version), PremasterSecret, - ConnectionStates0, client) of - {MasterSecret, ConnectionStates} -> - Session = Session0#session{master_secret = MasterSecret}, - State = State0#state{connection_states = ConnectionStates, - session = Session}, - client_certify_and_key_exchange(State, Connection); - #alert{} = Alert -> - handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0) - end; -certify(internal = Type, #client_key_exchange{} = Msg, - #state{static_env = #static_env{role = server}, - client_certificate_requested = true, - ssl_options = #{fail_if_no_peer_cert := true}} = State, - Connection) -> - %% We expect a certificate here - handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection); -certify(internal, #client_key_exchange{exchange_keys = Keys}, - State = #state{handshake_env = #handshake_env{kex_algorithm = KeyAlg}, - connection_env = #connection_env{negotiated_version = Version}}, Connection) -> - try - certify_client_key_exchange(ssl_handshake:decode_client_key(Keys, KeyAlg, ssl:tls_version(Version)), - State, Connection) - catch - #alert{} = Alert -> - handle_own_alert(Alert, Version, ?FUNCTION_NAME, State) - end; -certify(Type, Msg, State, Connection) -> - handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection). - -%%-------------------------------------------------------------------- --spec cipher(gen_statem:event_type(), - #hello_request{} | #certificate_verify{} | #finished{} | term(), - #state{}, tls_connection | dtls_connection) -> - gen_statem:state_function_result(). -%%-------------------------------------------------------------------- -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}, - handshake_env = #handshake_env{tls_handshake_history = Hist, - kex_algorithm = KexAlg, - public_key_info = PubKeyInfo} = HsEnv, - connection_env = #connection_env{negotiated_version = Version}, - session = #session{master_secret = MasterSecret} - } = State, Connection) -> - - TLSVersion = ssl:tls_version(Version), - %% Use negotiated value if TLS-1.2 otherwhise return default - HashSign = negotiated_hashsign(CertHashSign, KexAlg, PubKeyInfo, TLSVersion), - case ssl_handshake:certificate_verify(Signature, PubKeyInfo, - TLSVersion, HashSign, MasterSecret, Hist) of - valid -> - Connection:next_event(?FUNCTION_NAME, no_record, - State#state{handshake_env = HsEnv#handshake_env{cert_hashsign_algorithm = HashSign}}); - #alert{} = Alert -> - handle_own_alert(Alert, Version, ?FUNCTION_NAME, State) - end; -%% client must send a next protocol message if we are expecting it -cipher(internal, #finished{}, - #state{static_env = #static_env{role = server}, - handshake_env = #handshake_env{expecting_next_protocol_negotiation = true, - negotiated_protocol = undefined}, - connection_env = #connection_env{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, - trackers = Trackers}, - handshake_env = #handshake_env{tls_handshake_history = Hist, - expecting_finished = true} = HsEnv, - connection_env = #connection_env{negotiated_version = Version}, - session = #session{master_secret = MasterSecret} - = Session0, - ssl_options = SslOpts, - connection_states = ConnectionStates0} = State, Connection) -> - case ssl_handshake:verify_connection(ssl:tls_version(Version), Finished, - opposite_role(Role), - get_current_prf(ConnectionStates0, read), - MasterSecret, Hist) of - verified -> - Session = handle_session(Role, SslOpts, Host, Port, Trackers, Session0), - cipher_role(Role, Data, Session, - State#state{handshake_env = HsEnv#handshake_env{expecting_finished = false}}, Connection); - #alert{} = Alert -> - 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(internal, #next_protocol{selected_protocol = SelectedProtocol}, - #state{static_env = #static_env{role = server}, - handshake_env = #handshake_env{expecting_finished = true, - expecting_next_protocol_negotiation = true} = HsEnv} = State, Connection) -> - Connection:next_event(?FUNCTION_NAME, no_record, - State#state{handshake_env = HsEnv#handshake_env{negotiated_protocol = SelectedProtocol, - expecting_next_protocol_negotiation = false}}); -cipher(internal, #change_cipher_spec{type = <<1>>}, #state{handshake_env = HsEnv, connection_states = ConnectionStates0} = - State, Connection) -> - ConnectionStates = - ssl_record:activate_pending_connection_state(ConnectionStates0, read, Connection), - Connection:next_event(?FUNCTION_NAME, no_record, State#state{handshake_env = HsEnv#handshake_env{expecting_finished = true}, - connection_states = ConnectionStates}); -cipher(Type, Msg, State, Connection) -> - handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection). - -%%-------------------------------------------------------------------- --spec connection(gen_statem:event_type(), term(), - #state{}, tls_connection | dtls_connection) -> - gen_statem:state_function_result(). -%%-------------------------------------------------------------------- -connection({call, RecvFrom}, {recv, N, Timeout}, - #state{static_env = #static_env{protocol_cb = Connection}, - socket_options = - #socket_options{active = false}} = State0, Connection) -> - passive_receive(State0#state{bytes_to_read = N, - start_or_recv_from = RecvFrom}, ?FUNCTION_NAME, Connection, - [{{timeout, recv}, Timeout, timeout}]); - -connection({call, From}, renegotiate, #state{static_env = #static_env{protocol_cb = Connection}, - handshake_env = HsEnv} = State, - Connection) -> - Connection:renegotiate(State#state{handshake_env = HsEnv#handshake_env{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{handshake_env = #handshake_env{alpn = undefined, - negotiated_protocol = undefined}} = State, _) -> - hibernate_after(?FUNCTION_NAME, State, [{reply, From, {error, protocol_not_negotiated}}]); -connection({call, From}, negotiated_protocol, - #state{handshake_env = #handshake_env{alpn = undefined, - negotiated_protocol = SelectedProtocol}} = State, _) -> - hibernate_after(?FUNCTION_NAME, State, - [{reply, From, {ok, SelectedProtocol}}]); -connection({call, From}, negotiated_protocol, - #state{handshake_env = #handshake_env{alpn = SelectedProtocol, - negotiated_protocol = undefined}} = 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}, - handshake_env = HsEnv, - connection_states = ConnectionStates} - = State, Connection) -> - Connection:renegotiate(State#state{handshake_env = HsEnv#handshake_env{renegotiation = {true, internal}}, - connection_states = ConnectionStates#{current_write => WriteState}}, []); -connection(cast, {dist_handshake_complete, DHandle}, - #state{ssl_options = #{erl_dist := true}, - connection_env = CEnv, - socket_options = SockOpts} = State0, Connection) -> - process_flag(priority, normal), - State1 = - State0#state{ - socket_options = SockOpts#socket_options{active = true}, - connection_env = CEnv#connection_env{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, RecvFrom}, #state{start_or_recv_from = RecvFrom} = State, Connection) -> - passive_receive(State, ?FUNCTION_NAME, Connection, []); -connection(Type, Msg, State, Connection) -> - handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection). - -%%-------------------------------------------------------------------- --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). - -%%-------------------------------------------------------------------- -%% Event handling functions called by state functions to handle -%% common or unexpected events for the state. -%%-------------------------------------------------------------------- -handle_common_event(internal, {handshake, {#hello_request{} = Handshake, _}}, connection = StateName, - #state{static_env = #static_env{role = client}, - handshake_env = HsEnv} = State, _) -> - %% Should not be included in handshake history - {next_state, StateName, State#state{handshake_env = HsEnv#handshake_env{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{handshake_env = #handshake_env{tls_handshake_history = Hist0}, - connection_env = #connection_env{negotiated_version = Version}} = 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 - case handle_sni_extension(PossibleSNI, State0) of - #state{handshake_env = HsEnv} = State -> - Hist = ssl_handshake:update_handshake_history(Hist0, Raw), - {next_state, StateName, State#state{handshake_env = HsEnv#handshake_env{tls_handshake_history = Hist}}, - [{next_event, internal, Handshake}]}; - #alert{} = Alert -> - handle_own_alert(Alert, Version, StateName, State0) - end; -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{connection_env = #connection_env{negotiated_version = Version}} = State, _) -> - handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE), Version, - StateName, State); -handle_common_event({timeout, handshake}, close, _StateName, #state{start_or_recv_from = StartFrom} = State, _) -> - {stop_and_reply, - {shutdown, user_timeout}, - {reply, StartFrom, {error, timeout}}, State#state{start_or_recv_from = undefined}}; -handle_common_event({timeout, recv}, timeout, StateName, #state{start_or_recv_from = RecvFrom} = State, _) -> - {next_state, StateName, State#state{start_or_recv_from = undefined, - bytes_to_read = undefined}, [{reply, RecvFrom, {error, timeout}}]}; -handle_common_event(internal, {recv, RecvFrom}, StateName, #state{start_or_recv_from = RecvFrom}, _) when - StateName =/= connection -> - {keep_state_and_data, [postpone]}; -handle_common_event(Type, Msg, StateName, #state{connection_env = - #connection_env{negotiated_version = Version}} = State, - _) -> - Alert = ?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE, {unexpected_msg, {Type,Msg}}), - handle_own_alert(Alert, Version, StateName, State). - -handle_call({application_data, _Data}, _, _, _, _) -> - %% In renegotiation priorities handshake, send data when handshake is finished - {keep_state_and_data, [postpone]}; -handle_call({close, _} = Close, From, StateName, #state{connection_env = CEnv} = 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{connection_env = CEnv#connection_env{terminated = true}}}; -handle_call({shutdown, read_write = How}, From, StateName, - #state{static_env = #static_env{transport_cb = Transport, - socket = Socket}, - connection_env = CEnv} = State, _) -> - try send_alert(?ALERT_REC(?WARNING, ?CLOSE_NOTIFY), - StateName, State) of - _ -> - case Transport:shutdown(Socket, How) of - ok -> - {next_state, StateName, State#state{connection_env = - CEnv#connection_env{terminated = true}}, - [{reply, From, ok}]}; - Error -> - {stop_and_reply, {shutdown, normal}, {reply, From, Error}, - State#state{connection_env = CEnv#connection_env{terminated = true}}} - end - catch - throw:Return -> - Return - 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 -> - {next_state, StateName, State, [{reply, From, ok}]}; - Error -> - {stop_and_reply, {shutdown, normal}, {reply, From, Error}, State} - end; -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. - {next_state, StateName, State#state{bytes_to_read = N, start_or_recv_from = RecvFrom}, - [{next_event, internal, {recv, RecvFrom}} , {{timeout, recv}, Timeout, timeout}]}; -handle_call({new_user, User}, From, StateName, - State = #state{connection_env = #connection_env{user_application = {OldMon, _}} = CEnv}, _) -> - NewMon = erlang:monitor(process, User), - erlang:demonitor(OldMon, [flush]), - {next_state, StateName, State#state{connection_env = CEnv#connection_env{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, - trackers = Trackers}, - connection_env = - #connection_env{user_application = {_Mon, Pid}}, - socket_options = Opts1 - } = State0, Connection) -> - {Reply, Opts} = set_socket_opts(Connection, Transport, Socket, Opts0, Opts1, []), - case {proplists:lookup(active, Opts0), Opts} of - {{_, N}, #socket_options{active=false}} when is_integer(N) -> - send_user( - Pid, - format_passive( - Connection:pids(State0), Transport, Socket, Trackers, Connection)); - _ -> - ok - end, - State = State0#state{socket_options = Opts}, - handle_active_option(Opts#socket_options.active, StateName, From, Reply, State); - -handle_call(renegotiate, From, StateName, _, _) when StateName =/= connection -> - {keep_state_and_data, [{reply, From, {error, already_renegotiating}}]}; - -handle_call({prf, Secret, Label, Seed, WantedLength}, From, _, - #state{connection_states = ConnectionStates, - connection_env = #connection_env{negotiated_version = Version}}, _) -> - #{security_parameters := SecParams} = - ssl_record:current_connection_state(ConnectionStates, read), - #security_parameters{master_secret = MasterSecret, - client_random = ClientRandom, - server_random = ServerRandom, - prf_algorithm = PRFAlgorithm} = SecParams, - Reply = try - SecretToUse = case Secret of - _ when is_binary(Secret) -> Secret; - master_secret -> MasterSecret - end, - SeedToUse = lists:reverse( - lists:foldl(fun(X, Acc) when is_binary(X) -> [X|Acc]; - (client_random, Acc) -> [ClientRandom|Acc]; - (server_random, Acc) -> [ServerRandom|Acc] - end, [], Seed)), - ssl_handshake:prf(ssl:tls_version(Version), PRFAlgorithm, SecretToUse, Label, SeedToUse, WantedLength) - catch - exit:_ -> {error, badarg}; - error:Reason -> {error, Reason} - end, - {keep_state_and_data, [{reply, From, Reply}]}; -handle_call(_,_,_,_,_) -> - {keep_state_and_data, [postpone]}. - -handle_info({ErrorTag, Socket, econnaborted}, StateName, - #state{static_env = #static_env{role = Role, - host = Host, - port = Port, - socket = Socket, - transport_cb = Transport, - error_tag = ErrorTag, - trackers = Trackers, - protocol_cb = Connection}, - handshake_env = #handshake_env{renegotiation = Type}, - connection_env = #connection_env{negotiated_version = Version}, - session = Session, - start_or_recv_from = StartFrom - } = State) when StateName =/= connection -> - - maybe_invalidate_session(Version, Type, Role, Host, Port, Session), - Pids = Connection:pids(State), - alert_user(Pids, Transport, Trackers,Socket, - StartFrom, ?ALERT_REC(?FATAL, ?CLOSE_NOTIFY), Role, StateName, Connection), - {stop, {shutdown, normal}, State}; - -handle_info({ErrorTag, Socket, Reason}, StateName, #state{static_env = #static_env{ - role = Role, - socket = Socket, - error_tag = ErrorTag}, - ssl_options = #{log_level := Level}} = State) -> - ssl_logger:log(info, Level, #{description => "Socket error", - reason => [{error_tag, ErrorTag}, {description, Reason}]}, ?LOCATION), - Alert = ?ALERT_REC(?FATAL, ?CLOSE_NOTIFY, {transport_error, Reason}), - handle_normal_shutdown(Alert#alert{role = Role}, StateName, State), - {stop, {shutdown,normal}, State}; - -handle_info({'DOWN', MonitorRef, _, _, Reason}, _, - #state{connection_env = #connection_env{user_application = {MonitorRef, _Pid}}, - ssl_options = #{erl_dist := true}}) -> - {stop, {shutdown, Reason}}; -handle_info({'DOWN', MonitorRef, _, _, _}, _, - #state{connection_env = #connection_env{user_application = {MonitorRef, _Pid}}}) -> - {stop, {shutdown, normal}}; -handle_info({'EXIT', Pid, _Reason}, StateName, - #state{connection_env = #connection_env{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{static_env = #static_env{socket = Socket}} = State) -> - %% Handle as transport close" - {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{handshake_env = HsEnv} = State) -> - {next_state, StateName, State#state{handshake_env = HsEnv#handshake_env{allow_renegotiate = true}}}; - -handle_info(Msg, StateName, #state{static_env = #static_env{socket = Socket, error_tag = ErrorTag}, - ssl_options = #{log_level := Level}} = State) -> - ssl_logger:log(notice, Level, #{description => "Unexpected INFO message", - reason => [{message, Msg}, {socket, Socket}, - {error_tag, ErrorTag}]}, ?LOCATION), - {next_state, StateName, State}. - -%%==================================================================== -%% general gen_statem callbacks -%%==================================================================== -terminate(_, _, #state{connection_env = #connection_env{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 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} = Reason, - _StateName, #state{static_env = #static_env{protocol_cb = Connection, - socket = Socket, - transport_cb = Transport}} = State) -> - handle_trusted_certs_db(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), - case application:get_env(ssl, alert_timeout) of - {ok, Timeout} when is_integer(Timeout) -> - Connection:close({timeout, Timeout}, Socket, Transport, undefined, undefined); - _ -> - Connection:close({timeout, ?DEFAULT_TIMEOUT}, Socket, Transport, undefined, undefined) - end; -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 = #{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), - Connection:close(Reason, Socket, Transport, undefined, undefined). - -format_status(normal, [_, StateName, State]) -> - [{data, [{"State", {StateName, State}}]}]; -format_status(terminate, [_, StateName, State]) -> - SslOptions = (State#state.ssl_options), - NewOptions = SslOptions#{password => ?SECRET_PRINTOUT, - cert => ?SECRET_PRINTOUT, - cacerts => ?SECRET_PRINTOUT, - key => ?SECRET_PRINTOUT, - dh => ?SECRET_PRINTOUT, - psk_identity => ?SECRET_PRINTOUT, - srp_identity => ?SECRET_PRINTOUT}, - [{data, [{"State", {StateName, State#state{connection_states = ?SECRET_PRINTOUT, - protocol_buffers = ?SECRET_PRINTOUT, - user_data_buffer = ?SECRET_PRINTOUT, - handshake_env = ?SECRET_PRINTOUT, - connection_env = ?SECRET_PRINTOUT, - session = ?SECRET_PRINTOUT, - ssl_options = NewOptions, - flight_buffer = ?SECRET_PRINTOUT} - }}]}]. - -%%-------------------------------------------------------------------- -%%% Internal functions -%%-------------------------------------------------------------------- -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}, - handshake_env = #handshake_env{sni_hostname = SNIHostname, - resumption = Resumption}, - session = #session{session_id = SessionId, - cipher_suite = CipherSuite, - srp_username = SrpUsername, - ecc = ECCCurve}, - connection_states = #{current_write := CurrentWrite}, - connection_env = #connection_env{negotiated_version = {_,_} = Version}, - ssl_options = Opts}) -> - RecordCB = record_cb(Connection), - CipherSuiteDef = #{key_exchange := KexAlg} = ssl_cipher_format:suite_bin_to_map(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, - - MFLInfo = case maps:get(max_fragment_length, CurrentWrite, undefined) of - MaxFragmentLength when is_integer(MaxFragmentLength) -> - [{max_fragment_length, MaxFragmentLength}]; - _ -> - [] - end, - [{protocol, RecordCB:protocol_version(Version)}, - {session_id, SessionId}, - {session_resumption, Resumption}, - {selected_cipher_suite, CipherSuiteDef}, - {sni_hostname, SNIHostname}, - {srp_username, SrpUsername} | CurveInfo] ++ MFLInfo ++ 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, #{next_protocol_negotiation := NextProtocols} = - ServerHelloExt, - #state{connection_env = #connection_env{negotiated_version = Version}, - handshake_env = HsEnv, - session = #session{session_id = SessId}, - connection_states = ConnectionStates0, - ssl_options = #{versions := [HighestVersion|_]}} - = State0, Connection) when is_atom(Type) -> - %% TLS 1.3 - Section 4.1.3 - %% Override server random values for TLS 1.3 downgrade protection mechanism. - ConnectionStates1 = update_server_random(ConnectionStates0, Version, HighestVersion), - State1 = State0#state{connection_states = ConnectionStates1}, - ServerHello = - ssl_handshake:server_hello(SessId, ssl:tls_version(Version), - ConnectionStates1, ServerHelloExt), - State = server_hello(ServerHello, - State1#state{handshake_env = HsEnv#handshake_env{expecting_next_protocol_negotiation = - NextProtocols =/= undefined}}, Connection), - case Type of - new -> - new_server_hello(ServerHello, State, Connection); - resumed -> - resumed_server_hello(State, Connection) - end. - -update_server_random(#{pending_read := #{security_parameters := ReadSecParams0} = - ReadState0, - pending_write := #{security_parameters := WriteSecParams0} = - WriteState0} = ConnectionStates, - Version, HighestVersion) -> - ReadRandom = override_server_random( - ReadSecParams0#security_parameters.server_random, - Version, - HighestVersion), - WriteRandom = override_server_random( - WriteSecParams0#security_parameters.server_random, - Version, - HighestVersion), - ReadSecParams = ReadSecParams0#security_parameters{server_random = ReadRandom}, - WriteSecParams = WriteSecParams0#security_parameters{server_random = WriteRandom}, - ReadState = ReadState0#{security_parameters => ReadSecParams}, - WriteState = WriteState0#{security_parameters => WriteSecParams}, - - ConnectionStates#{pending_read => ReadState, pending_write => WriteState}. - -%% TLS 1.3 - Section 4.1.3 -%% -%% If negotiating TLS 1.2, TLS 1.3 servers MUST set the last eight bytes -%% of their Random value to the bytes: -%% -%% 44 4F 57 4E 47 52 44 01 -%% -%% If negotiating TLS 1.1 or below, TLS 1.3 servers MUST and TLS 1.2 -%% servers SHOULD set the last eight bytes of their Random value to the -%% bytes: -%% -%% 44 4F 57 4E 47 52 44 00 -override_server_random(<<Random0:24/binary,_:8/binary>> = Random, {M,N}, {Major,Minor}) - when Major > 3 orelse Major =:= 3 andalso Minor >= 4 -> %% TLS 1.3 or above - if M =:= 3 andalso N =:= 3 -> %% Negotating TLS 1.2 - Down = ?RANDOM_OVERRIDE_TLS12, - <<Random0/binary,Down/binary>>; - M =:= 3 andalso N < 3 -> %% Negotating TLS 1.1 or prior - Down = ?RANDOM_OVERRIDE_TLS11, - <<Random0/binary,Down/binary>>; - true -> - Random - end; -override_server_random(<<Random0:24/binary,_:8/binary>> = Random, {M,N}, {Major,Minor}) - when Major =:= 3 andalso Minor =:= 3 -> %% TLS 1.2 - if M =:= 3 andalso N < 3 -> %% Negotating TLS 1.1 or prior - Down = ?RANDOM_OVERRIDE_TLS11, - <<Random0/binary,Down/binary>>; - true -> - Random - end; -override_server_random(Random, _, _) -> - Random. - -new_server_hello(#server_hello{cipher_suite = CipherSuite, - compression_method = Compression, - session_id = SessionId}, - #state{session = Session0, - connection_env = #connection_env{negotiated_version = Version}} = State0, Connection) -> - try server_certify_and_key_exchange(State0, Connection) of - #state{} = State1 -> - {State, Actions} = server_hello_done(State1, Connection), - Session = - Session0#session{session_id = SessionId, - cipher_suite = CipherSuite, - compression_method = Compression}, - Connection:next_event(certify, no_record, State#state{session = Session}, Actions) - catch - #alert{} = Alert -> - handle_own_alert(Alert, Version, hello, State0) - end. - -resumed_server_hello(#state{session = Session, - connection_states = ConnectionStates0, - connection_env = #connection_env{negotiated_version = Version}} = State0, Connection) -> - - case ssl_handshake:master_secret(ssl:tls_version(Version), Session, - ConnectionStates0, server) of - {_, ConnectionStates1} -> - State1 = State0#state{connection_states = ConnectionStates1, - session = Session}, - {State, Actions} = - finalize_handshake(State1, abbreviated, Connection), - Connection:next_event(abbreviated, no_record, State, Actions); - #alert{} = Alert -> - handle_own_alert(Alert, Version, hello, State0) - end. - -server_hello(ServerHello, State0, Connection) -> - CipherSuite = ServerHello#server_hello.cipher_suite, - #{key_exchange := KeyAlgorithm} = ssl_cipher_format:suite_bin_to_map(CipherSuite), - #state{handshake_env = HsEnv} = State = Connection:queue_handshake(ServerHello, State0), - State#state{handshake_env = HsEnv#handshake_env{kex_algorithm = KeyAlgorithm}}. - -server_hello_done(State, Connection) -> - HelloDone = ssl_handshake:server_hello_done(), - Connection:send_handshake(HelloDone, State). - -handle_peer_cert(Role, PeerCert, PublicKeyInfo, - #state{handshake_env = HsEnv, - session = #session{cipher_suite = CipherSuite} = Session} = State0, - Connection, Actions) -> - State1 = State0#state{handshake_env = HsEnv#handshake_env{public_key_info = PublicKeyInfo}, - session = - Session#session{peer_certificate = PeerCert}}, - #{key_exchange := KeyAlgorithm} = ssl_cipher_format:suite_bin_to_map(CipherSuite), - State = handle_peer_cert_key(Role, PeerCert, PublicKeyInfo, KeyAlgorithm, State1), - Connection:next_event(certify, no_record, State, Actions). - -handle_peer_cert_key(client, _, - {?'id-ecPublicKey', #'ECPoint'{point = _ECPoint} = PublicKey, - PublicKeyParams}, - KeyAlg, #state{handshake_env = HsEnv, - 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{handshake_env = HsEnv#handshake_env{kex_keys = ECDHKey}, - session = Session#session{ecc = PublicKeyParams}}); -handle_peer_cert_key(_, _, _, _, State) -> - State. - -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:queue_handshake(Certificate, State); -certify_client(#state{client_certificate_requested = false} = State, _) -> - State. - -verify_client_cert(#state{static_env = #static_env{role = client}, - handshake_env = #handshake_env{tls_handshake_history = Hist, - cert_hashsign_algorithm = HashSign}, - connection_env = #connection_env{negotiated_version = Version, - private_key = PrivateKey}, - client_certificate_requested = true, - session = #session{master_secret = MasterSecret, - own_certificate = OwnCert}} = State, Connection) -> - - case ssl_handshake:client_certificate_verify(OwnCert, MasterSecret, - ssl:tls_version(Version), HashSign, PrivateKey, Hist) of - #certificate_verify{} = Verified -> - Connection:queue_handshake(Verified, State); - ignore -> - State; - #alert{} = Alert -> - throw(Alert) - end; -verify_client_cert(#state{client_certificate_requested = false} = State, _) -> - State. - -client_certify_and_key_exchange(#state{connection_env = #connection_env{negotiated_version = Version}} = - State0, Connection) -> - try do_client_certify_and_key_exchange(State0, Connection) of - State1 = #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 -> - handle_own_alert(Alert, Version, certify, State0) - end. - -do_client_certify_and_key_exchange(State0, Connection) -> - State1 = certify_client(State0, Connection), - State2 = key_exchange(State1, Connection), - verify_client_cert(State2, Connection). - -server_certify_and_key_exchange(State0, Connection) -> - State1 = certify_server(State0, Connection), - State2 = key_exchange(State1, Connection), - request_client_cert(State2, Connection). - -certify_client_key_exchange(#encrypted_premaster_secret{premaster_secret= EncPMS}, - #state{connection_env = #connection_env{private_key = Key}, - handshake_env = #handshake_env{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{handshake_env = #handshake_env{diffie_hellman_params = #'DHParameter'{} = Params, - kex_keys = {_, ServerDhPrivateKey}} - } = State, - Connection) -> - PremasterSecret = ssl_handshake:premaster_secret(ClientPublicDhKey, ServerDhPrivateKey, Params), - calculate_master_secret(PremasterSecret, State, Connection, certify, cipher); - -certify_client_key_exchange(#client_ec_diffie_hellman_public{dh_public = ClientPublicEcDhPoint}, - #state{handshake_env = #handshake_env{kex_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 = - #{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{handshake_env = #handshake_env{diffie_hellman_params = #'DHParameter'{} = Params, - kex_keys = {_, ServerDhPrivateKey}}, - ssl_options = - #{user_lookup_fun := PSKLookup}} = State0, - Connection) -> - 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{handshake_env = #handshake_env{kex_keys = ServerEcDhPrivateKey}, - 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{connection_env = #connection_env{private_key = Key}, - 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{handshake_env = #handshake_env{srp_params = Params, - kex_keys = Key} - } = State0, Connection) -> - PremasterSecret = ssl_handshake:premaster_secret(ClientKey, Key, Params), - calculate_master_secret(PremasterSecret, State0, Connection, certify, cipher). - -certify_server(#state{handshake_env = #handshake_env{kex_algorithm = KexAlg}} = - State, _) when KexAlg == dh_anon; - KexAlg == ecdh_anon; - KexAlg == psk; - KexAlg == dhe_psk; - KexAlg == ecdhe_psk; - KexAlg == srp_anon -> - State; -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:queue_handshake(Cert, State); - Alert = #alert{} -> - throw(Alert) - end. - -key_exchange(#state{static_env = #static_env{role = server}, - handshake_env = #handshake_env{kex_algorithm = rsa}} = State,_) -> - State; -key_exchange(#state{static_env = #static_env{role = server}, - handshake_env = #handshake_env{kex_algorithm = KexAlg, - diffie_hellman_params = #'DHParameter'{} = Params, - hashsign_algorithm = HashSignAlgo}, - connection_env = #connection_env{negotiated_version = Version, - private_key = PrivateKey}, - connection_states = ConnectionStates0} = State0, Connection) - when KexAlg == dhe_dss; - KexAlg == dhe_rsa; - KexAlg == dh_anon -> - DHKeys = public_key:generate_key(Params), - #{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), {dh, DHKeys, Params, - HashSignAlgo, ClientRandom, - ServerRandom, - PrivateKey}), - #state{handshake_env = HsEnv} = State = Connection:queue_handshake(Msg, State0), - State#state{handshake_env = HsEnv#handshake_env{kex_keys = DHKeys}}; -key_exchange(#state{static_env = #static_env{role = server}, - handshake_env = #handshake_env{kex_algorithm = KexAlg} = HsEnv, - connection_env = #connection_env{private_key = #'ECPrivateKey'{parameters = ECCurve} = Key}, - session = Session} = State, _) - when KexAlg == ecdh_ecdsa; - KexAlg == ecdh_rsa -> - State#state{handshake_env = HsEnv#handshake_env{kex_keys = Key}, - session = Session#session{ecc = ECCurve}}; -key_exchange(#state{static_env = #static_env{role = server}, - handshake_env = #handshake_env{kex_algorithm = KexAlg, - hashsign_algorithm = HashSignAlgo}, - connection_env = #connection_env{negotiated_version = Version, - private_key = PrivateKey}, - session = #session{ecc = ECCCurve}, - connection_states = ConnectionStates0} = State0, Connection) - when KexAlg == ecdhe_ecdsa; - KexAlg == ecdhe_rsa; - KexAlg == ecdh_anon -> - - 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), - {ecdh, ECDHKeys, - HashSignAlgo, ClientRandom, - ServerRandom, - PrivateKey}), - #state{handshake_env = HsEnv} = State = Connection:queue_handshake(Msg, State0), - State#state{handshake_env = HsEnv#handshake_env{kex_keys = ECDHKeys}}; -key_exchange(#state{static_env = #static_env{role = server}, - handshake_env = #handshake_env{kex_algorithm = psk}, - ssl_options = #{psk_identity := undefined}} = State, _) -> - State; -key_exchange(#state{static_env = #static_env{role = server}, - ssl_options = #{psk_identity := PskIdentityHint}, - handshake_env = #handshake_env{kex_algorithm = psk, - hashsign_algorithm = HashSignAlgo}, - connection_env = #connection_env{negotiated_version = Version, - private_key = PrivateKey}, - connection_states = ConnectionStates0} = State0, Connection) -> - #{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), - {psk, PskIdentityHint, - HashSignAlgo, ClientRandom, - ServerRandom, - PrivateKey}), - Connection:queue_handshake(Msg, State0); -key_exchange(#state{static_env = #static_env{role = server}, - ssl_options = #{psk_identity := PskIdentityHint}, - handshake_env = #handshake_env{kex_algorithm = dhe_psk, - diffie_hellman_params = #'DHParameter'{} = Params, - hashsign_algorithm = HashSignAlgo}, - connection_env = #connection_env{negotiated_version = Version, - private_key = PrivateKey}, - connection_states = ConnectionStates0 - } = State0, Connection) -> - DHKeys = public_key:generate_key(Params), - #{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), - {dhe_psk, - PskIdentityHint, DHKeys, Params, - HashSignAlgo, ClientRandom, - ServerRandom, - PrivateKey}), - #state{handshake_env = HsEnv} = State = Connection:queue_handshake(Msg, State0), - State#state{handshake_env = HsEnv#handshake_env{kex_keys = DHKeys}}; -key_exchange(#state{static_env = #static_env{role = server}, - ssl_options = #{psk_identity := PskIdentityHint}, - handshake_env = #handshake_env{kex_algorithm = ecdhe_psk, - hashsign_algorithm = HashSignAlgo}, - connection_env = #connection_env{negotiated_version = Version, - private_key = PrivateKey}, - session = #session{ecc = ECCCurve}, - connection_states = ConnectionStates0 - } = 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{handshake_env = HsEnv} = State = Connection:queue_handshake(Msg, State0), - State#state{handshake_env = HsEnv#handshake_env{kex_keys = ECDHKeys}}; -key_exchange(#state{static_env = #static_env{role = server}, - handshake_env = #handshake_env{kex_algorithm = rsa_psk}, - ssl_options = #{psk_identity := undefined}} = State, _) -> - State; -key_exchange(#state{static_env = #static_env{role = server}, - ssl_options = #{psk_identity := PskIdentityHint}, - handshake_env = #handshake_env{kex_algorithm = rsa_psk, - hashsign_algorithm = HashSignAlgo}, - connection_env = #connection_env{negotiated_version = Version, - private_key = PrivateKey}, - connection_states = ConnectionStates0 - } = State0, Connection) -> - #{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), - {psk, PskIdentityHint, - HashSignAlgo, ClientRandom, - ServerRandom, - PrivateKey}), - Connection:queue_handshake(Msg, State0); -key_exchange(#state{static_env = #static_env{role = server}, - ssl_options = #{user_lookup_fun := LookupFun}, - handshake_env = #handshake_env{kex_algorithm = KexAlg, - hashsign_algorithm = HashSignAlgo}, - connection_env = #connection_env{negotiated_version = Version, - private_key = PrivateKey}, - session = #session{srp_username = Username}, - connection_states = ConnectionStates0 - } = State0, Connection) - when KexAlg == srp_dss; - KexAlg == srp_rsa; - KexAlg == srp_anon -> - SrpParams = handle_srp_identity(Username, LookupFun), - Keys = case generate_srp_server_keys(SrpParams, 0) of - Alert = #alert{} -> - throw(Alert); - Keys0 = {_,_} -> - Keys0 - end, - #{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), - {srp, Keys, SrpParams, - HashSignAlgo, ClientRandom, - ServerRandom, - PrivateKey}), - #state{handshake_env = HsEnv} = State = Connection:queue_handshake(Msg, State0), - State#state{handshake_env = HsEnv#handshake_env{srp_params = SrpParams, - kex_keys = Keys}}; -key_exchange(#state{static_env = #static_env{role = client}, - handshake_env = #handshake_env{kex_algorithm = rsa, - public_key_info = PublicKeyInfo, - premaster_secret = PremasterSecret}, - connection_env = #connection_env{negotiated_version = Version} - } = State0, Connection) -> - Msg = rsa_key_exchange(ssl:tls_version(Version), PremasterSecret, PublicKeyInfo), - Connection:queue_handshake(Msg, State0); -key_exchange(#state{static_env = #static_env{role = client}, - handshake_env = #handshake_env{kex_algorithm = KexAlg, - kex_keys = {DhPubKey, _}}, - connection_env = #connection_env{negotiated_version = Version} - } = State0, Connection) - when KexAlg == dhe_dss; - KexAlg == dhe_rsa; - KexAlg == dh_anon -> - Msg = ssl_handshake:key_exchange(client, ssl:tls_version(Version), {dh, DhPubKey}), - Connection:queue_handshake(Msg, State0); - -key_exchange(#state{static_env = #static_env{role = client}, - handshake_env = #handshake_env{kex_algorithm = KexAlg, - kex_keys = #'ECPrivateKey'{parameters = ECCurve} = Key}, - connection_env = #connection_env{negotiated_version = Version}, - session = Session - } = State0, Connection) - when KexAlg == ecdhe_ecdsa; - KexAlg == ecdhe_rsa; - KexAlg == ecdh_ecdsa; - KexAlg == ecdh_rsa; - KexAlg == ecdh_anon -> - 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}, - handshake_env = #handshake_env{kex_algorithm = psk}, - connection_env = #connection_env{negotiated_version = Version}, - ssl_options = #{psk_identity := PSKIdentity}} = State0, Connection) -> - Msg = ssl_handshake:key_exchange(client, ssl:tls_version(Version), - {psk, PSKIdentity}), - Connection:queue_handshake(Msg, State0); -key_exchange(#state{static_env = #static_env{role = client}, - handshake_env = #handshake_env{kex_algorithm = dhe_psk, - kex_keys = {DhPubKey, _}}, - connection_env = #connection_env{negotiated_version = Version}, - ssl_options = #{psk_identity := PSKIdentity}} = State0, Connection) -> - Msg = ssl_handshake:key_exchange(client, ssl:tls_version(Version), - {dhe_psk, - PSKIdentity, DhPubKey}), - Connection:queue_handshake(Msg, State0); - -key_exchange(#state{static_env = #static_env{role = client}, - handshake_env = #handshake_env{kex_algorithm = ecdhe_psk, - kex_keys = ECDHKeys}, - connection_env = #connection_env{negotiated_version = Version}, - ssl_options = #{psk_identity := PSKIdentity}} = State0, Connection) -> - Msg = ssl_handshake:key_exchange(client, ssl:tls_version(Version), - {ecdhe_psk, - PSKIdentity, ECDHKeys}), - Connection:queue_handshake(Msg, State0); - -key_exchange(#state{static_env = #static_env{role = client}, - handshake_env = #handshake_env{kex_algorithm = rsa_psk, - public_key_info = PublicKeyInfo, - premaster_secret = PremasterSecret}, - connection_env = #connection_env{negotiated_version = Version}, - ssl_options = #{psk_identity := PSKIdentity}} - = State0, Connection) -> - Msg = rsa_psk_key_exchange(ssl:tls_version(Version), PSKIdentity, - PremasterSecret, PublicKeyInfo), - Connection:queue_handshake(Msg, State0); -key_exchange(#state{static_env = #static_env{role = client}, - handshake_env = #handshake_env{kex_algorithm = KexAlg, - kex_keys = {ClientPubKey, _}}, - connection_env = #connection_env{negotiated_version = Version}} - = State0, Connection) - when KexAlg == srp_dss; - KexAlg == srp_rsa; - KexAlg == srp_anon -> - 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; - Algorithm == ?md2WithRSAEncryption; - Algorithm == ?md5WithRSAEncryption; - Algorithm == ?sha1WithRSAEncryption; - Algorithm == ?sha224WithRSAEncryption; - Algorithm == ?sha256WithRSAEncryption; - Algorithm == ?sha384WithRSAEncryption; - Algorithm == ?sha512WithRSAEncryption - -> - ssl_handshake:key_exchange(client, ssl:tls_version(Version), - {premaster_secret, PremasterSecret, - PublicKeyInfo}); -rsa_key_exchange(_, _, _) -> - throw (?ALERT_REC(?FATAL,?HANDSHAKE_FAILURE, pub_key_is_not_rsa)). - -rsa_psk_key_exchange(Version, PskIdentity, PremasterSecret, - PublicKeyInfo = {Algorithm, _, _}) - when Algorithm == ?rsaEncryption; - Algorithm == ?md2WithRSAEncryption; - Algorithm == ?md5WithRSAEncryption; - Algorithm == ?sha1WithRSAEncryption; - Algorithm == ?sha224WithRSAEncryption; - Algorithm == ?sha256WithRSAEncryption; - Algorithm == ?sha384WithRSAEncryption; - Algorithm == ?sha512WithRSAEncryption - -> - 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, pub_key_is_not_rsa)). - -request_client_cert(#state{handshake_env = #handshake_env{kex_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}, - connection_env = #connection_env{negotiated_version = Version}, - ssl_options = #{verify := verify_peer, - signature_algs := SupportedHashSigns}, - connection_states = ConnectionStates0} = State0, Connection) -> - #{security_parameters := - #security_parameters{cipher_suite = CipherSuite}} = - ssl_record:pending_connection_state(ConnectionStates0, read), - 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 = #{verify := verify_none}} = - State, _) -> - State. - -calculate_master_secret(PremasterSecret, - #state{connection_env = #connection_env{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}, - State = State0#state{connection_states = ConnectionStates, - session = Session}, - Connection:next_event(Next, no_record, State); - #alert{} = Alert -> - handle_own_alert(Alert, Version, certify, State0) - end. - -finalize_handshake(State0, StateName, Connection) -> - #state{connection_states = ConnectionStates0} = - State1 = cipher_protocol(State0, Connection), - - ConnectionStates = - ssl_record:activate_pending_connection_state(ConnectionStates0, - write, Connection), - - State2 = State1#state{connection_states = ConnectionStates}, - State = next_protocol(State2, Connection), - finished(State, StateName, Connection). - -next_protocol(#state{static_env = #static_env{role = server}} = State, _) -> - State; -next_protocol(#state{handshake_env = #handshake_env{negotiated_protocol = undefined}} = State, _) -> - State; -next_protocol(#state{handshake_env = #handshake_env{expecting_next_protocol_negotiation = false}} = State, _) -> - State; -next_protocol(#state{handshake_env = #handshake_env{negotiated_protocol = NextProtocol}} = State0, Connection) -> - NextProtocolMessage = ssl_handshake:next_protocol(NextProtocol), - Connection:queue_handshake(NextProtocolMessage, State0). - -cipher_protocol(State, Connection) -> - Connection:queue_change_cipher(#change_cipher_spec{}, State). - -finished(#state{static_env = #static_env{role = Role}, - handshake_env = #handshake_env{tls_handshake_history = Hist}, - connection_env = #connection_env{negotiated_version = Version}, - session = Session, - connection_states = ConnectionStates0} = State0, - StateName, Connection) -> - MasterSecret = Session#session.master_secret, - Finished = ssl_handshake:finished(ssl:tls_version(Version), Role, - get_current_prf(ConnectionStates0, write), - MasterSecret, Hist), - ConnectionStates = save_verify_data(Role, Finished, ConnectionStates0, StateName), - Connection:send_handshake(Finished, State0#state{connection_states = - ConnectionStates}). - -save_verify_data(client, #finished{verify_data = Data}, ConnectionStates, certify) -> - ssl_record:set_client_verify_data(current_write, Data, ConnectionStates); -save_verify_data(server, #finished{verify_data = Data}, ConnectionStates, cipher) -> - ssl_record:set_server_verify_data(current_both, Data, ConnectionStates); -save_verify_data(client, #finished{verify_data = Data}, ConnectionStates, abbreviated) -> - ssl_record:set_client_verify_data(current_both, Data, ConnectionStates); -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{handshake_env = HsEnv} = State, Connection) -> - Keys = {_, PrivateDhKey} = crypto:generate_key(dh, [Prime, Base]), - PremasterSecret = - ssl_handshake:premaster_secret(ServerPublicDhKey, PrivateDhKey, Params), - calculate_master_secret(PremasterSecret, - State#state{handshake_env = HsEnv#handshake_env{kex_keys = Keys}}, - Connection, certify, certify); - -calculate_secret(#server_ecdh_params{curve = ECCurve, public = ECServerPubKey}, - #state{handshake_env = HsEnv, - session = Session} = State, Connection) -> - ECDHKeys = public_key:generate_key(ECCurve), - PremasterSecret = - ssl_handshake:premaster_secret(#'ECPoint'{point = ECServerPubKey}, ECDHKeys), - calculate_master_secret(PremasterSecret, - State#state{handshake_env = HsEnv#handshake_env{kex_keys = ECDHKeys}, - session = Session#session{ecc = ECCurve}}, - Connection, certify, certify); - -calculate_secret(#server_psk_params{ - hint = IdentityHint}, - #state{handshake_env = HsEnv} = State, Connection) -> - %% store for later use - Connection:next_event(certify, no_record, - State#state{handshake_env = - HsEnv#handshake_env{server_psk_identity = IdentityHint}}); - -calculate_secret(#server_dhe_psk_params{ - dh_params = #server_dh_params{dh_p = Prime, dh_g = Base}} = ServerKey, - #state{handshake_env = HsEnv, - 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{handshake_env = HsEnv#handshake_env{kex_keys = Keys}}, - Connection, certify, certify); - -calculate_secret(#server_ecdhe_psk_params{ - dh_params = #server_ecdh_params{curve = ECCurve}} = ServerKey, - #state{ssl_options = #{user_lookup_fun := PSKLookup}} = - #state{handshake_env = HsEnv, - session = Session} = State, Connection) -> - ECDHKeys = public_key:generate_key(ECCurve), - - PremasterSecret = ssl_handshake:premaster_secret(ServerKey, ECDHKeys, PSKLookup), - calculate_master_secret(PremasterSecret, - State#state{handshake_env = HsEnv#handshake_env{kex_keys = ECDHKeys}, - session = Session#session{ecc = ECCurve}}, - Connection, certify, certify); - -calculate_secret(#server_srp_params{srp_n = Prime, srp_g = Generator} = ServerKey, - #state{handshake_env = HsEnv, - 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{handshake_env = HsEnv#handshake_env{kex_keys = Keys}}, Connection, - certify, certify). - -master_secret(#alert{} = Alert, _) -> - Alert; -master_secret(PremasterSecret, #state{static_env = #static_env{role = Role}, - connection_env = #connection_env{negotiated_version = Version}, - session = Session, - connection_states = ConnectionStates0} = State) -> - case ssl_handshake:master_secret(ssl:tls_version(Version), PremasterSecret, - ConnectionStates0, Role) of - {MasterSecret, ConnectionStates} -> - State#state{ - session = - Session#session{master_secret = MasterSecret}, - connection_states = ConnectionStates}; - #alert{} = Alert -> - Alert - end. - -generate_srp_server_keys(_SrpParams, 10) -> - ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER); -generate_srp_server_keys(SrpParams = - #srp_user{generator = Generator, prime = Prime, - verifier = Verifier}, N) -> - 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) -> - - 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}) -> - case Fun(srp, Username, UserState) of - {ok, {SRPParams, Salt, DerivedKey}} - when is_atom(SRPParams), is_binary(Salt), is_binary(DerivedKey) -> - {Generator, Prime} = ssl_srp_primes:get_srp_params(SRPParams), - Verifier = crypto:mod_pow(Generator, DerivedKey, Prime), - #srp_user{generator = Generator, prime = Prime, - salt = Salt, verifier = Verifier}; - #alert{} = Alert -> - throw(Alert); - _ -> - throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)) - end. - - -cipher_role(client, Data, Session, #state{connection_states = ConnectionStates0} = State0, - Connection) -> - 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, [{{timeout, handshake}, infinity, close}]); -cipher_role(server, Data, Session, #state{connection_states = ConnectionStates0} = State0, - Connection) -> - ConnectionStates1 = ssl_record:set_client_verify_data(current_read, Data, - ConnectionStates0), - {State1, Actions} = - finalize_handshake(State0#state{connection_states = ConnectionStates1, - session = Session}, cipher, Connection), - {Record, State} = prepare_connection(State1, Connection), - Connection:next_event(connection, Record, State, [{{timeout, handshake}, infinity, close} | Actions]). - -is_anonymous(KexAlg) when KexAlg == dh_anon; - KexAlg == ecdh_anon; - KexAlg == psk; - KexAlg == dhe_psk; - KexAlg == ecdhe_psk; - KexAlg == rsa_psk; - KexAlg == srp_anon -> - true; -is_anonymous(_) -> - false. - -get_current_prf(CStates, Direction) -> - #{security_parameters := SecParams} = ssl_record:current_connection_state(CStates, Direction), - SecParams#security_parameters.prf_algorithm. -get_pending_prf(CStates, Direction) -> - #{security_parameters := SecParams} = ssl_record:pending_connection_state(CStates, Direction), - SecParams#security_parameters.prf_algorithm. - -opposite_role(client) -> - server; -opposite_role(server) -> - client. - -record_cb(tls_connection) -> - tls_record; -record_cb(dtls_connection) -> - dtls_record. - -call(FsmPid, Event) -> - try gen_statem:call(FsmPid, Event) - catch - exit:{noproc, _} -> - {error, closed}; - exit:{normal, _} -> - {error, closed}; - exit:{{shutdown, _},_} -> - {error, closed} - end. - -get_socket_opts(_, _,_,[], _, Acc) -> - {ok, Acc}; -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(Connection, Transport, Socket, [packet | Tags], SockOpts, Acc) -> - case SockOpts#socket_options.packet of - {Type, headers} -> - get_socket_opts(Connection, Transport, Socket, Tags, SockOpts, [{packet, Type} | Acc]); - Type -> - get_socket_opts(Connection, Transport, Socket, Tags, SockOpts, [{packet, Type} | Acc]) - end; -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(Connection, Transport, Socket, [active | Tags], SockOpts, Acc) -> - get_socket_opts(Connection, Transport, Socket, Tags, SockOpts, - [{active, SockOpts#socket_options.active} | Acc]); -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, _,_) -> - {error, {options, {socket_options, Opts, function_clause}}}. - -set_socket_opts(_,_,_, [], SockOpts, []) -> - {ok, SockOpts}; -set_socket_opts(ConnectionCb, Transport, Socket, [], SockOpts, Other) -> - %% Set non emulated options - try ConnectionCb:setopts(Transport, Socket, Other) of - ok -> - {ok, SockOpts}; - {error, InetError} -> - {{error, {options, {socket_options, Other, InetError}}}, SockOpts} - catch - _:Error -> - %% So that inet behavior does not crash our process - {{error, {options, {socket_options, Other, Error}}}, SockOpts} - end; - -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, _) -> - {{error, {options, {socket_options, Opt}}}, SockOpts}; -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, _) -> - {{error, {options, {socket_options, Opt}}}, SockOpts}; -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, _) -> - {{error,{options, {socket_options, Opt}}}, SockOpts}; -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(ConnectionCb, Transport, Socket, [{active, Active1} = Opt| Opts], - SockOpts=#socket_options{active = Active0}, Other) - when Active1 >= -32768, Active1 =< 32767 -> - Active = if - is_integer(Active0), Active0 + Active1 < -32768 -> - error; - is_integer(Active0), Active0 + Active1 =< 0 -> - false; - is_integer(Active0), Active0 + Active1 > 32767 -> - error; - Active1 =< 0 -> - false; - is_integer(Active0) -> - Active0 + Active1; - true -> - Active1 - end, - case Active of - error -> - {{error, {options, {socket_options, Opt}} }, SockOpts}; - _ -> - set_socket_opts(ConnectionCb, Transport, Socket, Opts, - SockOpts#socket_options{active = Active}, Other) - end; -set_socket_opts(_,_, _, [{active, _} = Opt| _], SockOpts, _) -> - {{error, {options, {socket_options, Opt}} }, SockOpts}; -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= #{hibernate_after := HibernateAfter}} = State, - Actions) -> - {next_state, StateName, State, [{timeout, HibernateAfter, hibernate} | Actions]}; -hibernate_after(StateName, State, Actions) -> - {next_state, StateName, State, Actions}. - - -terminate_alert(normal) -> - ?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 = - #{cacertfile := <<>>, cacerts := []}}) -> - %% No trusted certs specified - ok; -handle_trusted_certs_db(#state{static_env = #static_env{cert_db_ref = Ref, - cert_db = CertDb}, - 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{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{static_env = #static_env{cert_db_ref = Ref, - file_ref_db = RefDb}, - ssl_options = #{cacertfile := File}}) -> - case ssl_pkix_db:ref_count(Ref, RefDb, -1) of - 0 -> - ssl_manager:clean_cert_db(Ref, File); - _ -> - ok - end. - -prepare_connection(#state{handshake_env = #handshake_env{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{handshake_env = #handshake_env{renegotiation = {true, Initiater}} = HsEnv} = State) when Initiater == peer; - Initiater == internal -> - State#state{handshake_env = HsEnv#handshake_env{renegotiation = undefined}}; -ack_connection(#state{handshake_env = #handshake_env{renegotiation = {true, From}} = HsEnv} = State) -> - gen_statem:reply(From, ok), - State#state{handshake_env = HsEnv#handshake_env{renegotiation = undefined}}; -ack_connection(#state{handshake_env = #handshake_env{renegotiation = {false, first}} = HsEnv, - start_or_recv_from = StartFrom} = State) when StartFrom =/= undefined -> - gen_statem:reply(StartFrom, connected), - State#state{handshake_env = HsEnv#handshake_env{renegotiation = undefined}, - start_or_recv_from = undefined}; -ack_connection(State) -> - State. - -session_handle_params(#server_ecdh_params{curve = ECCurve}, Session) -> - Session#session{ecc = ECCurve}; -session_handle_params(_, Session) -> - Session. - -handle_session(server, #{reuse_sessions := true}, - _Host, _Port, Trackers, #session{is_resumable = false} = Session) -> - Tracker = proplists:get_value(session_id_tracker, Trackers), - server_register_session(Tracker, Session#session{is_resumable = true}); -handle_session(Role = client, #{verify := verify_peer, - reuse_sessions := Reuse} = SslOpts, - Host, Port, _, #session{is_resumable = false} = Session) when Reuse =/= false -> - client_register_session(host_id(Role, Host, SslOpts), Port, Session#session{is_resumable = true}, - reg_type(Reuse)); -handle_session(_,_,_,_,_, Session) -> - Session. - -reg_type(save) -> - true; -reg_type(true) -> - unique. - -client_register_session(Host, Port, Session, Save) -> - ssl_manager:register_session(Host, Port, Session, Save), - Session. -server_register_session(Tracker, Session) -> - ssl_server_session_cache:register_session(Tracker, Session), - Session. - -host_id(client, _Host, #{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}, - 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_env = #connection_env{negotiated_version = Version}, - connection_states = ConnectionStates0} = State) -> - Session = CacheCb:lookup(Cache, {{Host, Port}, SessId}), - case ssl_handshake:master_secret(ssl:tls_version(Version), Session, - ConnectionStates0, client) of - {_, ConnectionStates} -> - Connection:next_event(abbreviated, no_record, State#state{ - connection_states = ConnectionStates, - session = Session}); - #alert{} = Alert -> - handle_own_alert(Alert, Version, hello, State) - end. - -make_premaster_secret({MajVer, MinVer}, rsa) -> - Rand = ssl_cipher:random_bytes(?NUM_OF_PREMASTERSECRET_BYTES-2), - <<?BYTE(MajVer), ?BYTE(MinVer), Rand/binary>>; -make_premaster_secret(_, _) -> - undefined. - -negotiated_hashsign(undefined, KexAlg, PubKeyInfo, Version) -> - %% Not negotiated choose default - case is_anonymous(KexAlg) of - true -> - {null, anon}; - false -> - {PubAlg, _, _} = PubKeyInfo, - ssl_handshake:select_hashsign_algs(undefined, PubAlg, Version) - end; -negotiated_hashsign(HashSign = {_, _}, _, _, _) -> - HashSign. - -ssl_options_list(SslOptions) -> - L = maps:to_list(SslOptions), - ssl_options_list(L, []). - -ssl_options_list([], Acc) -> - lists:reverse(Acc); -%% Skip internal options, only return user options -ssl_options_list([{protocol, _}| T], Acc) -> - ssl_options_list(T, Acc); -ssl_options_list([{erl_dist, _}|T], Acc) -> - ssl_options_list(T, Acc); -ssl_options_list([{renegotiate_at, _}|T], Acc) -> - ssl_options_list(T, Acc); -ssl_options_list([{max_fragment_length, _}|T], Acc) -> - %% skip max_fragment_length from options since it is taken above from connection_states - ssl_options_list(T, Acc); -ssl_options_list([{ciphers = Key, Value}|T], Acc) -> - ssl_options_list(T, - [{Key, lists:map( - fun(Suite) -> - ssl_cipher_format:suite_bin_to_map(Suite) - end, Value)} - | Acc]); -ssl_options_list([{Key, Value}|T], Acc) -> - ssl_options_list(T, [{Key, Value} | Acc]). - -handle_active_option(false, connection = StateName, To, Reply, State) -> - hibernate_after(StateName, State, [{reply, To, Reply}]); - -handle_active_option(_, connection = StateName, To, _Reply, #state{static_env = #static_env{role = Role}, - connection_env = #connection_env{terminated = true}, - user_data_buffer = {_,0,_}} = State) -> - Alert = ?ALERT_REC(?FATAL, ?CLOSE_NOTIFY, all_data_deliverd), - handle_normal_shutdown(Alert#alert{role = Role}, StateName, - State#state{start_or_recv_from = To}), - {stop,{shutdown, peer_close}, State}; -handle_active_option(_, connection = StateName0, To, Reply, #state{static_env = #static_env{protocol_cb = Connection}, - user_data_buffer = {_,0,_}} = 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 = {_,0,_}} = State) -> - %% Active once already set - {next_state, StateName, State, [{reply, To, Reply}]}; - -%% user_data_buffer nonempty -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(#socket_options{active=false}, undefined, _Bin) -> - %% Recv timed out save buffer data until next recv - passive; -get_data(#socket_options{active=Active, packet=Raw}, BytesToRead, Bin) - when Raw =:= raw; Raw =:= 0 -> %% Raw Mode - case Bin of - <<_/binary>> when Active =/= false orelse BytesToRead =:= 0 -> - %% Active true or once, or passive mode recv(0) - {ok, Bin, <<>>}; - <<Data:BytesToRead/binary, Rest/binary>> -> - %% Passive Mode, recv(Bytes) - {ok, Data, Rest}; - <<_/binary>> -> - %% Passive Mode not enough data - {more, BytesToRead} - end; -get_data(#socket_options{packet=Type, packet_size=Size}, _, Bin) -> - PacketOpts = [{packet_size, Size}], - decode_packet(Type, Bin, PacketOpts). - -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, Trackers, Connection) -> - %% - send_or_reply( - Active, Pid, From, - format_reply( - CPids, Transport, Socket, SOpts, Data, Trackers, 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}; - 1 -> - send_user( - Pid, - format_passive( - CPids, Transport, Socket, Trackers, Connection)), - SO#socket_options{active=false}; - N when is_integer(N) -> - SO#socket_options{active=N - 1}; - _ -> - 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, Trackers, Connection) -> - {ssl, Connection:socket(CPids, Transport, Socket, Trackers), - do_format_reply(Mode, Packet, Header, Data)}. - -deliver_packet_error(CPids, Transport, Socket, - SO= #socket_options{active = Active}, Data, Pid, From, Trackers, Connection) -> - send_or_reply(Active, Pid, From, format_packet_error(CPids, - Transport, Socket, SO, Data, Trackers, 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, Trackers, Connection) -> - {ssl_error, Connection:socket(CPids, Transport, Socket, Trackers), - {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). - -format_passive(CPids, Transport, Socket, Trackers, Connection) -> - {ssl_passive, Connection:socket(CPids, Transport, Socket, Trackers)}. - -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); -send_or_reply(false, Pid, undefined, _) when is_pid(Pid) -> - ok; -send_or_reply(_, no_pid, _, _) -> - ok; -send_or_reply(_, Pid, _, Data) -> - send_user(Pid, Data). - -send_user(Pid, Msg) -> - Pid ! Msg, - ok. - -alert_user(Pids, Transport, Trackers, Socket, connection, Opts, Pid, From, Alert, Role, StateName, Connection) -> - alert_user(Pids, Transport, Trackers, Socket, Opts#socket_options.active, Pid, From, Alert, Role, StateName, Connection); -alert_user(Pids, Transport, Trackers, Socket,_, _, _, From, Alert, Role, StateName, Connection) -> - alert_user(Pids, Transport, Trackers, Socket, From, Alert, Role, StateName, Connection). - -alert_user(Pids, Transport, Trackers, Socket, From, Alert, Role, StateName, Connection) -> - alert_user(Pids, Transport, Trackers, Socket, false, no_pid, From, Alert, Role, StateName, Connection). - -alert_user(_, _, _, _, false = Active, Pid, From, Alert, Role, StateName, Connection) 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, Connection:protocol_name(), StateName), - send_or_reply(Active, Pid, From, {error, ReasonCode}); -alert_user(Pids, Transport, Trackers, Socket, Active, Pid, From, Alert, Role, StateName, Connection) -> - case ssl_alert:reason_code(Alert, Role, Connection:protocol_name(), StateName) of - closed -> - send_or_reply(Active, Pid, From, - {ssl_closed, Connection:socket(Pids, Transport, Socket, Trackers)}); - ReasonCode -> - send_or_reply(Active, Pid, From, - {ssl_error, Connection:socket(Pids, Transport, Socket, Trackers), ReasonCode}) - end. - -log_alert(Level, Role, ProtocolName, StateName, #alert{role = Role} = Alert) -> - ssl_logger:log(notice, Level, #{protocol => ProtocolName, - role => Role, - statename => StateName, - alert => Alert, - alerter => own}, Alert#alert.where); -log_alert(Level, Role, ProtocolName, StateName, Alert) -> - ssl_logger:log(notice, Level, #{protocol => ProtocolName, - role => Role, - statename => StateName, - alert => Alert, - alerter => peer}, Alert#alert.where). - -maybe_invalidate_session({false, first}, server = Role, Host, Port, Session) -> - invalidate_session(Role, Host, Port, Session); -maybe_invalidate_session(_, _, _, _, _) -> - ok. - -invalidate_session(client, Host, Port, Session) -> - ssl_manager:invalidate_session(Host, Port, Session); -invalidate_session(server, _, _, _) -> - ok. - -handle_sni_extension(undefined, State) -> - State; -handle_sni_extension(#sni{hostname = Hostname}, State) -> - case is_sni_value(Hostname) of - true -> - handle_sni_extension(Hostname, State); - false -> - ?ALERT_REC(?FATAL, ?UNRECOGNIZED_NAME, {sni_included_trailing_dot, Hostname}) - end; -handle_sni_extension(Hostname, #state{static_env = #static_env{role = Role} = InitStatEnv0, - handshake_env = HsEnv, - connection_env = CEnv} = 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 - }, - connection_env = CEnv#connection_env{private_key = Key}, - ssl_options = NewOptions, - handshake_env = HsEnv#handshake_env{sni_hostname = Hostname, - diffie_hellman_params = DHParams} - } - end. - -update_ssl_options_from_sni(#{sni_fun := SNIFun, - sni_hosts := SNIHosts} = OrigSSLOptions, SNIHostname) -> - SSLOption = - case SNIFun of - undefined -> - proplists:get_value(SNIHostname, - SNIHosts); - SNIFun -> - SNIFun(SNIHostname) - end, - case SSLOption of - undefined -> - undefined; - _ -> - ssl:handle_options(SSLOption, server, OrigSSLOptions) - end. - -new_emulated([], EmOpts) -> - EmOpts; -new_emulated(NewEmOpts, _) -> - NewEmOpts. - -no_records(Extensions) -> - maps:map(fun(_, Value) -> - ssl_handshake:extension_value(Value) - end, Extensions). - -is_sni_value(Hostname) -> - case hd(lists:reverse(Hostname)) of - $. -> - false; - _ -> - true - end. - -ensure_tls({254, _} = Version) -> - dtls_v1:corresponding_tls_version(Version); -ensure_tls(Version) -> - Version. - -ocsp_info(#{ocsp_expect := stapled, - ocsp_response := CertStatus} = OcspState, - #{ocsp_responder_certs := OcspResponderCerts}, PeerCert) -> - #{cert_ext => #{public_key:pkix_subject_id(PeerCert) => [CertStatus]}, - ocsp_responder_certs => OcspResponderCerts, - ocsp_state => OcspState - }; -ocsp_info(#{ocsp_expect := no_staple} = OcspState, _, PeerCert) -> - #{cert_ext => #{public_key:pkix_subject_id(PeerCert) => []}, - ocsp_responder_certs => [], - ocsp_state => OcspState - }. |