diff options
Diffstat (limited to 'lib/ssl/src')
-rw-r--r-- | lib/ssl/src/ssl.appup.src | 6 | ||||
-rw-r--r-- | lib/ssl/src/ssl.erl | 83 | ||||
-rw-r--r-- | lib/ssl/src/ssl_alert.erl | 60 | ||||
-rw-r--r-- | lib/ssl/src/ssl_cipher.hrl | 15 | ||||
-rw-r--r-- | lib/ssl/src/ssl_connection.erl | 996 | ||||
-rw-r--r-- | lib/ssl/src/ssl_handshake.erl | 299 | ||||
-rw-r--r-- | lib/ssl/src/ssl_handshake.hrl | 15 | ||||
-rw-r--r-- | lib/ssl/src/ssl_internal.hrl | 1 | ||||
-rw-r--r-- | lib/ssl/src/ssl_record.erl | 105 | ||||
-rw-r--r-- | lib/ssl/src/ssl_record.hrl | 6 |
10 files changed, 1035 insertions, 551 deletions
diff --git a/lib/ssl/src/ssl.appup.src b/lib/ssl/src/ssl.appup.src index fdda65021d..e8ae6846aa 100644 --- a/lib/ssl/src/ssl.appup.src +++ b/lib/ssl/src/ssl.appup.src @@ -9,7 +9,8 @@ {"3.10.5", [{restart_application, ssl}]}, {"3.10.6", [{restart_application, ssl}]}, {"3.10.7", [{restart_application, ssl}]}, - {"3.10.8", [{restart_application, ssl}]} + {"3.10.8", [{restart_application, ssl}]}, + {"3.10.9", [{restart_application, ssl}]} ], [ {"3.10", [{restart_application, ssl}]}, @@ -19,6 +20,7 @@ {"3.10.4", [{restart_application, ssl}]}, {"3.10.5", [{restart_application, ssl}]}, {"3.10.6", [{restart_application, ssl}]}, - {"3.10.8", [{restart_application, ssl}]} + {"3.10.8", [{restart_application, ssl}]}, + {"3.10.9", [{restart_application, ssl}]} ]}. diff --git a/lib/ssl/src/ssl.erl b/lib/ssl/src/ssl.erl index fd9ed4a344..95cd92ee60 100644 --- a/lib/ssl/src/ssl.erl +++ b/lib/ssl/src/ssl.erl @@ -147,23 +147,23 @@ transport_accept(ListenSocket) -> transport_accept(ListenSocket, infinity). transport_accept(#sslsocket{pid = {ListenSocket, #config{cb=CbInfo, ssl=SslOpts}}, - fd = new_ssl} = SslSocket, Timeout) -> + fd = new_ssl}, Timeout) -> %% The setopt could have been invoked on the listen socket %% and options should be inherited. EmOptions = emulated_options(), {ok, InetValues} = inet:getopts(ListenSocket, EmOptions), - {CbModule,_,_} = CbInfo, + ok = inet:setopts(ListenSocket, internal_inet_values()), + {CbModule,_,_, _} = CbInfo, case CbModule:accept(ListenSocket, Timeout) of {ok, Socket} -> - inet:setopts(Socket, internal_inet_values()), + ok = inet:setopts(ListenSocket, InetValues), {ok, Port} = inet:port(Socket), ConnArgs = [server, "localhost", Port, Socket, {SslOpts, socket_options(InetValues)}, self(), CbInfo], case ssl_connection_sup:start_child(ConnArgs) of {ok, Pid} -> - CbModule:controlling_process(Socket, Pid), - {ok, SslSocket#sslsocket{pid = Pid}}; + ssl_connection:socket_control(Socket, Pid, CbModule); {error, Reason} -> {error, Reason} end; @@ -186,22 +186,9 @@ transport_accept(#sslsocket{} = ListenSocket, Timeout) -> ssl_accept(ListenSocket) -> ssl_accept(ListenSocket, infinity). -ssl_accept(#sslsocket{pid = Pid, fd = new_ssl}, Timeout) -> - gen_fsm:send_event(Pid, socket_control), - try gen_fsm:sync_send_all_state_event(Pid, started, Timeout) of - connected -> - ok; - {error, _} = Error -> - Error - catch - exit:{noproc, _} -> - {error, closed}; - exit:{timeout, _} -> - {error, timeout}; - exit:{normal, _} -> - {error, closed} - end; - +ssl_accept(#sslsocket{fd = new_ssl} = Socket, Timeout) -> + ssl_connection:handshake(Socket, Timeout); + ssl_accept(ListenSocket, SslOptions) when is_port(ListenSocket) -> ssl_accept(ListenSocket, SslOptions, infinity); @@ -217,7 +204,7 @@ ssl_accept(Socket, SslOptions, Timeout) when is_port(Socket) -> try handle_options(SslOptions ++ InetValues, server) of {ok, #config{cb=CbInfo,ssl=SslOpts, emulated=EmOpts}} -> {ok, Port} = inet:port(Socket), - ssl_connection:accept(Port, Socket, + ssl_connection:ssl_accept(Port, Socket, {SslOpts, EmOpts}, self(), CbInfo, Timeout) catch @@ -229,7 +216,7 @@ ssl_accept(Socket, SslOptions, Timeout) when is_port(Socket) -> %% %% Description: Close a ssl connection %%-------------------------------------------------------------------- -close(#sslsocket{pid = {ListenSocket, #config{cb={CbMod,_, _}}}, fd = new_ssl}) -> +close(#sslsocket{pid = {ListenSocket, #config{cb={CbMod,_, _, _}}}, fd = new_ssl}) -> CbMod:close(ListenSocket); close(#sslsocket{pid = Pid, fd = new_ssl}) -> ssl_connection:close(Pid); @@ -238,7 +225,7 @@ close(Socket = #sslsocket{}) -> ssl_broker:close(Socket). %%-------------------------------------------------------------------- -%% Function: send(Socket, Data) -> ok +%% Function: send(Socket, Data) -> ok | {error, Reason} %% %% Description: Sends data over the ssl connection %%-------------------------------------------------------------------- @@ -388,7 +375,7 @@ setopts(#sslsocket{} = Socket, Options) -> %% %% Description: Same as gen_tcp:shutdown/2 %%-------------------------------------------------------------------- -shutdown(#sslsocket{pid = {ListenSocket, #config{cb={CbMod,_, _}}}, fd = new_ssl}, How) -> +shutdown(#sslsocket{pid = {ListenSocket, #config{cb={CbMod,_, _, _}}}, fd = new_ssl}, How) -> CbMod:shutdown(ListenSocket, How); shutdown(#sslsocket{pid = Pid, fd = new_ssl}, How) -> ssl_connection:shutdown(Pid, How). @@ -462,7 +449,7 @@ do_new_connect(Address, Port, #config{cb=CbInfo, inet_user=UserOpts, ssl=SslOpts, emulated=EmOpts,inet_ssl=SocketOpts}, Timeout) -> - {CbModule, _, _} = CbInfo, + {CbModule, _, _, _} = CbInfo, try CbModule:connect(Address, Port, SocketOpts, Timeout) of {ok, Socket} -> ssl_connection:connect(Address, Port, Socket, {SslOpts,EmOpts}, @@ -484,7 +471,7 @@ old_connect(Address, Port, Options, Timeout) -> new_listen(Port, Options0) -> try {ok, Config} = handle_options(Options0, server), - #config{cb={CbModule, _, _},inet_user=Options} = Config, + #config{cb={CbModule, _, _, _},inet_user=Options} = Config, case CbModule:listen(Port, Options) of {ok, ListenSocket} -> {ok, #sslsocket{pid = {ListenSocket, Config}, fd = new_ssl}}; @@ -559,17 +546,18 @@ handle_options(Opts0, Role) -> %% Server side option reuse_session = handle_option(reuse_session, Opts, ReuseSessionFun), reuse_sessions = handle_option(reuse_sessions, Opts, true), + secure_renegotiate = handle_option(secure_renegotiate, Opts, false), renegotiate_at = handle_option(renegotiate_at, Opts, ?DEFAULT_RENEGOTIATE_AT), debug = handle_option(debug, Opts, []) }, - CbInfo = proplists:get_value(cb_info, Opts, {gen_tcp, tcp, tcp_closed}), + CbInfo = proplists:get_value(cb_info, Opts, {gen_tcp, tcp, tcp_closed, tcp_error}), SslOptions = [versions, verify, verify_fun, validate_extensions_fun, fail_if_no_peer_cert, verify_client_once, depth, certfile, keyfile, key, password, cacertfile, dhfile, ciphers, debug, reuse_session, reuse_sessions, ssl_imp, - cb_info, renegotiate_at], + cb_info, renegotiate_at, secure_renegotiate], SockOpts = lists:foldl(fun(Key, PropList) -> proplists:delete(Key, PropList) @@ -640,6 +628,10 @@ validate_option(reuse_session, Value) when is_function(Value) -> validate_option(reuse_sessions, Value) when Value == true; Value == false -> Value; + +validate_option(secure_renegotiate, Value) when Value == true; + Value == false -> + Value; validate_option(renegotiate_at, Value) when is_integer(Value) -> min(Value, ?DEFAULT_RENEGOTIATE_AT); @@ -748,24 +740,34 @@ cipher_suites(Version, Ciphers0) -> format_error({error, Reason}) -> format_error(Reason); +format_error(Reason) when is_list(Reason) -> + Reason; format_error(closed) -> - "Connection closed for the operation in question."; + "The connection is closed"; +format_error(ecacertfile) -> + "Own CA certificate file is invalid."; +format_error(ecertfile) -> + "Own certificate file is invalid."; +format_error(ekeyfile) -> + "Own private key file is invalid."; +format_error(esslaccept) -> + "Server SSL handshake procedure between client and server failed."; +format_error(esslconnect) -> + "Client SSL handshake procedure between client and server failed."; +format_error({eoptions, Options}) -> + lists:flatten(io_lib:format("Error in options list: ~p~n", [Options])); + +%%%%%%%%%%%% START OLD SSL format_error %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% format_error(ebadsocket) -> "Connection not found (internal error)."; format_error(ebadstate) -> "Connection not in connect state (internal error)."; format_error(ebrokertype) -> "Wrong broker type (internal error)."; -format_error(ecacertfile) -> - "Own CA certificate file is invalid."; -format_error(ecertfile) -> - "Own certificate file is invalid."; format_error(echaintoolong) -> "The chain of certificates provided by peer is too long."; format_error(ecipher) -> "Own list of specified ciphers is invalid."; -format_error(ekeyfile) -> - "Own private key file is invalid."; format_error(ekeymismatch) -> "Own private key does not match own certificate."; format_error(enoissuercert) -> @@ -791,10 +793,6 @@ format_error(epeercertinvalid) -> "Certificate provided by peer is invalid."; format_error(eselfsignedcert) -> "Certificate provided by peer is self signed."; -format_error(esslaccept) -> - "Server SSL handshake procedure between client and server failed."; -format_error(esslconnect) -> - "Client SSL handshake procedure between client and server failed."; format_error(esslerrssl) -> "SSL protocol failure. Typically because of a fatal alert from peer."; format_error(ewantconnect) -> @@ -813,6 +811,9 @@ format_error({badcast, _Cast}) -> format_error({badinfo, _Info}) -> "Call not recognized for current mode (active or passive) and state " "of socket."; + +%%%%%%%%%%%%%%%%%% END OLD SSL format_error %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + format_error(Error) -> case (catch inet:format_error(Error)) of "unkknown POSIX" ++ _ -> @@ -824,7 +825,7 @@ format_error(Error) -> end. no_format(Error) -> - io_lib:format("No format string for error: \"~p\" available.", [Error]). + lists:flatten(io_lib:format("No format string for error: \"~p\" available.", [Error])). %% Start old ssl port program if needed. ensure_old_ssl_started() -> diff --git a/lib/ssl/src/ssl_alert.erl b/lib/ssl/src/ssl_alert.erl index d3f9c833f1..db9a883654 100644 --- a/lib/ssl/src/ssl_alert.erl +++ b/lib/ssl/src/ssl_alert.erl @@ -1,19 +1,19 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2007-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 2007-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% %% @@ -38,10 +38,6 @@ reason_code(#alert{description = ?HANDSHAKE_FAILURE}, client) -> esslconnect; reason_code(#alert{description = ?HANDSHAKE_FAILURE}, server) -> esslaccept; -reason_code(#alert{description = ?CERTIFICATE_EXPIRED}, _) -> - epeercertexpired; -reason_code(#alert{level = ?FATAL}, _) -> - esslerrssl; reason_code(#alert{description = Description}, _) -> description_txt(Description). @@ -55,51 +51,51 @@ level_txt(?FATAL) -> "Fatal error:". description_txt(?CLOSE_NOTIFY) -> - "close_notify"; + "close notify"; description_txt(?UNEXPECTED_MESSAGE) -> - "unexpected_message"; + "unexpected message"; description_txt(?BAD_RECORD_MAC) -> - "bad_record_mac"; + "bad record mac"; description_txt(?DECRYPTION_FAILED) -> - "decryption_failed"; + "decryption failed"; description_txt(?RECORD_OVERFLOW) -> - "record_overflow"; + "record overflow"; description_txt(?DECOMPRESSION_FAILURE) -> - "decompression_failure"; + "decompression failure"; description_txt(?HANDSHAKE_FAILURE) -> - "handshake_failure"; + "handshake failure"; description_txt(?BAD_CERTIFICATE) -> - "bad_certificate"; + "bad certificate"; description_txt(?UNSUPPORTED_CERTIFICATE) -> - "unsupported_certificate"; + "unsupported certificate"; description_txt(?CERTIFICATE_REVOKED) -> - "certificate_revoked"; + "certificate revoked"; description_txt(?CERTIFICATE_EXPIRED) -> - "certificate_expired"; + "certificate expired"; description_txt(?CERTIFICATE_UNKNOWN) -> - "certificate_unknown"; + "certificate unknown"; description_txt(?ILLEGAL_PARAMETER) -> - "illegal_parameter"; + "illegal parameter"; description_txt(?UNKNOWN_CA) -> - "unknown_ca"; + "unknown ca"; description_txt(?ACCESS_DENIED) -> - "access_denied"; + "access denied"; description_txt(?DECODE_ERROR) -> - "decode_error"; + "decode error"; description_txt(?DECRYPT_ERROR) -> - "decrypt_error"; + "decrypt error"; description_txt(?EXPORT_RESTRICTION) -> - "export_restriction"; + "export restriction"; description_txt(?PROTOCOL_VERSION) -> - "protocol_version"; + "protocol version"; description_txt(?INSUFFICIENT_SECURITY) -> - "insufficient_security"; + "insufficient security"; description_txt(?INTERNAL_ERROR) -> - "internal_error"; + "internal error"; description_txt(?USER_CANCELED) -> - "user_canceled"; + "user canceled"; description_txt(?NO_RENEGOTIATION) -> - "no_renegotiation". + "no renegotiation". diff --git a/lib/ssl/src/ssl_cipher.hrl b/lib/ssl/src/ssl_cipher.hrl index 4304c501b7..d282cbd780 100644 --- a/lib/ssl/src/ssl_cipher.hrl +++ b/lib/ssl/src/ssl_cipher.hrl @@ -1,19 +1,19 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2007-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 2007-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% %% @@ -250,4 +250,9 @@ -define(TLS_DHE_DSS_EXPORT1024_WITH_RC4_56_SHA, <<?BYTE(16#00), ?BYTE(16#65)>>). -define(TLS_DHE_DSS_WITH_RC4_128_SHA, <<?BYTE(16#00), ?BYTE(16#66)>>). +%% RFC 5746 - Not a real ciphersuite used to signal empty "renegotiation_info" extension +%% to avoid handshake failure from old servers that do not ignore +%% hello extension data as they should. +-define(TLS_EMPTY_RENEGOTIATION_INFO_SCSV, <<?BYTE(16#00), ?BYTE(16#FF)>>). + -endif. % -ifdef(ssl_cipher). diff --git a/lib/ssl/src/ssl_connection.erl b/lib/ssl/src/ssl_connection.erl index 8ff001b172..a9ddc44edf 100644 --- a/lib/ssl/src/ssl_connection.erl +++ b/lib/ssl/src/ssl_connection.erl @@ -39,7 +39,8 @@ -include_lib("public_key/include/public_key.hrl"). %% Internal application API --export([send/2, send/3, recv/3, connect/7, accept/6, close/1, shutdown/2, +-export([send/2, send/3, recv/3, connect/7, ssl_accept/6, handshake/2, + socket_control/3, close/1, shutdown/2, new_user/2, get_opts/2, set_opts/2, info/1, session_info/1, peer_certificate/1, sockname/1, peername/1, renegotiation/1]). @@ -57,6 +58,7 @@ transport_cb, % atom() - callback module data_tag, % atom() - ex tcp. close_tag, % atom() - ex tcp_closed + error_tag, % atom() - ex tcp_error host, % string() | ipadress() port, % integer() socket, % socket() @@ -101,41 +103,70 @@ %%==================================================================== %%-------------------------------------------------------------------- -%% Function: +%% Function: send(Pid, Data) -> ok | {error, Reason} %% -%% Description: +%% Description: Sends data over the ssl connection %%-------------------------------------------------------------------- send(Pid, Data) -> sync_send_all_state_event(Pid, {application_data, erlang:iolist_to_binary(Data)}, infinity). send(Pid, Data, Timeout) -> sync_send_all_state_event(Pid, {application_data, erlang:iolist_to_binary(Data)}, Timeout). %%-------------------------------------------------------------------- -%% Function: +%% Function: recv(Socket, Length Timeout) -> {ok, Data} | {error, reason} %% -%% Description: +%% Description: Receives data when active = false %%-------------------------------------------------------------------- recv(Pid, Length, Timeout) -> sync_send_all_state_event(Pid, {recv, Length}, Timeout). %%-------------------------------------------------------------------- -%% Function: +%% Function: : connect(Host, Port, Socket, Options, +%% User, CbInfo, Timeout) -> {ok, Socket} %% -%% Description: +%% Description: Connect to a ssl server. %%-------------------------------------------------------------------- connect(Host, Port, Socket, Options, User, CbInfo, Timeout) -> start_fsm(client, Host, Port, Socket, Options, User, CbInfo, Timeout). %%-------------------------------------------------------------------- -%% Function: +%% Function: accept(Port, Socket, Opts, User, +%% CbInfo, Timeout) -> {ok, Socket} | {error, Reason} %% -%% Description: +%% Description: Performs accept on a ssl listen socket. e.i. performs +%% ssl handshake. %%-------------------------------------------------------------------- -accept(Port, Socket, Opts, User, CbInfo, Timeout) -> +ssl_accept(Port, Socket, Opts, User, CbInfo, Timeout) -> start_fsm(server, "localhost", Port, Socket, Opts, User, CbInfo, Timeout). + %%-------------------------------------------------------------------- -%% Function: +%% Function: handshake(SslSocket, Timeout) -> ok | {error, Reason} +%% +%% Description: Starts ssl handshake. +%%-------------------------------------------------------------------- +handshake(#sslsocket{pid = Pid}, Timeout) -> + case sync_send_all_state_event(Pid, start, Timeout) of + connected -> + ok; + Error -> + Error + end. +%-------------------------------------------------------------------- +%% Function: socket_control(Pid) -> {ok, SslSocket} | {error, Reason} %% -%% Description: +%% Description: Set the ssl process to own the accept socket +%%-------------------------------------------------------------------- +socket_control(Socket, Pid, CbModule) -> + case CbModule:controlling_process(Socket, Pid) of + ok -> + {ok, sslsocket(Pid)}; + {error, Reason} -> + {error, Reason} + end. + +%%-------------------------------------------------------------------- +%% Function: close() -> ok +%% +%% Description: Close a ssl connection %%-------------------------------------------------------------------- close(ConnectionPid) -> case sync_send_all_state_event(ConnectionPid, close) of @@ -146,80 +177,81 @@ close(ConnectionPid) -> end. %%-------------------------------------------------------------------- -%% Function: +%% Function: shutdown(Socket, How) -> ok | {error, Reason} %% -%% Description: +%% Description: Same as gen_tcp:shutdown/2 %%-------------------------------------------------------------------- shutdown(ConnectionPid, How) -> sync_send_all_state_event(ConnectionPid, {shutdown, How}). - %%-------------------------------------------------------------------- -%% Function: +%% Function: new_user(ConnectionPid, User) -> ok | {error, Reason} %% -%% Description: +%% Description: Changes process that receives the messages when active = true +%% or once. %%-------------------------------------------------------------------- new_user(ConnectionPid, User) -> sync_send_all_state_event(ConnectionPid, {new_user, User}). %%-------------------------------------------------------------------- -%% Function: +%% Function: sockname(ConnectionPid) -> {ok, {Address, Port}} | {error, Reason} %% -%% Description: +%% Description: Same as inet:sockname/1 %%-------------------------------------------------------------------- sockname(ConnectionPid) -> sync_send_all_state_event(ConnectionPid, sockname). %%-------------------------------------------------------------------- -%% Function: +%% Function: peername(ConnectionPid) -> {ok, {Address, Port}} | {error, Reason} %% -%% Description: +%% Description: Same as inet:peername/1 %%-------------------------------------------------------------------- peername(ConnectionPid) -> sync_send_all_state_event(ConnectionPid, peername). %%-------------------------------------------------------------------- -%% Function: +%% Function: get_opts(ConnectionPid, OptTags) -> {ok, Options} | {error, Reason} %% -%% Description: +%% Description: Same as inet:getopts/2 %%-------------------------------------------------------------------- get_opts({ListenSocket, {_SslOpts, SockOpts}, _}, OptTags) -> get_socket_opts(ListenSocket, OptTags, SockOpts, []); get_opts(ConnectionPid, OptTags) -> sync_send_all_state_event(ConnectionPid, {get_opts, OptTags}). %%-------------------------------------------------------------------- -%% Function: +%% Function: setopts(Socket, Options) -> ok | {error, Reason} %% -%% Description: +%% Description: Same as inet:setopts/2 %%-------------------------------------------------------------------- set_opts(ConnectionPid, Options) -> sync_send_all_state_event(ConnectionPid, {set_opts, Options}). %%-------------------------------------------------------------------- -%% Function: +%% Function: info(ConnectionPid) -> {ok, {Protocol, CipherSuite}} | +%% {error, Reason} %% -%% Description: +%% Description: Returns ssl protocol and cipher used for the connection %%-------------------------------------------------------------------- info(ConnectionPid) -> sync_send_all_state_event(ConnectionPid, info). %%-------------------------------------------------------------------- -%% Function: +%% Function: session_info(ConnectionPid) -> {ok, PropList} | {error, Reason} %% -%% Description: +%% Description: Returns info about the ssl session %%-------------------------------------------------------------------- session_info(ConnectionPid) -> sync_send_all_state_event(ConnectionPid, session_info). %%-------------------------------------------------------------------- -%% Function: +%% Function: peercert(ConnectionPid) -> {ok, Cert} | {error, Reason} %% -%% Description: +%% Description: Returns the peer cert %%-------------------------------------------------------------------- peer_certificate(ConnectionPid) -> sync_send_all_state_event(ConnectionPid, peer_certificate). %%-------------------------------------------------------------------- -%% Function: +%% Function: renegotiation(ConnectionPid) -> ok | {error, Reason} %% -%% Description: +%% Description: Starts a renegotiation of the ssl session. %%-------------------------------------------------------------------- renegotiation(ConnectionPid) -> sync_send_all_state_event(ConnectionPid, renegotiate). @@ -269,7 +301,7 @@ init([Role, Host, Port, Socket, {SSLOpts, _} = Options, throw:Error -> {stop, Error} end. - + %%-------------------------------------------------------------------- %% Function: %% state_name(Event, State) -> {next_state, NextStateName, NextState}| @@ -282,32 +314,38 @@ init([Role, Host, Port, Socket, {SSLOpts, _} = Options, %% using gen_fsm:send_event/2, the instance of this function with the %% same name as the current state name StateName is called to handle %% the event. It is also called if a timeout occurs. +%% %%-------------------------------------------------------------------- -hello(socket_control, #state{host = Host, port = Port, role = client, - ssl_options = SslOpts, - transport_cb = Transport, socket = Socket, - connection_states = ConnectionStates} +hello(start, #state{host = Host, port = Port, role = client, + ssl_options = SslOpts, + transport_cb = Transport, socket = Socket, + connection_states = ConnectionStates, + renegotiation = {Renegotiation, _}} = State0) -> Hello = ssl_handshake:client_hello(Host, Port, - ConnectionStates, SslOpts), + ConnectionStates, SslOpts, Renegotiation), + Version = Hello#client_hello.client_version, Hashes0 = ssl_handshake:init_hashes(), {BinMsg, CS2, Hashes1} = encode_handshake(Hello, Version, ConnectionStates, Hashes0), Transport:send(Socket, BinMsg), - State = State0#state{connection_states = CS2, + State1 = State0#state{connection_states = CS2, negotiated_version = Version, %% Requested version session = #session{session_id = Hello#client_hello.session_id, is_resumable = false}, tls_handshake_hashes = Hashes1}, - {next_state, hello, next_record(State)}; + {Record, State} = next_record(State1), + next_state(hello, Record, State); -hello(socket_control, #state{role = server} = State) -> - {next_state, hello, next_record(State)}; +hello(start, #state{role = server} = State0) -> + {Record, State} = next_record(State0), + next_state(hello, Record, State); -hello(#hello_request{}, #state{role = client} = State) -> - {next_state, hello, State}; +hello(#hello_request{}, #state{role = client} = State0) -> + {Record, State} = next_record(State0), + next_state(hello, Record, State); hello(#server_hello{cipher_suite = CipherSuite, compression_method = Compression} = Hello, @@ -316,54 +354,60 @@ hello(#server_hello{cipher_suite = CipherSuite, role = client, negotiated_version = ReqVersion, host = Host, port = Port, + renegotiation = {Renegotiation, _}, + ssl_options = SslOptions, session_cache = Cache, session_cache_cb = CacheCb} = State0) -> - {Version, NewId, ConnectionStates1} = - ssl_handshake:hello(Hello, ConnectionStates0), - - {KeyAlgorithm, _, _, _} = - ssl_cipher:suite_definition(CipherSuite), - - PremasterSecret = make_premaster_secret(ReqVersion, KeyAlgorithm), - - State = State0#state{key_algorithm = KeyAlgorithm, - negotiated_version = Version, - connection_states = ConnectionStates1, - premaster_secret = PremasterSecret}, - - case ssl_session:is_new(OldId, NewId) of - true -> - Session = Session0#session{session_id = NewId, - cipher_suite = CipherSuite, + case ssl_handshake:hello(Hello, SslOptions, ConnectionStates0, Renegotiation) of + {Version, NewId, ConnectionStates1} -> + {KeyAlgorithm, _, _, _} = + ssl_cipher:suite_definition(CipherSuite), + + PremasterSecret = make_premaster_secret(ReqVersion, KeyAlgorithm), + + State1 = State0#state{key_algorithm = KeyAlgorithm, + negotiated_version = Version, + connection_states = ConnectionStates1, + premaster_secret = PremasterSecret}, + + case ssl_session:is_new(OldId, NewId) of + true -> + Session = Session0#session{session_id = NewId, + cipher_suite = CipherSuite, compression_method = Compression}, - {next_state, certify, - next_record(State#state{session = Session})}; - false -> - Session = CacheCb:lookup(Cache, {{Host, Port}, NewId}), - case ssl_handshake:master_secret(Version, Session, - ConnectionStates1, client) of - {_, ConnectionStates2} -> - {next_state, abbreviated, - next_record(State#state{ - connection_states = ConnectionStates2, - session = Session})}; - #alert{} = Alert -> - handle_own_alert(Alert, Version, hello, State), - {stop, normal, State} - end + {Record, State} = next_record(State1#state{session = Session}), + next_state(certify, Record, State); + false -> + Session = CacheCb:lookup(Cache, {{Host, Port}, NewId}), + case ssl_handshake:master_secret(Version, Session, + ConnectionStates1, client) of + {_, ConnectionStates2} -> + {Record, State} = + next_record(State1#state{ + connection_states = ConnectionStates2, + session = Session}), + next_state(abbreviated, Record, State); + #alert{} = Alert -> + handle_own_alert(Alert, Version, hello, State1), + {stop, normal, State1} + end + end; + #alert{} = Alert -> + handle_own_alert(Alert, ReqVersion, hello, State0), + {stop, normal, State0} end; hello(Hello = #client_hello{client_version = ClientVersion}, State = #state{connection_states = ConnectionStates0, port = Port, session = Session0, - session_cache = Cache, + renegotiation = {Renegotiation, _}, + session_cache = Cache, session_cache_cb = CacheCb, ssl_options = SslOpts}) -> - case ssl_handshake:hello(Hello, {Port, SslOpts, - Session0, Cache, CacheCb, - ConnectionStates0}) of + case ssl_handshake:hello(Hello, SslOpts, {Port, Session0, Cache, CacheCb, + ConnectionStates0}, Renegotiation) of {Version, {Type, Session}, ConnectionStates} -> do_server_hello(Type, State#state{connection_states = ConnectionStates, @@ -372,50 +416,59 @@ hello(Hello = #client_hello{client_version = ClientVersion}, #alert{} = Alert -> handle_own_alert(Alert, ClientVersion, hello, State), {stop, normal, State} - end. + end; -abbreviated(socket_control, #state{role = server} = State) -> - {next_state, abbreviated, State}; -abbreviated(#hello_request{}, State) -> - {next_state, certify, State}; +hello(Msg, State) -> + handle_unexpected_message(Msg, hello, State). -abbreviated(Finished = #finished{}, +abbreviated(#hello_request{}, State0) -> + {Record, State} = next_record(State0), + next_state(hello, Record, State); + +abbreviated(Finished = #finished{verify_data = Data}, #state{role = server, negotiated_version = Version, tls_handshake_hashes = Hashes, - session = #session{master_secret = MasterSecret}} = - State0) -> + session = #session{master_secret = MasterSecret}, + connection_states = ConnectionStates0} = + State) -> case ssl_handshake:verify_connection(Version, Finished, client, MasterSecret, Hashes) of - verified -> - State = ack_connection(State0), - next_state_connection(State); + verified -> + ConnectionStates = ssl_record:set_client_verify_data(current_both, Data, ConnectionStates0), + next_state_connection(abbreviated, + ack_connection(State#state{connection_states = ConnectionStates})); #alert{} = Alert -> - handle_own_alert(Alert, Version, abbreviated, State0), - {stop, normal, State0} + handle_own_alert(Alert, Version, abbreviated, State), + {stop, normal, State} end; -abbreviated(Finished = #finished{}, +abbreviated(Finished = #finished{verify_data = Data}, #state{role = client, tls_handshake_hashes = Hashes0, session = #session{master_secret = MasterSecret}, - negotiated_version = Version} = State0) -> + negotiated_version = Version, + connection_states = ConnectionStates0} = State) -> case ssl_handshake:verify_connection(Version, Finished, server, MasterSecret, Hashes0) of verified -> - {ConnectionStates, Hashes} = finalize_client_handshake(State0), - State = ack_connection(State0), - next_state_connection(State#state{tls_handshake_hashes = Hashes, - connection_states = - ConnectionStates}); + ConnectionStates1 = ssl_record:set_server_verify_data(current_read, Data, ConnectionStates0), + {ConnectionStates, Hashes} = + finalize_handshake(State#state{connection_states = ConnectionStates1}, abbreviated), + next_state_connection(abbreviated, + ack_connection(State#state{tls_handshake_hashes = Hashes, + connection_states = + ConnectionStates})); #alert{} = Alert -> - handle_own_alert(Alert, Version, abbreviated, State0), - {stop, normal, State0} - end. + handle_own_alert(Alert, Version, abbreviated, State), + {stop, normal, State} + end; -certify(socket_control, #state{role = server} = State) -> - {next_state, certify, State}; -certify(#hello_request{}, State) -> - {next_state, certify, State}; +abbreviated(Msg, State) -> + handle_unexpected_message(Msg, abbreviated, State). + +certify(#hello_request{}, State0) -> + {Record, State} = next_record(State0), + next_state(hello, Record, State); certify(#certificate{asn1_certificates = []}, #state{role = server, negotiated_version = Version, @@ -430,9 +483,9 @@ certify(#certificate{asn1_certificates = []}, #state{role = server, ssl_options = #ssl_options{verify = verify_peer, fail_if_no_peer_cert = false}} = - State) -> - {next_state, certify, - next_record(State#state{client_certificate_requested = false})}; + State0) -> + {Record, State} = next_record(State0#state{client_certificate_requested = false}), + next_state(certify, Record, State); certify(#certificate{} = Cert, #state{negotiated_version = Version, @@ -456,8 +509,9 @@ certify(#server_key_exchange{} = KeyExchangeMsg, key_algorithm = Alg} = State0) when Alg == dhe_dss; Alg == dhe_rsa ->%%Not imp:Alg == dh_anon;Alg == krb5 -> case handle_server_key(KeyExchangeMsg, State0) of - #state{} = State -> - {next_state, certify, next_record(State)}; + #state{} = State1 -> + {Record, State} = next_record(State1), + next_state(certify, Record, State); #alert{} = Alert -> handle_own_alert(Alert, Version, certify_server_keyexchange, State0), @@ -472,10 +526,9 @@ certify(#server_key_exchange{}, handle_own_alert(Alert, Version, certify_server_key_exchange, State), {stop, normal, State}; -certify(#certificate_request{}, State) -> - NewState = State#state{client_certificate_requested = true}, - {next_state, certify, next_record(NewState)}; - +certify(#certificate_request{}, State0) -> + {Record, State} = next_record(State0#state{client_certificate_requested = true}), + next_state(certify, Record, State); %% Master secret was determined with help of server-key exchange msg certify(#server_hello_done{}, @@ -540,9 +593,10 @@ certify(#client_key_exchange{exchange_keys ConnectionStates0, server) of {MasterSecret, ConnectionStates} -> Session = Session0#session{master_secret = MasterSecret}, - State = State0#state{connection_states = ConnectionStates, + State1 = State0#state{connection_states = ConnectionStates, session = Session}, - {next_state, cipher, next_record(State)}; + {Record, State} = next_record(State1), + next_state(cipher, Record, State); #alert{} = Alert -> handle_own_alert(Alert, Version, certify_client_key_exchange, State0), @@ -574,21 +628,25 @@ certify(#client_key_exchange{exchange_keys = #client_diffie_hellman_public{ case ssl_handshake:master_secret(Version, PremasterSecret, ConnectionStates0, Role) of {MasterSecret, ConnectionStates} -> - State = State0#state{session = + State1 = State0#state{session = Session#session{master_secret = MasterSecret}, connection_states = ConnectionStates}, - {next_state, cipher, next_record(State)}; + + {Record, State} = next_record(State1), + next_state(cipher, Record, State); #alert{} = Alert -> handle_own_alert(Alert, Version, certify_client_key_exchange, State0), {stop, normal, State0} - end. + end; + +certify(Msg, State) -> + handle_unexpected_message(Msg, certify, State). -cipher(socket_control, #state{role = server} = State) -> - {next_state, cipher, State}; -cipher(#hello_request{}, State) -> - {next_state, cipher, State}; +cipher(#hello_request{}, State0) -> + {Record, State} = next_record(State0), + next_state(hello, Record, State); cipher(#certificate_verify{signature = Signature}, #state{role = server, @@ -597,71 +655,83 @@ cipher(#certificate_verify{signature = Signature}, session = #session{master_secret = MasterSecret}, key_algorithm = Algorithm, tls_handshake_hashes = Hashes - } = State) -> + } = State0) -> case ssl_handshake:certificate_verify(Signature, PublicKeyInfo, Version, MasterSecret, Algorithm, Hashes) of valid -> - {next_state, cipher, next_record(State)}; + {Record, State} = next_record(State0), + next_state(cipher, Record, State); #alert{} = Alert -> - handle_own_alert(Alert, Version, cipher, State), - {stop, normal, State} + handle_own_alert(Alert, Version, cipher, State0), + {stop, normal, State0} end; -cipher(#finished{} = Finished, +cipher(#finished{verify_data = Data} = Finished, #state{negotiated_version = Version, host = Host, port = Port, role = Role, session = #session{master_secret = MasterSecret} = Session0, - tls_handshake_hashes = Hashes} = State0) -> + tls_handshake_hashes = Hashes0, + connection_states = ConnectionStates0} = State) -> case ssl_handshake:verify_connection(Version, Finished, opposite_role(Role), - MasterSecret, Hashes) of + MasterSecret, Hashes0) of verified -> - State = ack_connection(State0), Session = register_session(Role, Host, Port, Session0), case Role of client -> - next_state_connection(State#state{session = Session}); + ConnectionStates = ssl_record:set_server_verify_data(current_both, Data, ConnectionStates0), + next_state_connection(cipher, ack_connection(State#state{session = Session, + connection_states = ConnectionStates})); server -> - {NewConnectionStates, NewHashes} = - finalize_server_handshake(State#state{ - session = Session}), - next_state_connection(State#state{connection_states = - NewConnectionStates, - session = Session, - tls_handshake_hashes = - NewHashes}) + ConnectionStates1 = ssl_record:set_client_verify_data(current_read, Data, ConnectionStates0), + {ConnectionStates, Hashes} = + finalize_handshake(State#state{ + connection_states = ConnectionStates1, + session = Session}, cipher), + next_state_connection(cipher, ack_connection(State#state{connection_states = + ConnectionStates, + session = Session, + tls_handshake_hashes = + Hashes})) end; #alert{} = Alert -> - handle_own_alert(Alert, Version, cipher, State0), - {stop, normal, State0} - end. + handle_own_alert(Alert, Version, cipher, State), + {stop, normal, State} + end; + +cipher(Msg, State) -> + handle_unexpected_message(Msg, cipher, State). -connection(socket_control, #state{role = server} = State) -> - {next_state, connection, State}; -connection(#hello_request{}, State = #state{host = Host, port = Port, - socket = Socket, - ssl_options = SslOpts, - negotiated_version = Version, - transport_cb = Transport, - connection_states = ConnectionStates0, - tls_handshake_hashes = Hashes0}) -> +connection(#hello_request{}, #state{host = Host, port = Port, + socket = Socket, + ssl_options = SslOpts, + negotiated_version = Version, + transport_cb = Transport, + connection_states = ConnectionStates0, + renegotiation = {Renegotiation, _}, + tls_handshake_hashes = Hashes0} = State0) -> + Hello = ssl_handshake:client_hello(Host, Port, - ConnectionStates0, SslOpts), + ConnectionStates0, SslOpts, Renegotiation), + {BinMsg, ConnectionStates1, Hashes1} = encode_handshake(Hello, Version, ConnectionStates0, Hashes0), Transport:send(Socket, BinMsg), - {next_state, hello, next_record(State#state{connection_states = - ConnectionStates1, - tls_handshake_hashes = Hashes1})}; + {Record, State} = next_record(State0#state{connection_states = + ConnectionStates1, + tls_handshake_hashes = Hashes1}), + next_state(hello, Record, State); connection(#client_hello{} = Hello, #state{role = server} = State) -> - hello(Hello, State). + hello(Hello, State); +connection(Msg, State) -> + handle_unexpected_message(Msg, connection, State). %%-------------------------------------------------------------------- %% Function: %% handle_event(Event, StateName, State) -> {next_state, NextStateName, @@ -673,110 +743,8 @@ connection(#client_hello{} = Hello, #state{role = server} = State) -> %% gen_fsm:send_all_state_event/2, this function is called to handle %% the event. %%-------------------------------------------------------------------- -handle_event(#ssl_tls{type = ?HANDSHAKE, fragment = Data}, - StateName, - State0 = #state{key_algorithm = KeyAlg, - tls_handshake_buffer = Buf0, - negotiated_version = Version}) -> - Handle = - fun({#hello_request{} = Packet, _}, {next_state, connection = SName, State}) -> - %% This message should not be included in handshake - %% message hashes. Starts new handshake (renegotiation) - Hs0 = ssl_handshake:init_hashes(), - ?MODULE:SName(Packet, State#state{tls_handshake_hashes=Hs0, - renegotiation = {true, peer}}); - ({#hello_request{} = Packet, _}, {next_state, SName, State}) -> - %% This message should not be included in handshake - %% message hashes. Already in negotiation so it will be ignored! - ?MODULE:SName(Packet, State); - ({#client_hello{} = Packet, Raw}, {next_state, connection = SName, State}) -> - Hs0 = ssl_handshake:init_hashes(), - Hs1 = ssl_handshake:update_hashes(Hs0, Raw), - ?MODULE:SName(Packet, State#state{tls_handshake_hashes=Hs1, - renegotiation = {true, peer}}); - ({Packet, Raw}, {next_state, SName, State = #state{tls_handshake_hashes=Hs0}}) -> - Hs1 = ssl_handshake:update_hashes(Hs0, Raw), - ?MODULE:SName(Packet, State#state{tls_handshake_hashes=Hs1}); - (_, StopState) -> StopState - end, - try - {Packets, Buf} = ssl_handshake:get_tls_handshake(Data,Buf0, KeyAlg,Version), - Start = {next_state, StateName, State0#state{tls_handshake_buffer = Buf}}, - lists:foldl(Handle, Start, Packets) - catch throw:#alert{} = Alert -> - handle_own_alert(Alert, Version, StateName, State0), - {stop, normal, State0} - end; - -handle_event(#ssl_tls{type = ?APPLICATION_DATA, fragment = Data}, - StateName, State0) -> - case application_data(Data, State0) of - Stop = {stop,_,_} -> - Stop; - State -> - {next_state, StateName, State} - end; - -handle_event(#ssl_tls{type = ?CHANGE_CIPHER_SPEC, fragment = <<1>>} = - _ChangeCipher, - StateName, - State = #state{connection_states = ConnectionStates0}) -> - ?DBG_TERM(_ChangeCipher), - ConnectionStates1 = - ssl_record:activate_pending_connection_state(ConnectionStates0, read), - {next_state, StateName, - next_record(State#state{connection_states = ConnectionStates1})}; - -handle_event(#ssl_tls{type = ?ALERT, fragment = Data}, StateName, State) -> - Alerts = decode_alerts(Data), - ?DBG_TERM(Alerts), - [alert_event(A) || A <- Alerts], - {next_state, StateName, State}; - -handle_event(#alert{level = ?FATAL} = Alert, connection, - #state{from = From, user_application = {_Mon, Pid}, - log_alert = Log, - host = Host, port = Port, session = Session, - role = Role, socket_options = Opts} = State) -> - invalidate_session(Role, Host, Port, Session), - log_alert(Log, connection, Alert), - alert_user(Opts#socket_options.active, Pid, From, Alert, Role), - {stop, normal, State}; -handle_event(#alert{level = ?WARNING, description = ?CLOSE_NOTIFY} = Alert, - connection, #state{from = From, - role = Role, - user_application = {_Mon, Pid}, - socket_options = Opts} = State) -> - alert_user(Opts#socket_options.active, Pid, From, Alert, Role), - {stop, normal, State}; - -handle_event(#alert{level = ?FATAL} = Alert, StateName, - #state{from = From, host = Host, port = Port, session = Session, - log_alert = Log, role = Role} = State) -> - invalidate_session(Role, Host, Port, Session), - log_alert(Log, StateName, Alert), - alert_user(From, Alert, Role), - {stop, normal, State}; -handle_event(#alert{level = ?WARNING, description = ?CLOSE_NOTIFY} = Alert, - _, #state{from = From, role = Role} = State) -> - alert_user(From, Alert, Role), - {stop, normal, State}; - -handle_event(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert, StateName, - #state{log_alert = Log, renegotiation = {true, internal}} = State) -> - log_alert(Log, StateName, Alert), - {stop, normal, State}; - -handle_event(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert, StateName, - #state{log_alert = Log, renegotiation = {true, From}} = State) -> - log_alert(Log, StateName, Alert), - gen_fsm:reply(From, {error, renegotiation_rejected}), - {next_state, connection, next_record(State)}; - -handle_event(#alert{level = ?WARNING, description = ?USER_CANCELED} = Alert, StateName, - #state{log_alert = Log} = State) -> - log_alert(Log, StateName, Alert), - {next_state, StateName, next_record(State)}. +handle_event(_Event, StateName, State) -> + {next_state, StateName, State}. %%-------------------------------------------------------------------- %% Function: @@ -830,27 +798,41 @@ handle_sync_event({application_data, Data}, From, StateName, #state{send_queue = Queue} = State) -> %% In renegotiation priorities handshake, send data when handshake is finished {next_state, StateName, State#state{send_queue = queue:in({From, Data}, Queue)}}; -handle_sync_event(started, From, StateName, State) -> + +handle_sync_event(start, From, hello, State) -> + hello(start, State#state{from = From}); + +%% The two clauses below could happen if a server upgrades a socket in +%% active mode. Note that in this case we are lucky that +%% controlling_process has been evalueated before receiving handshake +%% messages from client. The server should put the socket in passive +%% mode before telling the client that it is willing to upgrade +%% and before calling ssl:ssl_accept/2. These clauses are +%% here to make sure it is the users problem and not owers if +%% they upgrade a active socket. +handle_sync_event(start, _, connection, State) -> + {reply, connected, connection, State}; +handle_sync_event(start, From, StateName, State) -> {next_state, StateName, State#state{from = From}}; -handle_sync_event(close, From, _StateName, State) -> - {stop, normal, ok, State#state{from = From}}; +handle_sync_event(close, _, _StateName, State) -> + {stop, normal, ok, State}; -handle_sync_event({shutdown, How}, From, StateName, +handle_sync_event({shutdown, How}, _, StateName, #state{transport_cb = CbModule, socket = Socket} = State) -> case CbModule:shutdown(Socket, How) of ok -> {reply, ok, StateName, State}; Error -> - {stop, normal, Error, State#state{from = From}} + {stop, normal, Error, State} end; handle_sync_event({recv, N}, From, connection = StateName, State0) -> passive_receive(State0#state{bytes_to_read = N, from = From}, StateName); %% Doing renegotiate wait with handling request until renegotiate is -%% finished. Will be handled by next_state_connection/1. +%% finished. Will be handled by next_state_connection/2. handle_sync_event({recv, N}, From, StateName, State) -> {next_state, StateName, State#state{bytes_to_read = N, from = From, recv_during_renegotiation = true}}; @@ -888,7 +870,13 @@ handle_sync_event({set_opts, Opts0}, _From, StateName, {reply, ok, StateName, State1}; Buffer =:= <<>>, Opts1#socket_options.active =:= false -> %% Need data, set active once - {reply, ok, StateName, next_record_if_active(State1)}; + {Record, State2} = next_record_if_active(State1), + case next_state(StateName, Record, State2) of + {next_state, StateName, State} -> + {reply, ok, StateName, State}; + {stop, Reason, State} -> + {stop, Reason, State} + end; Buffer =:= <<>> -> %% Active once already set {reply, ok, StateName, State1}; @@ -896,10 +884,15 @@ handle_sync_event({set_opts, Opts0}, _From, StateName, case application_data(<<>>, State1) of Stop = {stop,_,_} -> Stop; - State -> - {reply, ok, StateName, State} + {Record, State2} -> + case next_state(StateName, Record, State2) of + {next_state, StateName, State} -> + {reply, ok, StateName, State}; + {stop, Reason, State} -> + {stop, Reason, State} + end end - end; + end; handle_sync_event(renegotiate, From, connection, State) -> renegotiate(State#state{renegotiation = {true, From}}); @@ -939,20 +932,15 @@ handle_sync_event(peer_certificate, _, StateName, %%-------------------------------------------------------------------- %% raw data from TCP, unpack records -handle_info({Protocol, _, Data}, StateName, State = +handle_info({Protocol, _, Data}, StateName, #state{data_tag = Protocol, - negotiated_version = Version, - tls_record_buffer = Buf0, - tls_cipher_texts = CT0}) -> - case ssl_record:get_tls_records(Data, Buf0) of - {Records, Buf1} -> - CT1 = CT0 ++ Records, - {next_state, StateName, - next_record(State#state{tls_record_buffer = Buf1, - tls_cipher_texts = CT1})}; + negotiated_version = Version} = State0) -> + case next_tls_record(Data, State0) of + {Record, State} -> + next_state(StateName, Record, State); #alert{} = Alert -> - handle_own_alert(Alert, Version, StateName, State), - {stop, normal, State} + handle_own_alert(Alert, Version, StateName, State0), + {stop, normal, State0} end; handle_info({CloseTag, Socket}, _StateName, @@ -973,13 +961,28 @@ handle_info({CloseTag, Socket}, _StateName, ?ALERT_REC(?WARNING, ?CLOSE_NOTIFY), Role), {stop, normal, State}; +handle_info({ErrorTag, Socket, econnaborted}, StateName, + #state{socket = Socket, from = User, role = Role, + error_tag = ErrorTag} = State) when StateName =/= connection -> + alert_user(User, ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE), Role), + {stop, normal, State}; + +handle_info({ErrorTag, Socket, Reason}, _, + #state{socket = Socket, from = User, + role = Role, error_tag = ErrorTag} = State) -> + Report = io_lib:format("SSL: Socket error: ~p ~n", [Reason]), + error_logger:info_report(Report), + alert_user(User, ?ALERT_REC(?FATAL, ?CLOSE_NOTIFY), Role), + {stop, normal, State}; + handle_info({'DOWN', MonitorRef, _, _, _}, _, State = #state{user_application={MonitorRef,_Pid}}) -> {stop, normal, State}; -handle_info(A, StateName, State) -> - io:format("SSL: Bad info (state ~w): ~w\n", [StateName, A]), - {stop, bad_info, State}. +handle_info(Msg, StateName, State) -> + Report = io_lib:format("SSL: Got unexpected info: ~p ~n", [Msg]), + error_logger:info_report(Report), + {next_state, StateName, State}. %%-------------------------------------------------------------------- %% Function: terminate(Reason, StateName, State) -> void() @@ -998,12 +1001,14 @@ terminate(_Reason, connection, #state{negotiated_version = Version, {BinAlert, _} = encode_alert(?ALERT_REC(?WARNING,?CLOSE_NOTIFY), Version, ConnectionStates), Transport:send(Socket, BinAlert), + workaround_transport_delivery_problems(Socket, Transport), Transport:close(Socket); terminate(_Reason, _StateName, #state{transport_cb = Transport, socket = Socket, send_queue = SendQueue, renegotiation = Renegotiate}) -> notify_senders(SendQueue), notify_renegotiater(Renegotiate), + workaround_transport_delivery_problems(Socket, Transport), Transport:close(Socket). %%-------------------------------------------------------------------- @@ -1017,18 +1022,21 @@ code_change(_OldVsn, StateName, State, _Extra) -> %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- -start_fsm(Role, Host, Port, Socket, Opts, User, {CbModule, _,_} = CbInfo, +start_fsm(Role, Host, Port, Socket, Opts, User, {CbModule, _,_, _} = CbInfo, Timeout) -> case ssl_connection_sup:start_child([Role, Host, Port, Socket, Opts, User, CbInfo]) of {ok, Pid} -> - CbModule:controlling_process(Socket, Pid), - send_event(Pid, socket_control), - case sync_send_all_state_event(Pid, started, Timeout) of - connected -> - {ok, sslsocket(Pid)}; - {error, Reason} -> - {error, Reason} + case socket_control(Socket, Pid, CbModule) of + {ok, SslSocket} -> + case handshake(SslSocket, Timeout) of + ok -> + {ok, SslSocket}; + {error, Reason} -> + {error, Reason} + end; + {error, Reason} -> + {error, Reason} end; {error, Reason} -> {error, Reason} @@ -1128,15 +1136,8 @@ init_diffie_hellman(DHParamFile, server) -> ?DEFAULT_DIFFIE_HELLMAN_PARAMS end. -send_event(FsmPid, Event) -> - gen_fsm:send_event(FsmPid, Event). - - -send_all_state_event(FsmPid, Event) -> - gen_fsm:send_all_state_event(FsmPid, Event). - sync_send_all_state_event(FsmPid, Event) -> - sync_send_all_state_event(FsmPid, Event, ?DEFAULT_TIMEOUT). + sync_send_all_state_event(FsmPid, Event, infinity). sync_send_all_state_event(FsmPid, Event, Timeout) -> try gen_fsm:sync_send_all_state_event(FsmPid, Event, Timeout) @@ -1149,19 +1150,16 @@ sync_send_all_state_event(FsmPid, Event, Timeout) -> {error, closed} end. -%% Events: #alert{} -alert_event(Alert) -> - send_all_state_event(self(), Alert). - %% We do currently not support cipher suites that use fixed DH. %% If we want to implement that we should add a code %% here to extract DH parameters form cert. handle_peer_cert(PeerCert, PublicKeyInfo, #state{session = Session} = State0) -> - State = State0#state{session = + State1 = State0#state{session = Session#session{peer_certificate = PeerCert}, public_key_info = PublicKeyInfo}, - {next_state, certify, next_record(State)}. + {Record, State} = next_record(State1), + next_state(certify, Record, State). certify_client(#state{client_certificate_requested = true, role = client, connection_states = ConnectionStates0, @@ -1208,31 +1206,34 @@ verify_client_cert(#state{client_certificate_requested = false} = State) -> do_server_hello(Type, #state{negotiated_version = Version, session = Session, - connection_states = ConnectionStates0} + connection_states = ConnectionStates0, + renegotiation = {Renegotiation, _}} = State0) when is_atom(Type) -> ServerHello = ssl_handshake:server_hello(Session#session.session_id, Version, - ConnectionStates0), - State = server_hello(ServerHello, State0), + ConnectionStates0, Renegotiation), + State1 = server_hello(ServerHello, State0), case Type of new -> - do_server_hello(ServerHello, State); + do_server_hello(ServerHello, State1); resumed -> + ConnectionStates1 = State1#state.connection_states, case ssl_handshake:master_secret(Version, Session, - ConnectionStates0, server) of - {_, ConnectionStates1} -> - State1 = State#state{connection_states=ConnectionStates1, - session = Session}, + ConnectionStates1, server) of + {_, ConnectionStates2} -> + State2 = State1#state{connection_states=ConnectionStates2, + session = Session}, {ConnectionStates, Hashes} = - finalize_server_handshake(State1), - Resumed = State1#state{connection_states = - ConnectionStates, - tls_handshake_hashes = Hashes}, - {next_state, abbreviated, next_record(Resumed)}; + finalize_handshake(State2, abbreviated), + State3 = State2#state{connection_states = + ConnectionStates, + tls_handshake_hashes = Hashes}, + {Record, State} = next_record(State3), + next_state(abbreviated, Record, State); #alert{} = Alert -> - handle_own_alert(Alert, Version, hello, State), - {stop, normal, State} + handle_own_alert(Alert, Version, hello, State1), + {stop, normal, State1} end end; @@ -1243,12 +1244,13 @@ do_server_hello(#server_hello{cipher_suite = CipherSuite, negotiated_version = Version} = State0) -> try server_certify_and_key_exchange(State0) of #state{} = State1 -> - State = server_hello_done(State1), + State2 = server_hello_done(State1), Session = Session0#session{session_id = SessionId, cipher_suite = CipherSuite, compression_method = Compression}, - {next_state, certify, State#state{session = Session}} + {Record, State} = next_record(State2#state{session = Session}), + next_state(certify, Record, State) catch #alert{} = Alert -> handle_own_alert(Alert, Version, hello, State0), @@ -1259,16 +1261,16 @@ client_certify_and_key_exchange(#state{negotiated_version = Version} = State0) -> try do_client_certify_and_key_exchange(State0) of State1 = #state{} -> - {ConnectionStates, Hashes} = finalize_client_handshake(State1), - State = State1#state{connection_states = ConnectionStates, + {ConnectionStates, Hashes} = finalize_handshake(State1, certify), + State2 = State1#state{connection_states = ConnectionStates, %% Reinitialize client_certificate_requested = false, tls_handshake_hashes = Hashes}, - {next_state, cipher, next_record(State)} - + {Record, State} = next_record(State2), + next_state(cipher, Record, State) catch #alert{} = Alert -> - handle_own_alert(Alert, Version, certify_foo, State0), + handle_own_alert(Alert, Version, client_certify_and_key_exchange, State0), {stop, normal, State0} end. @@ -1301,17 +1303,16 @@ server_hello_done(#state{transport_cb = Transport, socket = Socket, negotiated_version = Version, connection_states = ConnectionStates, - tls_handshake_hashes = Hashes} = State0) -> + tls_handshake_hashes = Hashes} = State) -> HelloDone = ssl_handshake:server_hello_done(), - + {BinHelloDone, NewConnectionStates, NewHashes} = encode_handshake(HelloDone, Version, ConnectionStates, Hashes), Transport:send(Socket, BinHelloDone), - State = State0#state{connection_states = NewConnectionStates, - tls_handshake_hashes = NewHashes}, - next_record(State). - + State#state{connection_states = NewConnectionStates, + tls_handshake_hashes = NewHashes}. + certify_server(#state{transport_cb = Transport, socket = Socket, negotiated_version = Version, @@ -1471,45 +1472,44 @@ request_client_cert(#state{ssl_options = #ssl_options{verify = verify_none}} = State) -> State. -finalize_client_handshake(#state{connection_states = ConnectionStates0} - = State) -> - ConnectionStates1 = - cipher_protocol(State#state{connection_states = - ConnectionStates0}), - ConnectionStates2 = - ssl_record:activate_pending_connection_state(ConnectionStates1, +finalize_handshake(State, StateName) -> + ConnectionStates0 = cipher_protocol(State), + ConnectionStates = + ssl_record:activate_pending_connection_state(ConnectionStates0, write), - finished(State#state{connection_states = ConnectionStates2}). + finished(State#state{connection_states = ConnectionStates}, StateName). - -finalize_server_handshake(State) -> - ConnectionStates0 = cipher_protocol(State), - ConnectionStates = - ssl_record:activate_pending_connection_state(ConnectionStates0, - write), - finished(State#state{connection_states = ConnectionStates}). - -cipher_protocol(#state{connection_states = ConnectionStates, +cipher_protocol(#state{connection_states = ConnectionStates0, socket = Socket, negotiated_version = Version, transport_cb = Transport}) -> - {BinChangeCipher, NewConnectionStates} = + {BinChangeCipher, ConnectionStates} = encode_change_cipher(#change_cipher_spec{}, - Version, ConnectionStates), + Version, ConnectionStates0), Transport:send(Socket, BinChangeCipher), - NewConnectionStates. + ConnectionStates. finished(#state{role = Role, socket = Socket, negotiated_version = Version, transport_cb = Transport, session = Session, - connection_states = ConnectionStates, - tls_handshake_hashes = Hashes}) -> + connection_states = ConnectionStates0, + tls_handshake_hashes = Hashes0}, StateName) -> MasterSecret = Session#session.master_secret, - Finished = ssl_handshake:finished(Version, Role, MasterSecret, Hashes), - {BinFinished, NewConnectionStates, NewHashes} = - encode_handshake(Finished, Version, ConnectionStates, Hashes), + Finished = ssl_handshake:finished(Version, Role, MasterSecret, Hashes0), + ConnectionStates1 = save_verify_data(Role, Finished, ConnectionStates0, StateName), + {BinFinished, ConnectionStates, Hashes} = + encode_handshake(Finished, Version, ConnectionStates1, Hashes0), Transport:send(Socket, BinFinished), - {NewConnectionStates, NewHashes}. + {ConnectionStates, Hashes}. + +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). handle_server_key( #server_key_exchange{params = @@ -1622,14 +1622,14 @@ decode_alerts(<<>>, Acc) -> passive_receive(State0 = #state{user_data_buffer = Buffer}, StateName) -> case Buffer of <<>> -> - State = next_record(State0), - {next_state, StateName, State}; + {Record, State} = next_record(State0), + next_state(StateName, Record, State); _ -> case application_data(<<>>, State0) of Stop = {stop, _, _} -> Stop; - State -> - {next_state, StateName, State} + {Record, State} -> + next_state(StateName, Record, State) end end. @@ -1654,12 +1654,12 @@ application_data(Data, #state{user_application = {_Mon, Pid}, socket_options = SocketOpt }, if - SocketOpt#socket_options.active =:= false -> - State; %% Passive mode, wait for active once or recv - Buffer =:= <<>> -> %% Active and empty, get more data - next_record(State); - true -> %% We have more data - application_data(<<>>, State) + SocketOpt#socket_options.active =:= false; Buffer =:= <<>> -> + %% Passive mode, wait for active once or recv + %% Active and empty, get more data + next_record_if_active(State); + true -> %% We have more data + application_data(<<>>, State) end; {error,_Reason} -> %% Invalid packet in packet mode deliver_packet_error(SOpts, Buffer1, Pid, From), @@ -1727,25 +1727,38 @@ deliver_app_data(SOpts = #socket_options{active=Active, packet=Type}, SO end. -format_reply(#socket_options{active=false, mode=Mode, header=Header}, Data) -> - {ok, format_reply(Mode, Header, Data)}; -format_reply(#socket_options{active=_, mode=Mode, header=Header}, Data) -> - {ssl, sslsocket(), format_reply(Mode, Header, Data)}. +format_reply(#socket_options{active = false, mode = Mode, packet = Packet, + header = Header}, Data) -> + {ok, format_reply(Mode, Packet, Header, Data)}; +format_reply(#socket_options{active = _, mode = Mode, packet = Packet, + header = Header}, Data) -> + {ssl, sslsocket(), format_reply(Mode, Packet, Header, Data)}. -deliver_packet_error(SO= #socket_options{active=Active}, Data, Pid, From) -> +deliver_packet_error(SO= #socket_options{active = Active}, Data, Pid, From) -> send_or_reply(Active, Pid, From, format_packet_error(SO, Data)). -format_packet_error(#socket_options{active=false, mode=Mode}, Data) -> - {error, {invalid_packet, format_reply(Mode, raw, Data)}}; -format_packet_error(#socket_options{active=_, mode=Mode}, Data) -> - {ssl_error, sslsocket(), {invalid_packet, format_reply(Mode, raw, Data)}}. - -format_reply(list, _, Data) -> binary_to_list(Data); -format_reply(binary, 0, Data) -> Data; -format_reply(binary, raw, Data) -> Data; -format_reply(binary, N, Data) -> % Header mode - <<Header:N/binary, Rest/binary>> = Data, - [binary_to_list(Header), Rest]. +format_packet_error(#socket_options{active = false, mode = Mode}, Data) -> + {error, {invalid_packet, format_reply(Mode, raw, 0, Data)}}; +format_packet_error(#socket_options{active = _, mode = Mode}, Data) -> + {ssl_error, sslsocket(), {invalid_packet, format_reply(Mode, raw, 0, Data)}}. + +format_reply(binary, _, N, Data) when N > 0 -> % Header mode + header(N, Data); +format_reply(binary, _, _, Data) -> Data; +format_reply(list, Packet, _, Data) when is_integer(Packet); Packet == raw -> + binary_to_list(Data); +format_reply(list, _,_, Data) -> + Data. + +header(0, <<>>) -> + <<>>; +header(_, <<>>) -> + []; +header(0, Binary) -> + Binary; +header(N, Binary) -> + <<?BYTE(ByteN), NewBinary/binary>> = Binary, + [ByteN | header(N-1, NewBinary)]. %% tcp_closed send_or_reply(false, _Pid, undefined, _Data) -> @@ -1766,40 +1779,111 @@ opposite_role(server) -> send_user(Pid, Msg) -> Pid ! Msg. +next_state(Next, no_record, State) -> + {next_state, Next, State}; + +next_state(Next, #ssl_tls{type = ?ALERT, fragment = EncAlerts}, State) -> + Alerts = decode_alerts(EncAlerts), + handle_alerts(Alerts, {next_state, Next, State}); + +next_state(StateName, #ssl_tls{type = ?HANDSHAKE, fragment = Data}, + State0 = #state{key_algorithm = KeyAlg, + tls_handshake_buffer = Buf0, + negotiated_version = Version}) -> + Handle = + fun({#hello_request{} = Packet, _}, {next_state, connection = SName, State}) -> + %% This message should not be included in handshake + %% message hashes. Starts new handshake (renegotiation) + Hs0 = ssl_handshake:init_hashes(), + ?MODULE:SName(Packet, State#state{tls_handshake_hashes=Hs0, + renegotiation = {true, peer}}); + ({#hello_request{} = Packet, _}, {next_state, SName, State}) -> + %% This message should not be included in handshake + %% message hashes. Already in negotiation so it will be ignored! + ?MODULE:SName(Packet, State); + ({#client_hello{} = Packet, Raw}, {next_state, connection = SName, State}) -> + Hs0 = ssl_handshake:init_hashes(), + Hs1 = ssl_handshake:update_hashes(Hs0, Raw), + ?MODULE:SName(Packet, State#state{tls_handshake_hashes=Hs1, + renegotiation = {true, peer}}); + ({Packet, Raw}, {next_state, SName, State = #state{tls_handshake_hashes=Hs0}}) -> + Hs1 = ssl_handshake:update_hashes(Hs0, Raw), + ?MODULE:SName(Packet, State#state{tls_handshake_hashes=Hs1}); + (_, StopState) -> StopState + end, + try + {Packets, Buf} = ssl_handshake:get_tls_handshake(Data,Buf0, KeyAlg,Version), + Start = {next_state, StateName, State0#state{tls_handshake_buffer = Buf}}, + lists:foldl(Handle, Start, Packets) + catch throw:#alert{} = Alert -> + handle_own_alert(Alert, Version, StateName, State0), + {stop, normal, State0} + end; + +next_state(StateName, #ssl_tls{type = ?APPLICATION_DATA, fragment = Data}, State0) -> + case application_data(Data, State0) of + Stop = {stop,_,_} -> + Stop; + {Record, State} -> + next_state(StateName, Record, State) + end; +next_state(StateName, #ssl_tls{type = ?CHANGE_CIPHER_SPEC, fragment = <<1>>} = + _ChangeCipher, + #state{connection_states = ConnectionStates0} = State0) -> + ?DBG_TERM(_ChangeCipher), + ConnectionStates1 = + ssl_record:activate_pending_connection_state(ConnectionStates0, read), + {Record, State} = next_record(State0#state{connection_states = ConnectionStates1}), + next_state(StateName, Record, State); +next_state(StateName, #ssl_tls{type = _Unknown}, State0) -> + %% Ignore unknown type + {Record, State} = next_record(State0), + next_state(StateName, Record, State). + +next_tls_record(Data, #state{tls_record_buffer = Buf0, + tls_cipher_texts = CT0} = State0) -> + case ssl_record:get_tls_records(Data, Buf0) of + {Records, Buf1} -> + CT1 = CT0 ++ Records, + next_record(State0#state{tls_record_buffer = Buf1, + tls_cipher_texts = CT1}); + #alert{} = Alert -> + Alert + end. + next_record(#state{tls_cipher_texts = [], socket = Socket} = State) -> inet:setopts(Socket, [{active,once}]), - State; + {no_record, State}; next_record(#state{tls_cipher_texts = [CT | Rest], connection_states = ConnStates0} = State) -> {Plain, ConnStates} = ssl_record:decode_cipher_text(CT, ConnStates0), - gen_fsm:send_all_state_event(self(), Plain), - State#state{tls_cipher_texts = Rest, connection_states = ConnStates}. - + {Plain, State#state{tls_cipher_texts = Rest, connection_states = ConnStates}}. next_record_if_active(State = #state{socket_options = #socket_options{active = false}}) -> - State; + {no_record ,State}; next_record_if_active(State) -> next_record(State). -next_state_connection(#state{send_queue = Queue0, - negotiated_version = Version, - socket = Socket, - transport_cb = Transport, - connection_states = ConnectionStates0, - ssl_options = #ssl_options{renegotiate_at = RenegotiateAt} - } = State) -> +next_state_connection(StateName, #state{send_queue = Queue0, + negotiated_version = Version, + socket = Socket, + transport_cb = Transport, + connection_states = ConnectionStates0, + ssl_options = #ssl_options{renegotiate_at = RenegotiateAt} + } = State) -> %% Send queued up data case queue:out(Queue0) of {{value, {From, Data}}, Queue} -> case encode_data(Data, Version, ConnectionStates0, RenegotiateAt) of {Msgs, [], ConnectionStates} -> Result = Transport:send(Socket, Msgs), - gen_fsm:reply(From, Result), - next_state_connection(State#state{connection_states = ConnectionStates, - send_queue = Queue}); + gen_fsm:reply(From, Result), + next_state_connection(StateName, + State#state{connection_states = ConnectionStates, + send_queue = Queue}); %% This is unlikely to happen. User configuration of the %% undocumented test option renegotiation_at can make it more likely. {Msgs, RestData, ConnectionStates} -> @@ -1822,9 +1906,9 @@ next_state_is_connection(State = #socket_options{active = false}}) -> passive_receive(State#state{recv_during_renegotiation = false}, connection); -next_state_is_connection(State) -> - {next_state, connection, next_record_if_active(State)}. - +next_state_is_connection(State0) -> + {Record, State} = next_record_if_active(State0), + next_state(connection, Record, State). register_session(_, _, _, #session{is_resumable = true} = Session) -> Session; %% Already registered @@ -1843,7 +1927,7 @@ invalidate_session(server, _, Port, Session) -> ssl_manager:invalidate_session(Port, Session). initial_state(Role, Host, Port, Socket, {SSLOptions, SocketOptions}, User, - {CbModule, DataTag, CloseTag}) -> + {CbModule, DataTag, CloseTag, ErrorTag}) -> ConnectionStates = ssl_record:init_connection_states(Role), SessionCacheCb = case application:get_env(ssl, session_cb) of @@ -1863,6 +1947,7 @@ initial_state(Role, Host, Port, Socket, {SSLOptions, SocketOptions}, User, transport_cb = CbModule, data_tag = DataTag, close_tag = CloseTag, + error_tag = ErrorTag, role = Role, host = Host, port = Port, @@ -1934,10 +2019,71 @@ set_socket_opts(Socket, [{active, Active}| Opts], SockOpts, Other) -> set_socket_opts(Socket, [Opt | Opts], SockOpts, Other) -> set_socket_opts(Socket, Opts, SockOpts, [Opt | Other]). +handle_alerts([], Result) -> + Result; +handle_alerts(_, {stop, _, _} = Stop) -> + %% If it is a fatal alert immediately close + Stop; +handle_alerts([Alert | Alerts], {next_state, StateName, State}) -> + handle_alerts(Alerts, handle_alert(Alert, StateName, State)). + +handle_alert(#alert{level = ?FATAL} = Alert, connection, + #state{from = From, user_application = {_Mon, Pid}, + log_alert = Log, + host = Host, port = Port, session = Session, + role = Role, socket_options = Opts} = State) -> + invalidate_session(Role, Host, Port, Session), + log_alert(Log, connection, Alert), + alert_user(Opts#socket_options.active, Pid, From, Alert, Role), + {stop, normal, State}; + +handle_alert(#alert{level = ?WARNING, description = ?CLOSE_NOTIFY} = Alert, + connection, #state{from = From, + role = Role, + user_application = {_Mon, Pid}, + socket_options = Opts} = State) -> + alert_user(Opts#socket_options.active, Pid, From, Alert, Role), + {stop, normal, State}; + +handle_alert(#alert{level = ?FATAL} = Alert, StateName, + #state{from = From, host = Host, port = Port, session = Session, + log_alert = Log, role = Role} = State) -> + invalidate_session(Role, Host, Port, Session), + log_alert(Log, StateName, Alert), + alert_user(From, Alert, Role), + {stop, normal, State}; +handle_alert(#alert{level = ?WARNING, description = ?CLOSE_NOTIFY} = Alert, + _, #state{from = From, role = Role} = State) -> + alert_user(From, Alert, Role), + {stop, normal, State}; + +handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert, StateName, + #state{log_alert = Log, renegotiation = {true, internal}, from = From, + role = Role} = State) -> + log_alert(Log, StateName, Alert), + alert_user(From, Alert, Role), + {stop, normal, State}; + +handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert, StateName, + #state{log_alert = Log, renegotiation = {true, From}} = State0) -> + log_alert(Log, StateName, Alert), + gen_fsm:reply(From, {error, renegotiation_rejected}), + {Record, State} = next_record(State0), + next_state(connection, Record, State); + +handle_alert(#alert{level = ?WARNING, description = ?USER_CANCELED} = Alert, StateName, + #state{log_alert = Log} = State0) -> + log_alert(Log, StateName, Alert), + {Record, State} = next_record(State0), + next_state(StateName, Record, State). + alert_user(From, Alert, Role) -> alert_user(false, no_pid, From, Alert, Role). alert_user(false = Active, Pid, From, Alert, Role) -> + %% If there is an outstanding ssl_accept | recv + %% From will be defined and send_or_reply will + %% send the appropriate error message. ReasonCode = ssl_alert:reason_code(Alert, Role), send_or_reply(Active, Pid, From, {error, ReasonCode}); alert_user(Active, Pid, From, Alert, Role) -> @@ -1965,7 +2111,7 @@ handle_own_alert(Alert, Version, StateName, log_alert = Log}) -> try %% Try to tell the other side {BinMsg, _} = - encode_alert(Alert, Version, ConnectionStates), + encode_alert(Alert, Version, ConnectionStates), Transport:send(Socket, BinMsg) catch _:_ -> %% Can crash if we are in a uninitialized state ignore @@ -1976,6 +2122,12 @@ handle_own_alert(Alert, Version, StateName, catch _:_ -> ok end. + +handle_unexpected_message(_Msg, StateName, #state{negotiated_version = Version} = State) -> + Alert = ?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE), + handle_own_alert(Alert, Version, StateName, State), + {stop, normal, State}. + make_premaster_secret({MajVer, MinVer}, Alg) when Alg == rsa; Alg == dh_dss; Alg == dh_rsa -> @@ -1996,9 +2148,12 @@ ack_connection(#state{renegotiation = {true, Initiater}} = State) ack_connection(#state{renegotiation = {true, From}} = State) -> gen_fsm:reply(From, ok), State#state{renegotiation = undefined}; -ack_connection(#state{renegotiation = {false, first}, from = From} = State) -> +ack_connection(#state{renegotiation = {false, first}, + from = From} = State) when From =/= undefined -> gen_fsm:reply(From, connected), - State#state{renegotiation = undefined}. + State#state{renegotiation = undefined}; +ack_connection(State) -> + State. renegotiate(#state{role = client} = State) -> %% Handle same way as if server requested @@ -2009,16 +2164,18 @@ renegotiate(#state{role = server, socket = Socket, transport_cb = Transport, negotiated_version = Version, - connection_states = ConnectionStates0} = State) -> + connection_states = ConnectionStates0} = State0) -> HelloRequest = ssl_handshake:hello_request(), Frag = ssl_handshake:encode_handshake(HelloRequest, Version, undefined), Hs0 = ssl_handshake:init_hashes(), {BinMsg, ConnectionStates} = ssl_record:encode_handshake(Frag, Version, ConnectionStates0), Transport:send(Socket, BinMsg), - {next_state, hello, next_record(State#state{connection_states = - ConnectionStates, - tls_handshake_hashes = Hs0})}. + {Record, State} = next_record(State0#state{connection_states = + ConnectionStates, + tls_handshake_hashes = Hs0}), + next_state(hello, Record, State). + notify_senders(SendQueue) -> lists:foreach(fun({From, _}) -> gen_fsm:reply(From, {error, closed}) @@ -2028,3 +2185,8 @@ notify_renegotiater({true, From}) when not is_atom(From) -> gen_fsm:reply(From, {error, closed}); notify_renegotiater(_) -> ok. + +workaround_transport_delivery_problems(Socket, Transport) -> + inet:setopts(Socket, [{active, false}]), + Transport:shutdown(Socket, write), + Transport:recv(Socket, 0). diff --git a/lib/ssl/src/ssl_handshake.erl b/lib/ssl/src/ssl_handshake.erl index 9f5ac7106a..f0413c4d31 100644 --- a/lib/ssl/src/ssl_handshake.erl +++ b/lib/ssl/src/ssl_handshake.erl @@ -31,7 +31,7 @@ -include("ssl_debug.hrl"). -include_lib("public_key/include/public_key.hrl"). --export([master_secret/4, client_hello/4, server_hello/3, hello/2, +-export([master_secret/4, client_hello/5, server_hello/4, hello/4, hello_request/0, certify/7, certificate/3, client_certificate_verify/6, certificate_verify/6, certificate_request/2, @@ -57,7 +57,7 @@ %%-------------------------------------------------------------------- client_hello(Host, Port, ConnectionStates, #ssl_options{versions = Versions, ciphers = Ciphers} - = SslOpts) -> + = SslOpts, Renegotiation) -> Fun = fun(Version) -> ssl_record:protocol_version(Version) @@ -70,22 +70,25 @@ client_hello(Host, Port, ConnectionStates, #ssl_options{versions = Versions, #client_hello{session_id = Id, client_version = Version, - cipher_suites = Ciphers, + cipher_suites = cipher_suites(Ciphers, Renegotiation), compression_methods = ssl_record:compressions(), - random = SecParams#security_parameters.client_random + random = SecParams#security_parameters.client_random, + renegotiation_info = + renegotiation_info(client, ConnectionStates, Renegotiation) }. %%-------------------------------------------------------------------- -%% Function: server_hello(Host, Port, SessionId, -%% Version, ConnectionStates) -> #server_hello{} +%% Function: server_hello(SessionId, Version, +%% ConnectionStates, Renegotiation) -> #server_hello{} %% SessionId %% Version -%% ConnectionStates +%% ConnectionStates +%% Renegotiation %% %% %% Description: Creates a server hello message. %%-------------------------------------------------------------------- -server_hello(SessionId, Version, ConnectionStates) -> +server_hello(SessionId, Version, ConnectionStates, Renegotiation) -> Pending = ssl_record:pending_connection_state(ConnectionStates, read), SecParams = Pending#connection_state.security_parameters, #server_hello{server_version = Version, @@ -93,7 +96,9 @@ server_hello(SessionId, Version, ConnectionStates) -> compression_method = SecParams#security_parameters.compression_algorithm, random = SecParams#security_parameters.server_random, - session_id = SessionId + session_id = SessionId, + renegotiation_info = + renegotiation_info(server, ConnectionStates, Renegotiation) }. %%-------------------------------------------------------------------- @@ -106,27 +111,41 @@ hello_request() -> #hello_request{}. %%-------------------------------------------------------------------- -%% Function: hello(Hello, Info) -> +%% Function: hello(Hello, Info, Renegotiation) -> %% {Version, Id, NewConnectionStates} | %% #alert{} %% %% Hello = #client_hello{} | #server_hello{} -%% Info = ConnectionStates | {Port, Session, ConnectionStates} +%% Info = ConnectionStates | {Port, #ssl_options{}, Session, +%% Cahce, CahceCb, ConnectionStates} %% ConnectionStates = #connection_states{} +%% Renegotiation = boolean() %% %% Description: Handles a recieved hello message %%-------------------------------------------------------------------- hello(#server_hello{cipher_suite = CipherSuite, server_version = Version, compression_method = Compression, random = Random, - session_id = SessionId}, ConnectionStates) -> - NewConnectionStates = - hello_pending_connection_states(client, CipherSuite, Random, - Compression, ConnectionStates), - {Version, SessionId, NewConnectionStates}; - -hello(#client_hello{client_version = ClientVersion, random = Random} = Hello, - {Port, #ssl_options{versions = Versions} = SslOpts, - Session0, Cache, CacheCb, ConnectionStates0}) -> + session_id = SessionId, renegotiation_info = Info}, + #ssl_options{secure_renegotiate = SecureRenegotation}, + ConnectionStates0, Renegotiation) -> + + case handle_renegotiation_info(client, Info, ConnectionStates0, + Renegotiation, SecureRenegotation, []) of + {ok, ConnectionStates1} -> + ConnectionStates = + hello_pending_connection_states(client, CipherSuite, Random, + Compression, ConnectionStates1), + {Version, SessionId, ConnectionStates}; + #alert{} = Alert -> + Alert + end; + +hello(#client_hello{client_version = ClientVersion, random = Random, + cipher_suites = CipherSuites, + renegotiation_info = Info} = Hello, + #ssl_options{versions = Versions, + secure_renegotiate = SecureRenegotation} = SslOpts, + {Port, Session0, Cache, CacheCb, ConnectionStates0}, Renegotiation) -> Version = select_version(ClientVersion, Versions), case ssl_record:is_acceptable_version(Version) of true -> @@ -138,13 +157,20 @@ hello(#client_hello{client_version = ClientVersion, random = Random} = Hello, no_suite -> ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY); _ -> - ConnectionStates = - hello_pending_connection_states(server, - CipherSuite, - Random, - Compression, - ConnectionStates0), - {Version, {Type, Session}, ConnectionStates} + case handle_renegotiation_info(server, Info, ConnectionStates0, + Renegotiation, SecureRenegotation, + CipherSuites) of + {ok, ConnectionStates1} -> + ConnectionStates = + hello_pending_connection_states(server, + CipherSuite, + Random, + Compression, + ConnectionStates1), + {Version, {Type, Session}, ConnectionStates}; + #alert{} = Alert -> + Alert + end end; false -> ?ALERT_REC(?FATAL, ?PROTOCOL_VERSION) @@ -525,7 +551,109 @@ select_session(Hello, Port, Session, Version, false -> {resumed, CacheCb:lookup(Cache, {Port, SessionId})} end. - + + +cipher_suites(Suites, false) -> + [?TLS_EMPTY_RENEGOTIATION_INFO_SCSV | Suites]; +cipher_suites(Suites, true) -> + Suites. + +renegotiation_info(client, _, false) -> + #renegotiation_info{renegotiated_connection = undefined}; +renegotiation_info(server, ConnectionStates, false) -> + CS = ssl_record:current_connection_state(ConnectionStates, read), + case CS#connection_state.secure_renegotiation of + true -> + #renegotiation_info{renegotiated_connection = ?byte(0)}; + false -> + #renegotiation_info{renegotiated_connection = undefined} + end; +renegotiation_info(client, ConnectionStates, true) -> + CS = ssl_record:current_connection_state(ConnectionStates, read), + case CS#connection_state.secure_renegotiation of + true -> + Data = CS#connection_state.client_verify_data, + #renegotiation_info{renegotiated_connection = Data}; + false -> + #renegotiation_info{renegotiated_connection = undefined} + end; + +renegotiation_info(server, ConnectionStates, true) -> + CS = ssl_record:current_connection_state(ConnectionStates, read), + case CS#connection_state.secure_renegotiation of + true -> + CData = CS#connection_state.client_verify_data, + SData =CS#connection_state.server_verify_data, + #renegotiation_info{renegotiated_connection = <<CData/binary, SData/binary>>}; + false -> + #renegotiation_info{renegotiated_connection = undefined} + end. + +handle_renegotiation_info(_, #renegotiation_info{renegotiated_connection = ?byte(0)}, + ConnectionStates, false, _, _) -> + {ok, ssl_record:set_renegotiation_flag(true, ConnectionStates)}; + +handle_renegotiation_info(server, undefined, ConnectionStates, _, _, CipherSuites) -> + case is_member(?TLS_EMPTY_RENEGOTIATION_INFO_SCSV, CipherSuites) of + true -> + {ok, ssl_record:set_renegotiation_flag(true, ConnectionStates)}; + false -> + {ok, ssl_record:set_renegotiation_flag(false, ConnectionStates)} + end; + +handle_renegotiation_info(_, undefined, ConnectionStates, false, _, _) -> + {ok, ssl_record:set_renegotiation_flag(false, ConnectionStates)}; + +handle_renegotiation_info(client, #renegotiation_info{renegotiated_connection = ClientServerVerify}, + ConnectionStates, true, _, _) -> + CS = ssl_record:current_connection_state(ConnectionStates, read), + CData = CS#connection_state.client_verify_data, + SData = CS#connection_state.server_verify_data, + case <<CData/binary, SData/binary>> == ClientServerVerify of + true -> + {ok, ConnectionStates}; + false -> + ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE) + end; +handle_renegotiation_info(server, #renegotiation_info{renegotiated_connection = ClientVerify}, + ConnectionStates, true, _, CipherSuites) -> + + case is_member(?TLS_EMPTY_RENEGOTIATION_INFO_SCSV, CipherSuites) of + true -> + ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE); + false -> + CS = ssl_record:current_connection_state(ConnectionStates, read), + Data = CS#connection_state.client_verify_data, + case Data == ClientVerify of + true -> + {ok, ConnectionStates}; + false -> + ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE) + end + end; + +handle_renegotiation_info(client, undefined, ConnectionStates, true, SecureRenegotation, _) -> + handle_renegotiation_info(ConnectionStates, SecureRenegotation); + +handle_renegotiation_info(server, undefined, ConnectionStates, true, SecureRenegotation, CipherSuites) -> + case is_member(?TLS_EMPTY_RENEGOTIATION_INFO_SCSV, CipherSuites) of + true -> + ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE); + false -> + handle_renegotiation_info(ConnectionStates, SecureRenegotation) + end. + +handle_renegotiation_info(ConnectionStates, SecureRenegotation) -> + CS = ssl_record:current_connection_state(ConnectionStates, read), + case {SecureRenegotation, CS#connection_state.secure_renegotiation} of + {_, true} -> + ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE); + {true, false} -> + ?ALERT_REC(?FATAL, ?NO_RENEGOTIATION); + {false, false} -> + {ok, ConnectionStates} + end. + %% Update pending connection states with parameters exchanged via %% hello messages %% NOTE : Role is the role of the receiver of the hello message @@ -636,31 +764,52 @@ dec_hs(?CLIENT_HELLO, <<?BYTE(Major), ?BYTE(Minor), random = ssl_ssl2:client_random(ChallengeData, CDLength), session_id = 0, cipher_suites = from_3bytes(CipherSuites), - compression_methods = [?NULL] + compression_methods = [?NULL], + renegotiation_info = undefined }; dec_hs(?CLIENT_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary, ?BYTE(SID_length), Session_ID:SID_length/binary, ?UINT16(Cs_length), CipherSuites:Cs_length/binary, ?BYTE(Cm_length), Comp_methods:Cm_length/binary, - _FutureCompatData/binary>>, + Extensions/binary>>, _, _) -> + + RenegotiationInfo = proplists:get_value(renegotiation_info, dec_hello_extensions(Extensions), + undefined), #client_hello{ client_version = {Major,Minor}, random = Random, session_id = Session_ID, cipher_suites = from_2bytes(CipherSuites), - compression_methods = Comp_methods + compression_methods = Comp_methods, + renegotiation_info = RenegotiationInfo }; + dec_hs(?SERVER_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary, ?BYTE(SID_length), Session_ID:SID_length/binary, - Cipher_suite:2/binary, ?BYTE(Comp_method)>>, _, _) -> + Cipher_suite:2/binary, ?BYTE(Comp_method)>>, _, _) -> #server_hello{ server_version = {Major,Minor}, random = Random, session_id = Session_ID, cipher_suite = Cipher_suite, - compression_method = Comp_method - }; + compression_method = Comp_method, + renegotiation_info = undefined}; + +dec_hs(?SERVER_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary, + ?BYTE(SID_length), Session_ID:SID_length/binary, + Cipher_suite:2/binary, ?BYTE(Comp_method), + ?UINT16(ExtLen), Extensions:ExtLen/binary>>, _, _) -> + + RenegotiationInfo = proplists:get_value(renegotiation_info, dec_hello_extensions(Extensions, []), + undefined), + #server_hello{ + server_version = {Major,Minor}, + random = Random, + session_id = Session_ID, + cipher_suite = Cipher_suite, + compression_method = Comp_method, + renegotiation_info = RenegotiationInfo}; dec_hs(?CERTIFICATE, <<?UINT24(ACLen), ASN1Certs:ACLen/binary>>, _, _) -> #certificate{asn1_certificates = certs_to_list(ASN1Certs)}; dec_hs(?SERVER_KEY_EXCHANGE, <<?UINT16(ModLen), Mod:ModLen/binary, @@ -707,6 +856,32 @@ dec_hs(?FINISHED, VerifyData, _, _) -> dec_hs(_, _, _, _) -> throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE)). +dec_hello_extensions(<<>>) -> + []; +dec_hello_extensions(<<?UINT16(ExtLen), Extensions:ExtLen/binary>>) -> + dec_hello_extensions(Extensions, []); +dec_hello_extensions(_) -> + []. + +dec_hello_extensions(<<>>, Acc) -> + Acc; +dec_hello_extensions(<<?UINT16(?RENEGOTIATION_EXT), ?UINT16(Len), Info:Len/binary, Rest/binary>>, Acc) -> + RenegotiateInfo = case Len of + 1 -> % Initial handshake + Info; % should be <<0>> will be matched in handle_renegotiation_info + _ -> + VerifyLen = Len - 1, + <<?BYTE(VerifyLen), VerifyInfo/binary>> = Info, + VerifyInfo + end, + dec_hello_extensions(Rest, [{renegotiation_info, + #renegotiation_info{renegotiated_connection = RenegotiateInfo}} | Acc]); +dec_hello_extensions(<<?UINT16(_), ?UINT16(Len), _Unknown:Len, Rest/binary>>, Acc) -> + dec_hello_extensions(Rest, Acc); +%% Need this clause? +dec_hello_extensions(_, Acc) -> + Acc. + encrypted_premaster_secret(Secret, RSAPublicKey) -> try PreMasterSecret = public_key:encrypt_public(Secret, RSAPublicKey, @@ -743,31 +918,36 @@ certs_from_list(ACList) -> enc_hs(#hello_request{}, _Version, _) -> {?HELLO_REQUEST, <<>>}; -enc_hs(#client_hello{ - client_version = {Major, Minor}, - random = Random, - session_id = SessionID, - cipher_suites = CipherSuites, - compression_methods = CompMethods}, _Version, _) -> +enc_hs(#client_hello{client_version = {Major, Minor}, + random = Random, + session_id = SessionID, + cipher_suites = CipherSuites, + compression_methods = CompMethods, + renegotiation_info = RenegotiationInfo}, _Version, _) -> SIDLength = byte_size(SessionID), BinCompMethods = list_to_binary(CompMethods), CmLength = byte_size(BinCompMethods), BinCipherSuites = list_to_binary(CipherSuites), CsLength = byte_size(BinCipherSuites), + Extensions = hello_extensions(RenegotiationInfo), + ExtensionsBin = enc_hello_extensions(Extensions), {?CLIENT_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary, ?BYTE(SIDLength), SessionID/binary, ?UINT16(CsLength), BinCipherSuites/binary, - ?BYTE(CmLength), BinCompMethods/binary>>}; -enc_hs(#server_hello{ - server_version = {Major, Minor}, - random = Random, - session_id = Session_ID, - cipher_suite = Cipher_suite, - compression_method = Comp_method}, _Version, _) -> + ?BYTE(CmLength), BinCompMethods/binary, ExtensionsBin/binary>>}; + +enc_hs(#server_hello{server_version = {Major, Minor}, + random = Random, + session_id = Session_ID, + cipher_suite = Cipher_suite, + compression_method = Comp_method, + renegotiation_info = RenegotiationInfo}, _Version, _) -> SID_length = byte_size(Session_ID), + Extensions = hello_extensions(RenegotiationInfo), + ExtensionsBin = enc_hello_extensions(Extensions), {?SERVER_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary, ?BYTE(SID_length), Session_ID/binary, - Cipher_suite/binary, ?BYTE(Comp_method)>>}; + Cipher_suite/binary, ?BYTE(Comp_method), ExtensionsBin/binary>>}; enc_hs(#certificate{asn1_certificates = ASN1CertList}, _Version, _) -> ASN1Certs = certs_from_list(ASN1CertList), ACLen = erlang:iolist_size(ASN1Certs), @@ -826,6 +1006,29 @@ enc_bin_sig(BinSig) -> Size = byte_size(BinSig), <<?UINT16(Size), BinSig/binary>>. +%% Renegotiation info, only current extension +hello_extensions(#renegotiation_info{renegotiated_connection = undefined}) -> + []; +hello_extensions(#renegotiation_info{} = Info) -> + [Info]. + +enc_hello_extensions(Extensions) -> + enc_hello_extensions(Extensions, <<>>). +enc_hello_extensions([], <<>>) -> + <<>>; +enc_hello_extensions([], Acc) -> + Size = byte_size(Acc), + <<?UINT16(Size), Acc/binary>>; + +enc_hello_extensions([#renegotiation_info{renegotiated_connection = ?byte(0) = Info} | Rest], Acc) -> + Len = byte_size(Info), + enc_hello_extensions(Rest, <<?UINT16(?RENEGOTIATION_EXT), ?UINT16(Len), Info/binary, Acc/binary>>); + +enc_hello_extensions([#renegotiation_info{renegotiated_connection = Info} | Rest], Acc) -> + InfoLen = byte_size(Info), + Len = InfoLen +1, + enc_hello_extensions(Rest, <<?UINT16(?RENEGOTIATION_EXT), ?UINT16(Len), ?BYTE(InfoLen), Info/binary, Acc/binary>>). + init_hashes() -> T = {crypto:md5_init(), crypto:sha_init()}, {T, T}. diff --git a/lib/ssl/src/ssl_handshake.hrl b/lib/ssl/src/ssl_handshake.hrl index 889d39f2af..74fba3786c 100644 --- a/lib/ssl/src/ssl_handshake.hrl +++ b/lib/ssl/src/ssl_handshake.hrl @@ -81,7 +81,8 @@ random, session_id, % opaque SessionID<0..32> cipher_suites, % cipher_suites<2..2^16-1> - compression_methods % compression_methods<1..2^8-1> + compression_methods, % compression_methods<1..2^8-1>, + renegotiation_info }). -record(server_hello, { @@ -89,7 +90,8 @@ random, session_id, % opaque SessionID<0..32> cipher_suite, % cipher_suites - compression_method % compression_method + compression_method, % compression_method + renegotiation_info }). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -195,6 +197,15 @@ verify_data %opaque verify_data[12] }). +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Renegotiation info RFC 5746 section 3.2 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-define(RENEGOTIATION_EXT, 16#ff01). + +-record(renegotiation_info,{ + renegotiated_connection + }). + -endif. % -ifdef(ssl_handshake). diff --git a/lib/ssl/src/ssl_internal.hrl b/lib/ssl/src/ssl_internal.hrl index 8d19abfe1e..fdc0c33750 100644 --- a/lib/ssl/src/ssl_internal.hrl +++ b/lib/ssl/src/ssl_internal.hrl @@ -75,6 +75,7 @@ %% will be reused if possible. reuse_sessions, % boolean() renegotiate_at, + secure_renegotiate, debug % }). diff --git a/lib/ssl/src/ssl_record.erl b/lib/ssl/src/ssl_record.erl index da48f049f6..f9f915f13d 100644 --- a/lib/ssl/src/ssl_record.erl +++ b/lib/ssl/src/ssl_record.erl @@ -38,7 +38,10 @@ set_mac_secret/4, set_master_secret/2, activate_pending_connection_state/2, - set_pending_cipher_state/4]). + set_pending_cipher_state/4, + set_renegotiation_flag/2, + set_client_verify_data/3, + set_server_verify_data/3]). %% Handling of incoming data -export([get_tls_records/2]). @@ -175,6 +178,98 @@ set_master_secret(MasterSecret, master_secret = MasterSecret}}, States#connection_states{pending_read = Read1, pending_write = Write1}. +%%-------------------------------------------------------------------- +%% Function: set_renegotiation_flag(Flag, States) -> +%% #connection_states{} +%% Flag = boolean() +%% States = #connection_states{} +%% +%% Set master_secret in pending connection states +%%-------------------------------------------------------------------- +set_renegotiation_flag(Flag, #connection_states{ + current_read = CurrentRead0, + current_write = CurrentWrite0, + pending_read = PendingRead0, + pending_write = PendingWrite0} + = ConnectionStates) -> + CurrentRead = CurrentRead0#connection_state{secure_renegotiation = Flag}, + CurrentWrite = CurrentWrite0#connection_state{secure_renegotiation = Flag}, + PendingRead = PendingRead0#connection_state{secure_renegotiation = Flag}, + PendingWrite = PendingWrite0#connection_state{secure_renegotiation = Flag}, + ConnectionStates#connection_states{current_read = CurrentRead, + current_write = CurrentWrite, + pending_read = PendingRead, + pending_write = PendingWrite}. + +%%-------------------------------------------------------------------- +%% Function: set_client_verify_data(State, Data, States) -> +%% #connection_states{} +%% State = atom() +%% Data = binary() +%% States = #connection_states{} +%% +%% Set verify data in connection states. +%%-------------------------------------------------------------------- +set_client_verify_data(current_read, Data, + #connection_states{current_read = CurrentRead0, + pending_write = PendingWrite0} + = ConnectionStates) -> + CurrentRead = CurrentRead0#connection_state{client_verify_data = Data}, + PendingWrite = PendingWrite0#connection_state{client_verify_data = Data}, + ConnectionStates#connection_states{current_read = CurrentRead, + pending_write = PendingWrite}; +set_client_verify_data(current_write, Data, + #connection_states{pending_read = PendingRead0, + current_write = CurrentWrite0} + = ConnectionStates) -> + PendingRead = PendingRead0#connection_state{client_verify_data = Data}, + CurrentWrite = CurrentWrite0#connection_state{client_verify_data = Data}, + ConnectionStates#connection_states{pending_read = PendingRead, + current_write = CurrentWrite}; +set_client_verify_data(current_both, Data, + #connection_states{current_read = CurrentRead0, + current_write = CurrentWrite0} + = ConnectionStates) -> + CurrentRead = CurrentRead0#connection_state{client_verify_data = Data}, + CurrentWrite = CurrentWrite0#connection_state{client_verify_data = Data}, + ConnectionStates#connection_states{current_read = CurrentRead, + current_write = CurrentWrite}. + +%%-------------------------------------------------------------------- +%% Function: set_server_verify_data(State, Data, States) -> +%% #connection_states{} +%% State = atom() +%% Data = binary() +%% States = #connection_states{} +%% +%% Set verify data in pending connection states. +%%-------------------------------------------------------------------- +set_server_verify_data(current_write, Data, + #connection_states{pending_read = PendingRead0, + current_write = CurrentWrite0} + = ConnectionStates) -> + PendingRead = PendingRead0#connection_state{server_verify_data = Data}, + CurrentWrite = CurrentWrite0#connection_state{server_verify_data = Data}, + ConnectionStates#connection_states{pending_read = PendingRead, + current_write = CurrentWrite}; + +set_server_verify_data(current_read, Data, + #connection_states{current_read = CurrentRead0, + pending_write = PendingWrite0} + = ConnectionStates) -> + CurrentRead = CurrentRead0#connection_state{server_verify_data = Data}, + PendingWrite = PendingWrite0#connection_state{server_verify_data = Data}, + ConnectionStates#connection_states{current_read = CurrentRead, + pending_write = PendingWrite}; + +set_server_verify_data(current_both, Data, + #connection_states{current_read = CurrentRead0, + current_write = CurrentWrite0} + = ConnectionStates) -> + CurrentRead = CurrentRead0#connection_state{server_verify_data = Data}, + CurrentWrite = CurrentWrite0#connection_state{server_verify_data = Data}, + ConnectionStates#connection_states{current_read = CurrentRead, + current_write = CurrentWrite}. %%-------------------------------------------------------------------- %% Function: activate_pending_connection_state(States, Type) -> @@ -191,7 +286,9 @@ activate_pending_connection_state(States = NewCurrent = Pending#connection_state{sequence_number = 0}, SecParams = Pending#connection_state.security_parameters, ConnectionEnd = SecParams#security_parameters.connection_end, - NewPending = empty_connection_state(ConnectionEnd), + EmptyPending = empty_connection_state(ConnectionEnd), + SecureRenegotation = NewCurrent#connection_state.secure_renegotiation, + NewPending = EmptyPending#connection_state{secure_renegotiation = SecureRenegotation}, States#connection_states{current_read = NewCurrent, pending_read = NewPending }; @@ -202,7 +299,9 @@ activate_pending_connection_state(States = NewCurrent = Pending#connection_state{sequence_number = 0}, SecParams = Pending#connection_state.security_parameters, ConnectionEnd = SecParams#security_parameters.connection_end, - NewPending = empty_connection_state(ConnectionEnd), + EmptyPending = empty_connection_state(ConnectionEnd), + SecureRenegotation = NewCurrent#connection_state.secure_renegotiation, + NewPending = EmptyPending#connection_state{secure_renegotiation = SecureRenegotation}, States#connection_states{current_write = NewCurrent, pending_write = NewPending }. diff --git a/lib/ssl/src/ssl_record.hrl b/lib/ssl/src/ssl_record.hrl index 362b7039d4..5fb0070b91 100644 --- a/lib/ssl/src/ssl_record.hrl +++ b/lib/ssl/src/ssl_record.hrl @@ -60,7 +60,11 @@ compression_state, cipher_state, mac_secret, - sequence_number + sequence_number, + %% RFC 5746 + secure_renegotiation, + client_verify_data, + server_verify_data }). -define(MAX_SEQENCE_NUMBER, 18446744073709552000). %% math:pow(2, 64) - 1 = 1.8446744073709552e19 |