diff options
Diffstat (limited to 'lib/ssl')
28 files changed, 1010 insertions, 159 deletions
diff --git a/lib/ssl/doc/src/notes.xml b/lib/ssl/doc/src/notes.xml index 10c2bd933f..87bf25452f 100644 --- a/lib/ssl/doc/src/notes.xml +++ b/lib/ssl/doc/src/notes.xml @@ -391,7 +391,7 @@ <p> TLS sessions must be registered with SNI if provided, so that sessions where client hostname verification would - fail can not connect reusing a session created when the + fail cannot connect reusing a session created when the server name verification succeeded.</p> <p> Own Id: OTP-14632</p> @@ -569,7 +569,7 @@ public_key:pkix_verify_hostname/2 to verify the hostname of the connection with the server certificates specified hostname during certificate path validation. The user may - explicitly disables it. Also if the hostname can not be + explicitly disables it. Also if the hostname cannot be derived from the first argument to connect or is not supplied by the server name indication option, the check will not be performed.</p> diff --git a/lib/ssl/doc/src/ssl.xml b/lib/ssl/doc/src/ssl.xml index 3029977745..6efa022a79 100644 --- a/lib/ssl/doc/src/ssl.xml +++ b/lib/ssl/doc/src/ssl.xml @@ -88,6 +88,7 @@ <p><c>| {client_preferred_next_protocols, {client | server, [binary()]} | {client | server, [binary()], binary()}}</c></p> <p><c>| {log_alert, boolean()}</c></p> + <p><c>| {log_level, atom()}</c></p> <p><c>| {server_name_indication, hostname() | disable}</c></p> <p><c>| {customize_hostname_check, list()}</c></p> <p><c>| {sni_hosts, [{hostname(), [ssl_option()]}]}</c></p> @@ -409,7 +410,7 @@ marker="public_key:public_key#pkix_path_validation-3">public_key:pkix_path_valid <item>check is only performed on the peer certificate.</item> <tag><c>best_effort</c></tag> - <item>if certificate revocation status can not be determined + <item>if certificate revocation status cannot be determined it will be accepted as valid.</item> </taglist> @@ -796,7 +797,17 @@ fun(srp, Username :: string(), UserState :: term()) -> the client.</p></item> <tag><c>{log_alert, boolean()}</c></tag> - <item><p>If set to <c>false</c>, error reports are not displayed.</p></item> + <item><p>If set to <c>false</c>, error reports are not displayed.</p> + <p>Deprecated in OTP 22, use <seealso marker="#log_level">log_level</seealso> instead.</p> + </item> + + <tag><marker id="log_level"/><c>{log_level, atom()}</c></tag> + <item><p>Specifies the log level for TLS/DTLS. It can take the following + values (ordered by increasing verbosity level): <c>emergency, alert, critical, error, + warning, notice, info, debug.</c></p> + <p>At verbosity level <c>notice</c> and above error reports are + displayed in TLS. The level <c>debug</c> triggers verbose logging of TLS protocol + messages and logging of ignored alerts in DTLS.</p></item> <tag><c>{honor_cipher_order, boolean()}</c></tag> <item><p>If set to <c>true</c>, use the server preference for cipher @@ -1400,6 +1411,17 @@ fun(srp, Username :: string(), UserState :: term()) -> </func> <func> + <name>set_log_level(Level) -> ok | {error, Reason}</name> + <fsummary>Sets log level for the SSL application.</fsummary> + <type> + <v>Level = atom()</v> + </type> + <desc> + <p>Sets log level for the SSL application.</p> + </desc> + </func> + + <func> <name>shutdown(SslSocket, How) -> ok | {error, Reason}</name> <fsummary>Immediately closes a socket.</fsummary> <type> diff --git a/lib/ssl/doc/src/ssl_distribution.xml b/lib/ssl/doc/src/ssl_distribution.xml index e14f3f90dc..1774bd8f77 100644 --- a/lib/ssl/doc/src/ssl_distribution.xml +++ b/lib/ssl/doc/src/ssl_distribution.xml @@ -191,7 +191,7 @@ Eshell V5.0 (abort with ^G) Any available SSL/TLS option can be specified in an options file, but note that options that take a <c>fun()</c> has to use the syntax <c>fun Mod:Func/Arity</c> since a function - body can not be compiled when consulting a file. + body cannot be compiled when consulting a file. </p> <p> Do not tamper with the socket options diff --git a/lib/ssl/src/Makefile b/lib/ssl/src/Makefile index ebcb511653..1db18d4e5a 100644 --- a/lib/ssl/src/Makefile +++ b/lib/ssl/src/Makefile @@ -86,7 +86,8 @@ MODULES= \ ssl_record \ ssl_v3 \ tls_v1 \ - dtls_v1 + dtls_v1 \ + ssl_logger INTERNAL_HRL_FILES = \ ssl_alert.hrl ssl_cipher.hrl \ @@ -118,7 +119,7 @@ EXTRA_ERLC_FLAGS = +warn_unused_vars ERL_COMPILE_FLAGS += -I$(ERL_TOP)/lib/kernel/src \ -pz $(EBIN) \ -pz $(ERL_TOP)/lib/public_key/ebin \ - $(EXTRA_ERLC_FLAGS) -DVSN=\"$(VSN)\" + $(EXTRA_ERLC_FLAGS) # ---------------------------------------------------- diff --git a/lib/ssl/src/dtls_connection.erl b/lib/ssl/src/dtls_connection.erl index bf3ff3a9a7..c0e81d6a28 100644 --- a/lib/ssl/src/dtls_connection.erl +++ b/lib/ssl/src/dtls_connection.erl @@ -32,6 +32,7 @@ -include("ssl_internal.hrl"). -include("ssl_srp.hrl"). -include_lib("public_key/include/public_key.hrl"). +-include_lib("kernel/include/logger.hrl"). %% Internal application API @@ -928,7 +929,7 @@ handle_own_alert(Alert, Version, StateName, #state{data_tag = udp, ssl_options = Options} = State0) -> case ignore_alert(Alert, State0) of {true, State} -> - log_ignore_alert(Options#ssl_options.log_alert, StateName, Alert, Role), + log_ignore_alert(Options#ssl_options.log_level, StateName, Alert, Role), {next_state, StateName, State}; {false, State} -> ssl_connection:handle_own_alert(Alert, Version, StateName, State) @@ -1125,9 +1126,9 @@ is_ignore_alert(#alert{description = ?ILLEGAL_PARAMETER}) -> is_ignore_alert(_) -> false. -log_ignore_alert(true, StateName, Alert, Role) -> +log_ignore_alert(debug, StateName, Alert, Role) -> Txt = ssl_alert:alert_txt(Alert), - error_logger:format("DTLS over UDP ~p: In state ~p ignored to send ALERT ~s as DoS-attack mitigation \n", - [Role, StateName, Txt]); -log_ignore_alert(false, _, _,_) -> + ?LOG_ERROR("DTLS over UDP ~p: In state ~p ignored to send ALERT ~s as DoS-attack mitigation \n", + [Role, StateName, Txt]); +log_ignore_alert(_, _, _, _) -> ok. diff --git a/lib/ssl/src/dtls_packet_demux.erl b/lib/ssl/src/dtls_packet_demux.erl index 1497c77cf3..e03a4e9cb9 100644 --- a/lib/ssl/src/dtls_packet_demux.erl +++ b/lib/ssl/src/dtls_packet_demux.erl @@ -24,6 +24,7 @@ -behaviour(gen_server). -include("ssl_internal.hrl"). +-include_lib("kernel/include/logger.hrl"). %% API -export([start_link/5, active_once/3, accept/2, sockname/1, close/1, @@ -146,11 +147,11 @@ handle_info({Transport, Socket, IP, InPortNo, _} = Msg, #state{listener = Socket %% appears to make things work as expected! handle_info({Error, Socket, econnreset = Error}, #state{listener = Socket, transport = {_,_,_, udp_error}} = State) -> Report = io_lib:format("Ignore SSL UDP Listener: Socket error: ~p ~n", [Error]), - error_logger:info_report(Report), + ?LOG_NOTICE(Report), {noreply, State}; handle_info({Error, Socket, Error}, #state{listener = Socket, transport = {_,_,_, Error}} = State) -> Report = io_lib:format("SSL Packet muliplxer shutdown: Socket error: ~p ~n", [Error]), - error_logger:info_report(Report), + ?LOG_NOTICE(Report), {noreply, State#state{close=true}}; handle_info({'DOWN', _, process, Pid, _}, #state{clients = Clients, diff --git a/lib/ssl/src/inet_tls_dist.erl b/lib/ssl/src/inet_tls_dist.erl index aa3d7e3f72..1194f4fc72 100644 --- a/lib/ssl/src/inet_tls_dist.erl +++ b/lib/ssl/src/inet_tls_dist.erl @@ -41,6 +41,7 @@ -include_lib("public_key/include/public_key.hrl"). -include("ssl_api.hrl"). +-include_lib("kernel/include/logger.hrl"). %% ------------------------------------------------------------------------- @@ -226,7 +227,7 @@ accept_loop(Driver, Listen, Kernel) -> true -> accept_loop(Driver, Listen, Kernel, Socket); {false,IP} -> - error_logger:error_msg( + ?LOG_ERROR( "** Connection attempt from " "disallowed IP ~w ** ~n", [IP]), ?shutdown2(no_node, trace({disallowed, IP})) @@ -261,7 +262,7 @@ accept_loop(Driver, Listen, Kernel, Socket) -> {error, {options, _}} = Error -> %% Bad options: that's probably our fault. %% Let's log that. - error_logger:error_msg( + ?LOG_ERROR( "Cannot accept TLS distribution connection: ~s~n", [ssl:format_error(Error)]), gen_tcp:close(Socket), @@ -437,7 +438,7 @@ allowed_nodes(SslSocket, Allowed) -> PeerCert, allowed_hosts(Allowed), PeerIP) of [] -> - error_logger:error_msg( + ?LOG_ERROR( "** Connection attempt from " "disallowed node(s) ~p ** ~n", [PeerIP]), ?shutdown2( @@ -691,12 +692,12 @@ split_node(Driver, Node, LongOrShortNames) -> {node, Name, Host} -> check_node(Driver, Node, Name, Host, LongOrShortNames); {host, _} -> - error_logger:error_msg( + ?LOG_ERROR( "** Nodename ~p illegal, no '@' character **~n", [Node]), ?shutdown2(Node, trace({illegal_node_n@me, Node})); _ -> - error_logger:error_msg( + ?LOG_ERROR( "** Nodename ~p illegal **~n", [Node]), ?shutdown2(Node, trace({illegal_node_name, Node})) end. @@ -708,7 +709,7 @@ check_node(Driver, Node, Name, Host, LongOrShortNames) -> {ok, _} -> {Name, Host}; _ -> - error_logger:error_msg( + ?LOG_ERROR( "** System running to use " "fully qualified hostnames **~n" "** Hostname ~s is illegal **~n", @@ -716,7 +717,7 @@ check_node(Driver, Node, Name, Host, LongOrShortNames) -> ?shutdown2(Node, trace({not_longnames, Host})) end; [_,_|_] when LongOrShortNames =:= shortnames -> - error_logger:error_msg( + ?LOG_ERROR( "** System NOT running to use " "fully qualified hostnames **~n" "** Hostname ~s is illegal **~n", @@ -846,13 +847,13 @@ monitor_pid(Pid) -> %% MRef = erlang:monitor(process, Pid), %% receive %% {'DOWN', MRef, _, _, normal} -> - %% error_logger:error_report( - %% [dist_proc_died, + %% ?LOG_ERROR( + %% [{slogan, dist_proc_died}, %% {reason, normal}, %% {pid, Pid}]); %% {'DOWN', MRef, _, _, Reason} -> - %% error_logger:info_report( - %% [dist_proc_died, + %% ?LOG_NOTICE( + %% [{slogan, dist_proc_died}, %% {reason, Reason}, %% {pid, Pid}]) %% end diff --git a/lib/ssl/src/ssl.app.src b/lib/ssl/src/ssl.app.src index da281829cb..9679ea4687 100644 --- a/lib/ssl/src/ssl.app.src +++ b/lib/ssl/src/ssl.app.src @@ -51,6 +51,8 @@ ssl_crl_cache, ssl_crl_cache_api, ssl_crl_hash_dir, + %% Logging + ssl_logger, %% App structure ssl_app, ssl_sup, diff --git a/lib/ssl/src/ssl.erl b/lib/ssl/src/ssl.erl index 0f13b737ab..09953908ce 100644 --- a/lib/ssl/src/ssl.erl +++ b/lib/ssl/src/ssl.erl @@ -55,7 +55,8 @@ format_error/1, renegotiate/1, prf/5, negotiated_protocol/1, connection_information/1, connection_information/2]). %% Misc --export([handle_options/2, tls_version/1, new_ssl_options/3, suite_to_str/1]). +-export([handle_options/2, tls_version/1, new_ssl_options/3, suite_to_str/1, + set_log_level/1]). -deprecated({ssl_accept, 1, eventually}). -deprecated({ssl_accept, 2, eventually}). @@ -87,6 +88,7 @@ stop() -> application:stop(ssl). %%-------------------------------------------------------------------- + -spec connect(host() | port(), [connect_option()]) -> {ok, #sslsocket{}} | {error, reason()}. -spec connect(host() | port(), [connect_option()] | inet:port_number(), @@ -209,6 +211,8 @@ ssl_accept(Socket, SslOptions, Timeout) -> %% Description: Performs accept on an ssl listen socket. e.i. performs %% ssl handshake. %%-------------------------------------------------------------------- + +%% Performs the SSL/TLS/DTLS server-side handshake. handshake(ListenSocket) -> handshake(ListenSocket, infinity). @@ -216,6 +220,12 @@ handshake(#sslsocket{} = Socket, Timeout) when (is_integer(Timeout) andalso Tim (Timeout == infinity) -> ssl_connection:handshake(Socket, Timeout); +%% If Socket is a ordinary socket(): upgrades a gen_tcp, or equivalent, socket to +%% an SSL socket, that is, performs the SSL/TLS server-side handshake and returns +%% the SSL socket. +%% +%% If Socket is an sslsocket(): provides extra SSL/TLS/DTLS options to those +%% specified in ssl:listen/2 and then performs the SSL/TLS/DTLS handshake. handshake(ListenSocket, SslOptions) when is_port(ListenSocket) -> handshake(ListenSocket, SslOptions, infinity). @@ -792,6 +802,32 @@ suite_to_str(Cipher) -> ssl_cipher:suite_to_str(Cipher). +%%-------------------------------------------------------------------- +-spec set_log_level(atom()) -> ok | {error, term()}. +%% +%% Description: Set log level for the SSL application +%%-------------------------------------------------------------------- +set_log_level(Level) -> + case application:get_all_key(ssl) of + {ok, PropList} -> + Modules = proplists:get_value(modules, PropList), + set_module_level(Modules, Level); + undefined -> + {error, ssl_not_started} + end. + +set_module_level(Modules, Level) -> + Fun = fun (Module) -> + ok = logger:set_module_level(Module, Level) + end, + try lists:map(Fun, Modules) of + _ -> + ok + catch + error:{badmatch, Error} -> + Error + end. + %%%-------------------------------------------------------------- %%% Internal functions %%%-------------------------------------------------------------------- @@ -851,9 +887,10 @@ handle_options(Opts0, #ssl_options{protocol = Protocol, cacerts = CaCerts0, [] -> new_ssl_options(SslOpts1, NewVerifyOpts, RecordCB); Value -> - Versions = [RecordCB:protocol_version(Vsn) || Vsn <- Value], + Versions0 = [RecordCB:protocol_version(Vsn) || Vsn <- Value], + Versions1 = lists:sort(fun RecordCB:is_higher/2, Versions0), new_ssl_options(proplists:delete(versions, SslOpts1), - NewVerifyOpts#ssl_options{versions = Versions}, record_cb(Protocol)) + NewVerifyOpts#ssl_options{versions = Versions1}, record_cb(Protocol)) end; %% Handle all options in listen and connect @@ -876,7 +913,8 @@ handle_options(Opts0, Role, Host) -> [] -> RecordCb:supported_protocol_versions(); Vsns -> - [RecordCb:protocol_version(Vsn) || Vsn <- Vsns] + Versions0 = [RecordCb:protocol_version(Vsn) || Vsn <- Vsns], + lists:sort(fun RecordCb:is_higher/2, Versions0) end, Protocol = handle_option(protocol, Opts, tls), @@ -888,7 +926,7 @@ handle_options(Opts0, Role, Host) -> ok end, - SSLOptions = #ssl_options{ + SSLOptions0 = #ssl_options{ versions = Versions, verify = validate_option(verify, Verify), verify_fun = VerifyFun, @@ -935,7 +973,6 @@ handle_options(Opts0, Role, Host) -> next_protocol_selector = make_next_protocol_selector( handle_option(client_preferred_next_protocols, Opts, undefined)), - log_alert = handle_option(log_alert, Opts, true), server_name_indication = handle_option(server_name_indication, Opts, default_option_role(client, server_name_indication_default(Host), Role)), @@ -961,6 +998,10 @@ handle_options(Opts0, Role, Host) -> handshake = handle_option(handshake, Opts, full), customize_hostname_check = handle_option(customize_hostname_check, Opts, []) }, + LogLevel = handle_option(log_alert, Opts, true), + SSLOptions = SSLOptions0#ssl_options{ + log_level = handle_option(log_level, Opts, LogLevel) + }, CbInfo = proplists:get_value(cb_info, Opts, default_cb_info(Protocol)), SslOptions = [protocol, versions, verify, verify_fun, partial_chain, @@ -972,7 +1013,7 @@ handle_options(Opts0, Role, Host) -> cb_info, renegotiate_at, secure_renegotiate, hibernate_after, erl_dist, alpn_advertised_protocols, sni_hosts, sni_fun, alpn_preferred_protocols, next_protocols_advertised, - client_preferred_next_protocols, log_alert, + client_preferred_next_protocols, log_alert, log_level, server_name_indication, honor_cipher_order, padding_check, crl_check, crl_cache, fallback, signature_algs, eccs, honor_ecc_order, beast_mitigation, max_handshake_size, handshake, customize_hostname_check], @@ -1151,7 +1192,20 @@ validate_option(client_preferred_next_protocols, {Precedence, PreferredProtocols Value; validate_option(client_preferred_next_protocols, undefined) -> undefined; -validate_option(log_alert, Value) when is_boolean(Value) -> +validate_option(log_alert, true) -> + notice; +validate_option(log_alert, false) -> + warning; +validate_option(log_level, Value) when + is_atom(Value) andalso + (Value =:= emergency orelse + Value =:= alert orelse + Value =:= critical orelse + Value =:= error orelse + Value =:= warning orelse + Value =:= notice orelse + Value =:= info orelse + Value =:= debug) -> Value; validate_option(next_protocols_advertised, Value) when is_list(Value) -> validate_binary_list(next_protocols_advertised, Value), @@ -1259,7 +1313,8 @@ validate_binary_list(Opt, List) -> end, List). validate_versions([], Versions) -> Versions; -validate_versions([Version | Rest], Versions) when Version == 'tlsv1.2'; +validate_versions([Version | Rest], Versions) when Version == 'tlsv1.3'; + Version == 'tlsv1.2'; Version == 'tlsv1.1'; Version == tlsv1; Version == sslv3 -> @@ -1272,10 +1327,11 @@ validate_versions([Ver| _], Versions) -> tls_validate_versions([], Versions) -> Versions; -tls_validate_versions([Version | Rest], Versions) when Version == 'tlsv1.2'; - Version == 'tlsv1.1'; - Version == tlsv1; - Version == sslv3 -> +tls_validate_versions([Version | Rest], Versions) when Version == 'tlsv1.3'; + Version == 'tlsv1.2'; + Version == 'tlsv1.1'; + Version == tlsv1; + Version == sslv3 -> tls_validate_versions(Rest, Versions); tls_validate_versions([Ver| _], Versions) -> throw({error, {options, {Ver, {versions, Versions}}}}). @@ -1526,8 +1582,10 @@ new_ssl_options([{next_protocols_advertised, Value} | Rest], #ssl_options{} = Op new_ssl_options([{client_preferred_next_protocols, Value} | Rest], #ssl_options{} = Opts, RecordCB) -> new_ssl_options(Rest, Opts#ssl_options{next_protocol_selector = make_next_protocol_selector(validate_option(client_preferred_next_protocols, Value))}, RecordCB); -new_ssl_options([{log_alert, Value} | Rest], #ssl_options{} = Opts, RecordCB) -> - new_ssl_options(Rest, Opts#ssl_options{log_alert = validate_option(log_alert, Value)}, RecordCB); +new_ssl_options([{log_alert, Value} | Rest], #ssl_options{} = Opts, RecordCB) -> + new_ssl_options(Rest, Opts#ssl_options{log_level = validate_option(log_alert, Value)}, RecordCB); +new_ssl_options([{log_level, Value} | Rest], #ssl_options{} = Opts, RecordCB) -> + new_ssl_options(Rest, Opts#ssl_options{log_level = validate_option(log_level, Value)}, RecordCB); new_ssl_options([{server_name_indication, Value} | Rest], #ssl_options{} = Opts, RecordCB) -> new_ssl_options(Rest, Opts#ssl_options{server_name_indication = validate_option(server_name_indication, Value)}, RecordCB); new_ssl_options([{honor_cipher_order, Value} | Rest], #ssl_options{} = Opts, RecordCB) -> diff --git a/lib/ssl/src/ssl_app.erl b/lib/ssl/src/ssl_app.erl index 62e8765d4a..2a5047c75c 100644 --- a/lib/ssl/src/ssl_app.erl +++ b/lib/ssl/src/ssl_app.erl @@ -29,9 +29,26 @@ -export([start/2, stop/1]). start(_Type, _StartArgs) -> + start_logger(), ssl_sup:start_link(). stop(_State) -> + stop_logger(), ok. +%% +%% Description: Start SSL logger +start_logger() -> + Config = #{level => debug, + filter_default => stop, + formatter => {ssl_logger, #{}}}, + Filter = {fun logger_filters:domain/2,{log,sub,[otp,ssl]}}, + logger:add_handler(ssl_handler, logger_std_h, Config), + logger:add_handler_filter(ssl_handler, filter_non_ssl, Filter). + +%% +%% Description: Stop SSL logger +stop_logger() -> + logger:remove_handler(ssl_handler). + diff --git a/lib/ssl/src/ssl_certificate.erl b/lib/ssl/src/ssl_certificate.erl index c15e8a2138..017d18ee2c 100644 --- a/lib/ssl/src/ssl_certificate.erl +++ b/lib/ssl/src/ssl_certificate.erl @@ -69,7 +69,7 @@ trusted_cert_and_path(CertChain, CertDbHandle, CertDbRef, PartialChainHandler) - case SignedAndIssuerID of {error, issuer_not_found} -> - %% The root CA was not sent and can not be found. + %% The root CA was not sent and cannot be found. handle_incomplete_chain(Path, PartialChainHandler); {self, _} when length(Path) == 1 -> {selfsigned_peer, Path}; diff --git a/lib/ssl/src/ssl_cipher.erl b/lib/ssl/src/ssl_cipher.erl index 1aeb415bd9..07ec823829 100644 --- a/lib/ssl/src/ssl_cipher.erl +++ b/lib/ssl/src/ssl_cipher.erl @@ -187,7 +187,7 @@ block_cipher(Fun, BlockSz, #cipher_state{key=Key, iv=IV} = CS0, block_cipher(Fun, BlockSz, #cipher_state{key=Key, iv=IV} = CS0, Mac, Fragment, {3, N}) - when N == 2; N == 3 -> + when N == 2; N == 3; N == 4 -> NextIV = random_iv(IV), L0 = build_cipher_block(BlockSz, Mac, Fragment), L = [NextIV|L0], @@ -320,6 +320,8 @@ suites({3, Minor}) -> suites({_, Minor}) -> dtls_v1:suites(Minor). +all_suites({3, 4}) -> + all_suites({3, 3}); all_suites({3, _} = Version) -> suites(Version) ++ chacha_suites(Version) @@ -478,11 +480,12 @@ rc4_suites({3, Minor}) -> rc4_suites(0) -> [?TLS_RSA_WITH_RC4_128_SHA, ?TLS_RSA_WITH_RC4_128_MD5]; -rc4_suites(N) when N =< 3 -> +rc4_suites(N) when N =< 4 -> [?TLS_ECDHE_ECDSA_WITH_RC4_128_SHA, ?TLS_ECDHE_RSA_WITH_RC4_128_SHA, ?TLS_ECDH_ECDSA_WITH_RC4_128_SHA, ?TLS_ECDH_RSA_WITH_RC4_128_SHA]. + %%-------------------------------------------------------------------- -spec des_suites(Version::ssl_record:ssl_version()) -> [cipher_suite()]. %% @@ -517,13 +520,14 @@ rsa_suites(0) -> ?TLS_RSA_WITH_AES_128_CBC_SHA, ?TLS_RSA_WITH_3DES_EDE_CBC_SHA ]; -rsa_suites(N) when N =< 3 -> +rsa_suites(N) when N =< 4 -> [ ?TLS_RSA_WITH_AES_256_GCM_SHA384, ?TLS_RSA_WITH_AES_256_CBC_SHA256, ?TLS_RSA_WITH_AES_128_GCM_SHA256, ?TLS_RSA_WITH_AES_128_CBC_SHA256 ]. + %%-------------------------------------------------------------------- -spec suite_definition(cipher_suite()) -> erl_cipher_suite(). %% @@ -2430,7 +2434,7 @@ mac_hash({_,_}, ?NULL, _MacSecret, _SeqNo, _Type, mac_hash({3, 0}, MacAlg, MacSecret, SeqNo, Type, Length, Fragment) -> ssl_v3:mac_hash(MacAlg, MacSecret, SeqNo, Type, Length, Fragment); mac_hash({3, N} = Version, MacAlg, MacSecret, SeqNo, Type, Length, Fragment) - when N =:= 1; N =:= 2; N =:= 3 -> + when N =:= 1; N =:= 2; N =:= 3; N =:= 4 -> tls_v1:mac_hash(MacAlg, MacSecret, SeqNo, Type, Version, Length, Fragment). @@ -2635,7 +2639,7 @@ generic_block_cipher_from_bin({3, N}, T, IV, HashSize) next_iv = IV}; generic_block_cipher_from_bin({3, N}, T, IV, HashSize) - when N == 2; N == 3 -> + when N == 2; N == 3; N == 4 -> Sz1 = byte_size(T) - 1, <<_:Sz1/binary, ?BYTE(PadLength)>> = T, IVLength = byte_size(IV), diff --git a/lib/ssl/src/ssl_connection.erl b/lib/ssl/src/ssl_connection.erl index 4b3a6cde01..6e98baf984 100644 --- a/lib/ssl/src/ssl_connection.erl +++ b/lib/ssl/src/ssl_connection.erl @@ -35,6 +35,7 @@ -include("ssl_internal.hrl"). -include("ssl_srp.hrl"). -include_lib("public_key/include/public_key.hrl"). +-include_lib("kernel/include/logger.hrl"). %% Setup @@ -334,14 +335,20 @@ handle_own_alert(Alert, Version, StateName, connection_states = ConnectionStates, ssl_options = SslOpts} = State) -> try %% Try to tell the other side - {BinMsg, _} = - Connection:encode_alert(Alert, Version, ConnectionStates), - Connection:send(Transport, Socket, BinMsg) + {BinMsg, _} = + Connection:encode_alert(Alert, Version, ConnectionStates), + Connection:send(Transport, Socket, BinMsg), + Report = #{direction => outbound, + protocol => 'tls_record', + message => BinMsg}, + ssl_logger:debug(SslOpts#ssl_options.log_level, Report, #{domain => [otp,ssl,tls_record]}) catch _:_ -> %% Can crash if we are in a uninitialized state ignore end, try %% Try to tell the local user - log_alert(SslOpts#ssl_options.log_alert, Role, Connection:protocol_name(), StateName, Alert#alert{role = Role}), + log_alert(SslOpts#ssl_options.log_level, Role, + Connection:protocol_name(), StateName, + Alert#alert{role = Role}), handle_normal_shutdown(Alert,StateName, State) catch _:_ -> ok @@ -372,8 +379,9 @@ handle_alert(#alert{level = ?FATAL} = Alert, StateName, port = Port, session = Session, user_application = {_Mon, Pid}, role = Role, socket_options = Opts, tracker = Tracker} = State) -> invalidate_session(Role, Host, Port, Session), - log_alert(SslOpts#ssl_options.log_alert, Role, Connection:protocol_name(), - StateName, Alert#alert{role = opposite_role(Role)}), + log_alert(SslOpts#ssl_options.log_level, Role, + Connection:protocol_name(), StateName, + Alert#alert{role = opposite_role(Role)}), alert_user(Transport, Tracker, Socket, StateName, Opts, Pid, From, Alert, Role, Connection), stop(normal, State); @@ -384,8 +392,9 @@ handle_alert(#alert{level = ?WARNING, description = ?CLOSE_NOTIFY} = Alert, handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert, StateName, #state{role = Role, ssl_options = SslOpts, protocol_cb = Connection, renegotiation = {true, internal}} = State) -> - log_alert(SslOpts#ssl_options.log_alert, Role, - Connection:protocol_name(), StateName, Alert#alert{role = opposite_role(Role)}), + log_alert(SslOpts#ssl_options.log_level, Role, + Connection:protocol_name(), StateName, + Alert#alert{role = opposite_role(Role)}), handle_normal_shutdown(Alert, StateName, State), stop({shutdown, peer_close}, State); @@ -393,8 +402,9 @@ handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert, #state{role = Role, ssl_options = SslOpts, renegotiation = {true, From}, protocol_cb = Connection} = State0) -> - log_alert(SslOpts#ssl_options.log_alert, Role, - Connection:protocol_name(), StateName, Alert#alert{role = opposite_role(Role)}), + log_alert(SslOpts#ssl_options.log_level, Role, + Connection:protocol_name(), StateName, + Alert#alert{role = opposite_role(Role)}), gen_statem:reply(From, {error, renegotiation_rejected}), {Record, State1} = Connection:next_record(State0), %% Go back to connection! @@ -404,8 +414,9 @@ handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert, %% Gracefully log and ignore all other warning alerts handle_alert(#alert{level = ?WARNING} = Alert, StateName, #state{ssl_options = SslOpts, protocol_cb = Connection, role = Role} = State0) -> - log_alert(SslOpts#ssl_options.log_alert, Role, - Connection:protocol_name(), StateName, Alert#alert{role = opposite_role(Role)}), + log_alert(SslOpts#ssl_options.log_level, Role, + Connection:protocol_name(), StateName, + Alert#alert{role = opposite_role(Role)}), {Record, State} = Connection:next_record(State0), Connection:next_event(StateName, Record, State). @@ -419,7 +430,7 @@ write_application_data(Data0, {FromPid, _} = From, transport_cb = Transport, connection_states = ConnectionStates0, socket_options = SockOpts, - ssl_options = #ssl_options{renegotiate_at = RenegotiateAt}} = State) -> + ssl_options = #ssl_options{renegotiate_at = RenegotiateAt} = SslOpts} = State) -> Data = encode_packet(Data0, SockOpts), case time_to_renegotiate(Data, ConnectionStates0, RenegotiateAt) of @@ -430,16 +441,21 @@ write_application_data(Data0, {FromPid, _} = From, {Msgs, ConnectionStates} = Connection:encode_data(Data, Version, ConnectionStates0), NewState = State#state{connection_states = ConnectionStates}, - case Connection:send(Transport, Socket, Msgs) of - ok when FromPid =:= self() -> - hibernate_after(connection, NewState, []); - Error when FromPid =:= self() -> - stop({shutdown, Error}, NewState); - ok -> - hibernate_after(connection, NewState, [{reply, From, ok}]); - Result -> - hibernate_after(connection, NewState, [{reply, From, Result}]) - end + RetVal = case Connection:send(Transport, Socket, Msgs) of + ok when FromPid =:= self() -> + hibernate_after(connection, NewState, []); + Error when FromPid =:= self() -> + stop({shutdown, Error}, NewState); + ok -> + hibernate_after(connection, NewState, [{reply, From, ok}]); + Result -> + hibernate_after(connection, NewState, [{reply, From, Result}]) + end, + Report = #{direction => outbound, + protocol => 'tls_record', + message => Msgs}, + ssl_logger:debug(SslOpts#ssl_options.log_level, Report, #{domain => [otp,ssl,tls_record]}), + RetVal end. read_application_data(Data, #state{user_application = {_Mon, Pid}, @@ -1343,7 +1359,7 @@ handle_info({ErrorTag, Socket, econnaborted}, StateName, handle_info({ErrorTag, Socket, Reason}, StateName, #state{socket = Socket, error_tag = ErrorTag} = State) -> Report = io_lib:format("SSL: Socket error: ~p ~n", [Reason]), - error_logger:error_report(Report), + ?LOG_ERROR(Report), handle_normal_shutdown(?ALERT_REC(?FATAL, ?CLOSE_NOTIFY), StateName, State), stop(normal, State); @@ -1392,7 +1408,7 @@ handle_info({cancel_start_or_recv, _RecvFrom}, StateName, State) -> handle_info(Msg, StateName, #state{socket = Socket, error_tag = Tag} = State) -> Report = io_lib:format("SSL: Got unexpected info: ~p ~n", [{Msg, Tag, Socket}]), - error_logger:info_report(Report), + ?LOG_NOTICE(Report), {next_state, StateName, State}. %%==================================================================== @@ -1501,13 +1517,18 @@ do_server_hello(Type, #hello_extensions{next_protocol_negotiation = NextProtocol ServerHelloExt, #state{negotiated_version = Version, session = #session{session_id = SessId}, - connection_states = ConnectionStates0} + connection_states = ConnectionStates0, + ssl_options = #ssl_options{versions = [HighestVersion|_]}} = State0, Connection) when is_atom(Type) -> - + %% TLS 1.3 - Section 4.1.3 + %% Override server random values for TLS 1.3 downgrade protection mechanism. + ConnectionStates1 = update_server_random(ConnectionStates0, Version, HighestVersion), + State1 = State0#state{connection_states = ConnectionStates1}, ServerHello = - ssl_handshake:server_hello(SessId, ssl:tls_version(Version), ConnectionStates0, ServerHelloExt), + ssl_handshake:server_hello(SessId, ssl:tls_version(Version), + ConnectionStates1, ServerHelloExt), State = server_hello(ServerHello, - State0#state{expecting_next_protocol_negotiation = + State1#state{expecting_next_protocol_negotiation = NextProtocols =/= undefined}, Connection), case Type of new -> @@ -1516,6 +1537,60 @@ do_server_hello(Type, #hello_extensions{next_protocol_negotiation = NextProtocol resumed_server_hello(State, Connection) end. +update_server_random(#{pending_read := #{security_parameters := ReadSecParams0} = + ReadState0, + pending_write := #{security_parameters := WriteSecParams0} = + WriteState0} = ConnectionStates, + Version, HighestVersion) -> + ReadRandom = override_server_random( + ReadSecParams0#security_parameters.server_random, + Version, + HighestVersion), + WriteRandom = override_server_random( + WriteSecParams0#security_parameters.server_random, + Version, + HighestVersion), + ReadSecParams = ReadSecParams0#security_parameters{server_random = ReadRandom}, + WriteSecParams = WriteSecParams0#security_parameters{server_random = WriteRandom}, + ReadState = ReadState0#{security_parameters => ReadSecParams}, + WriteState = WriteState0#{security_parameters => WriteSecParams}, + + ConnectionStates#{pending_read => ReadState, pending_write => WriteState}. + +%% TLS 1.3 - Section 4.1.3 +%% +%% If negotiating TLS 1.2, TLS 1.3 servers MUST set the last eight bytes +%% of their Random value to the bytes: +%% +%% 44 4F 57 4E 47 52 44 01 +%% +%% If negotiating TLS 1.1 or below, TLS 1.3 servers MUST and TLS 1.2 +%% servers SHOULD set the last eight bytes of their Random value to the +%% bytes: +%% +%% 44 4F 57 4E 47 52 44 00 +override_server_random(<<Random0:24/binary,_:8/binary>> = Random, {M,N}, {Major,Minor}) + when Major > 3 orelse Major =:= 3 andalso Minor >= 4 -> %% TLS 1.3 or above + if M =:= 3 andalso N =:= 3 -> %% Negotating TLS 1.2 + Down = ?RANDOM_OVERRIDE_TLS12, + <<Random0/binary,Down/binary>>; + M =:= 3 andalso N < 3 -> %% Negotating TLS 1.1 or prior + Down = ?RANDOM_OVERRIDE_TLS11, + <<Random0/binary,Down/binary>>; + true -> + Random + end; +override_server_random(<<Random0:24/binary,_:8/binary>> = Random, {M,N}, {Major,Minor}) + when Major =:= 3 andalso Minor =:= 3 -> %% TLS 1.2 + if M =:= 3 andalso N < 3 -> %% Negotating TLS 1.1 or prior + Down = ?RANDOM_OVERRIDE_TLS11, + <<Random0/binary,Down/binary>>; + true -> + Random + end; +override_server_random(Random, _, _) -> + Random. + new_server_hello(#server_hello{cipher_suite = CipherSuite, compression_method = Compression, session_id = SessionId}, @@ -2397,7 +2472,7 @@ handle_trusted_certs_db(#state{ssl_options = handle_trusted_certs_db(#state{cert_db_ref = Ref, cert_db = CertDb, ssl_options = #ssl_options{cacertfile = <<>>}}) when CertDb =/= undefined -> - %% Certs provided as DER directly can not be shared + %% Certs provided as DER directly cannot be shared %% with other connections and it is safe to delete them when the connection ends. ssl_pkix_db:remove_trusted_certs(Ref, CertDb); handle_trusted_certs_db(#state{file_ref_db = undefined}) -> @@ -2750,14 +2825,14 @@ alert_user(Transport, Tracker, Socket, Active, Pid, From, Alert, Role, Connectio Transport, Socket, Connection, Tracker), ReasonCode}) end. -log_alert(true, Role, ProtocolName, StateName, #alert{role = Role} = Alert) -> +log_alert(Level, Role, ProtocolName, StateName, #alert{role = Role} = Alert) -> Txt = ssl_alert:own_alert_txt(Alert), - error_logger:info_report(io_lib:format("~s ~p: In state ~p ~s\n", [ProtocolName, Role, StateName, Txt])); -log_alert(true, Role, ProtocolName, StateName, Alert) -> + Report = io_lib:format("~s ~p: In state ~p ~s\n", [ProtocolName, Role, StateName, Txt]), + ssl_logger:notice(Level, Report); +log_alert(Level, Role, ProtocolName, StateName, Alert) -> Txt = ssl_alert:alert_txt(Alert), - error_logger:info_report(io_lib:format("~s ~p: In state ~p ~s\n", [ProtocolName, Role, StateName, Txt])); -log_alert(false, _, _, _, _) -> - ok. + Report = io_lib:format("~s ~p: In state ~p ~s\n", [ProtocolName, Role, StateName, Txt]), + ssl_logger:notice(Level, Report). invalidate_session(client, Host, Port, Session) -> ssl_manager:invalidate_session(Host, Port, Session); @@ -2847,7 +2922,7 @@ erl_dist_stop_reason( Reason, #state{ssl_options = #ssl_options{erl_dist = true}}) -> case Reason of normal -> - %% We can not exit with normal since that will not bring + %% We cannot exit with normal since that will not bring %% down the rest of the distribution processes {shutdown, normal}; _ -> Reason diff --git a/lib/ssl/src/ssl_crl_hash_dir.erl b/lib/ssl/src/ssl_crl_hash_dir.erl index bb62737232..9478ff9b78 100644 --- a/lib/ssl/src/ssl_crl_hash_dir.erl +++ b/lib/ssl/src/ssl_crl_hash_dir.erl @@ -20,6 +20,7 @@ -module(ssl_crl_hash_dir). -include_lib("public_key/include/public_key.hrl"). +-include_lib("kernel/include/logger.hrl"). -behaviour(ssl_crl_cache_api). @@ -55,7 +56,7 @@ select(Issuer, {_DbHandle, [{dir, Dir}]}) -> %% is happy with that, but if it's true, this is an error. []; {error, Error} -> - error_logger:error_report( + ?LOG_ERROR( [{cannot_find_crl, Error}, {dir, Dir}, {module, ?MODULE}, @@ -86,7 +87,7 @@ find_crls(Issuer, Hash, Dir, N, Acc) -> error:Error -> %% Something is wrong with the file. Report %% it, and try the next one. - error_logger:error_report( + ?LOG_ERROR( [{crl_parse_error, Error}, {filename, Filename}, {module, ?MODULE}, diff --git a/lib/ssl/src/ssl_handshake.erl b/lib/ssl/src/ssl_handshake.erl index 5e4e50f0e1..73757e6b65 100644 --- a/lib/ssl/src/ssl_handshake.erl +++ b/lib/ssl/src/ssl_handshake.erl @@ -53,7 +53,7 @@ -export([certify/7, certificate_verify/6, verify_signature/5, master_secret/4, server_key_exchange_hash/2, verify_connection/6, init_handshake_history/0, update_handshake_history/2, verify_server_key/5, - select_version/3, extension_value/1 + select_version/3, select_supported_version/2, extension_value/1 ]). %% Encode @@ -505,6 +505,21 @@ verify_server_key(#server_key_params{params_bin = EncParams, select_version(RecordCB, ClientVersion, Versions) -> do_select_version(RecordCB, ClientVersion, Versions). + +%% Called by TLS 1.2/1.3 Server when "supported_versions" is present +%% in ClientHello. +%% Input lists are ordered (highest first) +select_supported_version([], _ServerVersions) -> + undefined; +select_supported_version([ClientVersion|T], ServerVersions) -> + case lists:member(ClientVersion, ServerVersions) of + true -> + ClientVersion; + false -> + select_supported_version(T, ServerVersions) + end. + + %%==================================================================== %% Encode handshake %%==================================================================== @@ -632,7 +647,20 @@ encode_hello_extensions([#sni{hostname = Hostname} | Rest], Acc) -> ?UINT16(ServerNameLength), ?BYTE(?SNI_NAMETYPE_HOST_NAME), ?UINT16(HostLen), HostnameBin/binary, - Acc/binary>>). + Acc/binary>>); +encode_hello_extensions([#client_hello_versions{versions = Versions0} | Rest], Acc) -> + Versions = encode_versions(Versions0), + VerLen = byte_size(Versions), + Len = VerLen + 2, + encode_hello_extensions(Rest, <<?UINT16(?SUPPORTED_VERSIONS_EXT), + ?UINT16(Len), ?UINT16(VerLen), Versions/binary, Acc/binary>>); +encode_hello_extensions([#server_hello_selected_version{selected_version = Version0} | Rest], Acc) -> + Version = encode_versions(Version0), + Len = byte_size(Version), %% 2 + encode_hello_extensions(Rest, <<?UINT16(?SUPPORTED_VERSIONS_EXT), + ?UINT16(Len), Version/binary, Acc/binary>>). + + encode_client_protocol_negotiation(undefined, _) -> undefined; @@ -930,7 +958,8 @@ premaster_secret(EncSecret, #'RSAPrivateKey'{} = RSAPrivateKey) -> %%==================================================================== client_hello_extensions(Version, CipherSuites, #ssl_options{signature_algs = SupportedHashSigns, - eccs = SupportedECCs} = SslOpts, ConnectionStates, Renegotiation) -> + eccs = SupportedECCs, + versions = Versions} = SslOpts, ConnectionStates, Renegotiation) -> {EcPointFormats, EllipticCurves} = case advertises_ec_ciphers(lists:map(fun ssl_cipher:suite_definition/1, CipherSuites)) of true -> @@ -940,18 +969,29 @@ client_hello_extensions(Version, CipherSuites, end, SRP = srp_user(SslOpts), - #hello_extensions{ - renegotiation_info = renegotiation_info(tls_record, client, - ConnectionStates, Renegotiation), - srp = SRP, - signature_algs = available_signature_algs(SupportedHashSigns, Version), - ec_point_formats = EcPointFormats, - elliptic_curves = EllipticCurves, - alpn = encode_alpn(SslOpts#ssl_options.alpn_advertised_protocols, Renegotiation), - next_protocol_negotiation = - encode_client_protocol_negotiation(SslOpts#ssl_options.next_protocol_selector, - Renegotiation), - sni = sni(SslOpts#ssl_options.server_name_indication)}. + HelloExtensions = + #hello_extensions{ + renegotiation_info = renegotiation_info(tls_record, client, + ConnectionStates, Renegotiation), + srp = SRP, + signature_algs = available_signature_algs(SupportedHashSigns, Version), + ec_point_formats = EcPointFormats, + elliptic_curves = EllipticCurves, + alpn = encode_alpn(SslOpts#ssl_options.alpn_advertised_protocols, Renegotiation), + next_protocol_negotiation = + encode_client_protocol_negotiation(SslOpts#ssl_options.next_protocol_selector, + Renegotiation), + sni = sni(SslOpts#ssl_options.server_name_indication)}, + + %% Add "supported_versions" extension if TLS 1.3 + case Version of + {3,4} -> + HelloExtensions#hello_extensions{ + client_hello_versions = #client_hello_versions{ + versions = Versions}}; + _Else -> + HelloExtensions + end. handle_client_hello_extensions(RecordCB, Random, ClientCipherSuites, #hello_extensions{renegotiation_info = Info, @@ -1722,6 +1762,16 @@ encode_alpn(undefined, _) -> encode_alpn(Protocols, _) -> #alpn{extension_data = lists:foldl(fun encode_protocol/2, <<>>, Protocols)}. + +encode_versions(Versions) -> + encode_versions(lists:reverse(Versions), <<>>). +%% +encode_versions([], Acc) -> + Acc; +encode_versions([{M,N}|T], Acc) -> + encode_versions(T, <<?BYTE(M),?BYTE(N),Acc/binary>>). + + hello_extensions_list(#hello_extensions{renegotiation_info = RenegotiationInfo, srp = SRP, signature_algs = HashSigns, @@ -1729,9 +1779,13 @@ hello_extensions_list(#hello_extensions{renegotiation_info = RenegotiationInfo, elliptic_curves = EllipticCurves, alpn = ALPN, next_protocol_negotiation = NextProtocolNegotiation, - sni = Sni}) -> + sni = Sni, + client_hello_versions = Versions, + server_hello_selected_version = Version}) -> [Ext || Ext <- [RenegotiationInfo, SRP, HashSigns, - EcPointFormats, EllipticCurves, ALPN, NextProtocolNegotiation, Sni], Ext =/= undefined]. + EcPointFormats, EllipticCurves, ALPN, + NextProtocolNegotiation, Sni, + Versions, Version], Ext =/= undefined]. %%-------------Decode handshakes--------------------------------- dec_server_key(<<?UINT16(PLen), P:PLen/binary, @@ -1937,9 +1991,22 @@ dec_hello_extensions(<<?UINT16(?SNI_EXT), ?UINT16(Len), ExtData:Len/binary, Rest/binary>>, Acc) -> <<?UINT16(_), NameList/binary>> = ExtData, dec_hello_extensions(Rest, Acc#hello_extensions{sni = dec_sni(NameList)}); + +dec_hello_extensions(<<?UINT16(?SUPPORTED_VERSIONS_EXT), ?UINT16(Len), + ExtData:Len/binary, Rest/binary>>, Acc) when Len > 2 -> + <<?UINT16(_),Versions/binary>> = ExtData, + dec_hello_extensions(Rest, Acc#hello_extensions{ + client_hello_versions = + #client_hello_versions{versions = decode_versions(Versions)}}); + +dec_hello_extensions(<<?UINT16(?SUPPORTED_VERSIONS_EXT), ?UINT16(Len), + ?UINT16(Version), Rest/binary>>, Acc) when Len =:= 2, Version =:= 16#0304 -> + dec_hello_extensions(Rest, Acc#hello_extensions{ + server_hello_selected_version = + #server_hello_selected_version{selected_version = [{3,4}]}}); + %% Ignore data following the ClientHello (i.e., %% extensions) if not understood. - dec_hello_extensions(<<?UINT16(_), ?UINT16(Len), _Unknown:Len/binary, Rest/binary>>, Acc) -> dec_hello_extensions(Rest, Acc); %% This theoretically should not happen if the protocol is followed, but if it does it is ignored. @@ -1961,6 +2028,15 @@ decode_alpn(undefined) -> decode_alpn(#alpn{extension_data=Data}) -> decode_protocols(Data, []). +decode_versions(Versions) -> + decode_versions(Versions, []). +%% +decode_versions(<<>>, Acc) -> + lists:reverse(Acc); +decode_versions(<<?BYTE(M),?BYTE(N),Rest/binary>>, Acc) -> + decode_versions(Rest, [{M,N}|Acc]). + + decode_next_protocols({next_protocol_negotiation, Protocols}) -> decode_protocols(Protocols, []). diff --git a/lib/ssl/src/ssl_handshake.hrl b/lib/ssl/src/ssl_handshake.hrl index a191fcf766..9cc6f570fc 100644 --- a/lib/ssl/src/ssl_handshake.hrl +++ b/lib/ssl/src/ssl_handshake.hrl @@ -105,7 +105,9 @@ srp, ec_point_formats, elliptic_curves, - sni + sni, + client_hello_versions, + server_hello_selected_version }). -record(server_hello, { @@ -377,4 +379,13 @@ hostname = undefined }). +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Supported Versions TLS 1.3 section 4.2.1 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +-define(SUPPORTED_VERSIONS_EXT, 43). + +-record(client_hello_versions, {versions}). +-record(server_hello_selected_version, {selected_version}). + -endif. % -ifdef(ssl_handshake). diff --git a/lib/ssl/src/ssl_internal.hrl b/lib/ssl/src/ssl_internal.hrl index ae1c3ea47c..0d3093c1ae 100644 --- a/lib/ssl/src/ssl_internal.hrl +++ b/lib/ssl/src/ssl_internal.hrl @@ -25,6 +25,7 @@ -include_lib("public_key/include/public_key.hrl"). +-define(VSN, "8.2.6"). -define(SECRET_PRINTOUT, "***"). -type reason() :: term(). @@ -71,14 +72,39 @@ -define(FALSE, 1). %% sslv3 is considered insecure due to lack of padding check (Poodle attack) -%% Keep as interop with legacy software but do not support as default --define(ALL_AVAILABLE_VERSIONS, ['tlsv1.2', 'tlsv1.1', tlsv1, sslv3]). +%% Keep as interop with legacy software but do not support as default +%% tlsv1.3 is under development (experimental). +-define(ALL_AVAILABLE_VERSIONS, ['tlsv1.3', 'tlsv1.2', 'tlsv1.1', tlsv1, sslv3]). -define(ALL_AVAILABLE_DATAGRAM_VERSIONS, ['dtlsv1.2', dtlsv1]). +%% Defines the default versions when not specified by an ssl option. -define(ALL_SUPPORTED_VERSIONS, ['tlsv1.2', 'tlsv1.1', tlsv1]). -define(MIN_SUPPORTED_VERSIONS, ['tlsv1.1', tlsv1]). + +%% Versions allowed in TLSCiphertext.version (TLS 1.2 and prior) and +%% TLSCiphertext.legacy_record_version (TLS 1.3). +%% TLS 1.3 sets TLSCiphertext.legacy_record_version to 0x0303 for all records +%% generated other than an than an initial ClientHello, where it MAY also be 0x0301. +%% Thus, the allowed range is limited to 0x0300 - 0x0303. +-define(ALL_TLS_RECORD_VERSIONS, ['tlsv1.2', 'tlsv1.1', tlsv1, sslv3]). + -define(ALL_DATAGRAM_SUPPORTED_VERSIONS, ['dtlsv1.2', dtlsv1]). -define(MIN_DATAGRAM_SUPPORTED_VERSIONS, [dtlsv1]). +%% TLS 1.3 - Section 4.1.3 +%% +%% If negotiating TLS 1.2, TLS 1.3 servers MUST set the last eight bytes +%% of their Random value to the bytes: +%% +%% 44 4F 57 4E 47 52 44 01 +%% +%% If negotiating TLS 1.1 or below, TLS 1.3 servers MUST and TLS 1.2 +%% servers SHOULD set the last eight bytes of their Random value to the +%% bytes: +%% +%% 44 4F 57 4E 47 52 44 00 +-define(RANDOM_OVERRIDE_TLS12, <<16#44,16#4F,16#57,16#4E,16#47,16#52,16#44,16#01>>). +-define(RANDOM_OVERRIDE_TLS11, <<16#44,16#4F,16#57,16#4E,16#47,16#52,16#44,16#00>>). + -define('24H_in_msec', 86400000). -define('24H_in_sec', 86400). @@ -127,7 +153,7 @@ alpn_preferred_protocols = undefined :: [binary()] | undefined, next_protocols_advertised = undefined :: [binary()] | undefined, next_protocol_selector = undefined, %% fun([binary()]) -> binary()) - log_alert :: boolean(), + log_level = notice :: atom(), server_name_indication = undefined, sni_hosts :: [{inet:hostname(), [tuple()]}], sni_fun :: function() | undefined, @@ -180,6 +206,8 @@ -type gen_fsm_state_return() :: {next_state, state_name(), term()} | {next_state, state_name(), term(), timeout()} | {stop, term(), term()}. +-type ssl_options() :: #ssl_options{}. + -endif. % -ifdef(ssl_internal). diff --git a/lib/ssl/src/ssl_logger.erl b/lib/ssl/src/ssl_logger.erl new file mode 100644 index 0000000000..35c8dcfd48 --- /dev/null +++ b/lib/ssl/src/ssl_logger.erl @@ -0,0 +1,349 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1999-2018. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +-module(ssl_logger). + +-export([debug/3, + format/2, + notice/2]). + +-define(DEC2HEX(X), + if ((X) >= 0) andalso ((X) =< 9) -> (X) + $0; + ((X) >= 10) andalso ((X) =< 15) -> (X) + $a - 10 + end). + +-define(rec_info(T,R),lists:zip(record_info(fields,T),tl(tuple_to_list(R)))). + +-include("tls_record.hrl"). +-include("ssl_internal.hrl"). +-include("tls_handshake.hrl"). +-include_lib("kernel/include/logger.hrl"). + +%%------------------------------------------------------------------------- +%% External API +%%------------------------------------------------------------------------- + +%% SSL log formatter +format(#{level:= _Level, msg:= {report, Msg}, meta:= _Meta}, _Config0) -> + #{direction := Direction, + protocol := Protocol, + message := BinMsg0} = Msg, + case Protocol of + 'tls_record' -> + BinMsg = lists:flatten(BinMsg0), + format_tls_record(Direction, BinMsg); + 'handshake' -> + format_handshake(Direction, BinMsg0); + _Other -> + [] + end. + +%% Stateful logging +debug(Level, Report, Meta) -> + case logger:compare_levels(Level, debug) of + lt -> + ?LOG_DEBUG(Report, Meta); + eq -> + ?LOG_DEBUG(Report, Meta); + _ -> + ok + end. + +%% Stateful logging +notice(Level, Report) -> + case logger:compare_levels(Level, notice) of + lt -> + ?LOG_NOTICE(Report); + eq -> + ?LOG_NOTICE(Report); + _ -> + ok + end. + + +%%------------------------------------------------------------------------- +%% Handshake Protocol +%%------------------------------------------------------------------------- +format_handshake(Direction, BinMsg) -> + {Header, Message} = parse_handshake(Direction, BinMsg), + io_lib:format("~s~n~s~n", [Header, Message]). + + +parse_handshake(Direction, #client_hello{ + client_version = Version + } = ClientHello) -> + Header = io_lib:format("~s ~s Handshake, ClientHello", + [header_prefix(Direction), + version(Version)]), + Message = io_lib:format("~p", [?rec_info(client_hello, ClientHello)]), + {Header, Message}; +parse_handshake(Direction, #server_hello{ + server_version = Version + } = ServerHello) -> + Header = io_lib:format("~s ~s Handshake, ServerHello", + [header_prefix(Direction), + version(Version)]), + Message = io_lib:format("~p", [?rec_info(server_hello, ServerHello)]), + {Header, Message}; +parse_handshake(Direction, #certificate{} = Certificate) -> + Header = io_lib:format("~s Handshake, Certificate", + [header_prefix(Direction)]), + Message = io_lib:format("~p", [?rec_info(certificate, Certificate)]), + {Header, Message}; +parse_handshake(Direction, #server_key_exchange{} = ServerKeyExchange) -> + Header = io_lib:format("~s Handshake, ServerKeyExchange", + [header_prefix(Direction)]), + Message = io_lib:format("~p", [?rec_info(server_key_exchange, ServerKeyExchange)]), + {Header, Message}; +parse_handshake(Direction, #server_key_params{} = ServerKeyExchange) -> + Header = io_lib:format("~s Handshake, ServerKeyExchange", + [header_prefix(Direction)]), + Message = io_lib:format("~p", [?rec_info(server_key_params, ServerKeyExchange)]), + {Header, Message}; +parse_handshake(Direction, #certificate_request{} = CertificateRequest) -> + Header = io_lib:format("~s Handshake, CertificateRequest", + [header_prefix(Direction)]), + Message = io_lib:format("~p", [?rec_info(certificate_request, CertificateRequest)]), + {Header, Message}; +parse_handshake(Direction, #server_hello_done{} = ServerHelloDone) -> + Header = io_lib:format("~s Handshake, ServerHelloDone", + [header_prefix(Direction)]), + Message = io_lib:format("~p", [?rec_info(server_hello_done, ServerHelloDone)]), + {Header, Message}; +parse_handshake(Direction, #client_key_exchange{} = ClientKeyExchange) -> + Header = io_lib:format("~s Handshake, ClientKeyExchange", + [header_prefix(Direction)]), + Message = io_lib:format("~p", [?rec_info(client_key_exchange, ClientKeyExchange)]), + {Header, Message}; +parse_handshake(Direction, #certificate_verify{} = CertificateVerify) -> + Header = io_lib:format("~s Handshake, CertificateVerify", + [header_prefix(Direction)]), + Message = io_lib:format("~p", [?rec_info(certificate_verify, CertificateVerify)]), + {Header, Message}; +parse_handshake(Direction, #finished{} = Finished) -> + Header = io_lib:format("~s Handshake, Finished", + [header_prefix(Direction)]), + Message = io_lib:format("~p", [?rec_info(finished, Finished)]), + {Header, Message}; +parse_handshake(Direction, #hello_request{} = HelloRequest) -> + Header = io_lib:format("~s Handshake, HelloRequest", + [header_prefix(Direction)]), + Message = io_lib:format("~p", [?rec_info(hello_request, HelloRequest)]), + {Header, Message}. + + +version({3,3}) -> + "TLS 1.2"; +version({3,2}) -> + "TLS 1.1"; +version({3,1}) -> + "TLS 1.0"; +version({3,0}) -> + "SSL 3.0"; +version({M,N}) -> + io_lib:format("TLS [0x0~B0~B]", [M,N]). + + +header_prefix(inbound) -> + "<<<"; +header_prefix(outbound) -> + ">>>". + + +%%------------------------------------------------------------------------- +%% TLS Record Protocol +%%------------------------------------------------------------------------- +format_tls_record(Direction, BinMsg) -> + {Message, Size} = convert_to_hex('tls_record', BinMsg), + Header = io_lib:format("~s (~B bytes) ~s~n", + [header_prefix_tls_record(Direction), + Size, + tls_record_version(BinMsg)]), + Header ++ Message. + + +header_prefix_tls_record(inbound) -> + "reading"; +header_prefix_tls_record(outbound) -> + "writing". + + +tls_record_version([<<?BYTE(B),?BYTE(3),?BYTE(3),_/binary>>|_]) -> + io_lib:format("TLS 1.2 Record Protocol, ~s", [msg_type(B)]); +tls_record_version([<<?BYTE(B),?BYTE(3),?BYTE(2),_/binary>>|_]) -> + io_lib:format("TLS 1.1 Record Protocol, ~s", [msg_type(B)]); +tls_record_version([<<?BYTE(B),?BYTE(3),?BYTE(1),_/binary>>|_]) -> + io_lib:format("TLS 1.0 Record Protocol, ~s", [msg_type(B)]); +tls_record_version([<<?BYTE(B),?BYTE(3),?BYTE(0),_/binary>>|_]) -> + io_lib:format("SSL 3.0 Record Protocol, ~s", [msg_type(B)]); +tls_record_version([<<?BYTE(B),?BYTE(M),?BYTE(N),_/binary>>|_]) -> + io_lib:format("TLS [0x0~B0~B] Record Protocol, ~s", [M, N, msg_type(B)]). + + +msg_type(20) -> "change_cipher_spec"; +msg_type(21) -> "alert"; +msg_type(22) -> "handshake"; +msg_type(23) -> "application_data"; +msg_type(_) -> unknown. + + +%%------------------------------------------------------------------------- +%% Hex encoding functions +%%------------------------------------------------------------------------- +convert_to_hex(Protocol, BinMsg) -> + convert_to_hex(Protocol, BinMsg, [], [], 0). +%% +convert_to_hex(P, [], Row0, Acc, C) when C rem 16 =:= 0 -> + Row = lists:reverse(end_row(P, Row0)), + {lists:reverse(Acc) ++ Row ++ io_lib:nl(), C}; +convert_to_hex(P, [], Row0, Acc, C) -> + Row = lists:reverse(end_row(P, Row0)), + Padding = calculate_padding(Row0, Acc), + PaddedRow = string:pad(Row, Padding, leading, $ ), + {lists:reverse(Acc) ++ PaddedRow ++ io_lib:nl(), C}; +convert_to_hex(P, [H|T], Row, Acc, C) when is_list(H) -> + convert_to_hex(P, H ++ T, Row, Acc, C); +convert_to_hex(P, [<<>>|T], Row, Acc, C) -> + convert_to_hex(P, T, Row, Acc, C); + +%% First line +convert_to_hex(P, [<<A:4,B:4,R/binary>>|T], Row, Acc, C) when C =:= 0 -> + convert_to_hex(P, [<<R/binary>>|T], + update_row(<<A:4,B:4>>, Row), + prepend_first_row(P, A, B, Acc, C), + C + 1); +%% New line +convert_to_hex(P, [<<A:4,B:4,R/binary>>|T], Row, Acc, C) when C rem 16 =:= 0 -> + convert_to_hex(P, [<<R/binary>>|T], + update_row(<<A:4,B:4>>, []), + prepend_row(P, A, B, Row, Acc, C), + C + 1); +%% Add 8th hex with extra whitespace +%% 0000 - 16 03 02 00 bd 01 00 00 b9 ... +%% ^^^^ +convert_to_hex(P, [<<A:4,B:4,R/binary>>|T], Row, Acc, C) when C rem 8 =:= 7 -> + convert_to_hex(P, [<<R/binary>>|T], + update_row(<<A:4,B:4>>, Row), + prepend_eighths_hex(A, B, Acc), + C + 1); +convert_to_hex(P, [<<A:4,B:4,R/binary>>|T], Row, Acc, C) -> + convert_to_hex(P, [<<R/binary>>|T], + update_row(<<A:4,B:4>>, Row), + prepend_hex(A, B, Acc), + C + 1); +%% First line +convert_to_hex(P, [H|T], Row, Acc, C) when is_integer(H), C =:= 0 -> + convert_to_hex(P, T, + update_row(H, Row), + prepend_first_row(P, H, Acc, C), + C + 1); +%% New line +convert_to_hex(P, [H|T], Row, Acc, C) when is_integer(H), C rem 16 =:= 0 -> + convert_to_hex(P, T, + update_row(H, []), + prepend_row(P, H, Row, Acc, C), + C + 1); +%% Add 8th hex with extra whitespace +%% 0000 - 16 03 02 00 bd 01 00 00 b9 ... +%% ^^^^ +convert_to_hex(P, [H|T], Row, Acc, C) when is_integer(H), C rem 8 =:= 7 -> + convert_to_hex(P, T, + update_row(H, Row), + prepend_eighths_hex(H, Acc), + C + 1); +convert_to_hex(P, [H|T], Row, Acc, C) when is_integer(H) -> + convert_to_hex(P, T, + update_row(H, Row), + prepend_hex(H, Acc), + C + 1). + + +row_prefix(tls_record, N) -> + S = string:pad(string:to_lower(erlang:integer_to_list(N, 16)),4,leading,$0), + lists:reverse(lists:flatten(S ++ " - ")). + + +end_row(tls_record, Row) -> + Row ++ " ". + + +%% Calculate padding of the "printable character" lines in order to be +%% visually aligned. +calculate_padding(Row, Acc) -> + %% Number of new line characters + NNL = (length(Acc) div 75) * length(io_lib:nl()), + %% Length of the last printed line + Length = (length(Acc) - NNL) rem 75, + %% Adjusted length of the last printed line + PaddedLength = 75 - (16 - length(Row)), %% Length + %% Padding + PaddedLength - Length. + + +%%------------------------------------------------------------------------- +%% Functions operating on reversed lists +%%------------------------------------------------------------------------- +update_row(B, Row) when is_binary(B) -> + case binary_to_list(B) of + [C] when 32 =< C, C =< 126 -> + [C|Row]; + _Else -> + [$.|Row] + end; +update_row(C, Row) when 32 =< C, C =< 126 -> + [C|Row]; +update_row(_, Row) -> + [$.|Row]. + + +prepend_first_row(P, A, B, Acc, C) -> + prepend_hex(A, B,row_prefix(P, C) ++ Acc). +%% +prepend_first_row(P, N, Acc, C) -> + prepend_hex(N,row_prefix(P, C) ++ Acc). + +prepend_row(P, A, B, Row, Acc, C) -> + prepend_hex(A, B,row_prefix(P, C) ++ io_lib:nl() ++ end_row(P, Row) ++ Acc). +%% +prepend_row(P, N, Row, Acc, C) -> + prepend_hex(N,row_prefix(P, C) ++ io_lib:nl() ++ end_row(P, Row) ++ Acc). + + + +prepend_hex(A, B, Acc) -> + [$ ,?DEC2HEX(B),?DEC2HEX(A)|Acc]. +%% +prepend_hex(N, Acc) -> + " " ++ number_to_hex(N) ++ Acc. + + +prepend_eighths_hex(A, B, Acc) -> + [$ ,$ ,?DEC2HEX(B),?DEC2HEX(A)|Acc]. +%% +prepend_eighths_hex(N, Acc) -> + " " ++ number_to_hex(N) ++ Acc. + +number_to_hex(N) -> + case string:to_lower(erlang:integer_to_list(N, 16)) of + H when length(H) < 2 -> + lists:append(H, "0"); + H -> + lists:reverse(H) + end. diff --git a/lib/ssl/src/ssl_manager.erl b/lib/ssl/src/ssl_manager.erl index 52aa164420..b3a425b2fe 100644 --- a/lib/ssl/src/ssl_manager.erl +++ b/lib/ssl/src/ssl_manager.erl @@ -505,10 +505,10 @@ last_delay_timer({{_,_},_}, TRef, {LastServer, _}) -> last_delay_timer({_,_}, TRef, {_, LastClient}) -> {TRef, LastClient}. -%% If we can not generate a not allready in use session ID in +%% If we cannot generate a not allready in use session ID in %% ?GEN_UNIQUE_ID_MAX_TRIES we make the new session uncacheable The %% value of ?GEN_UNIQUE_ID_MAX_TRIES is stolen from open SSL which -%% states : "If we can not find a session id in +%% states : "If we cannot find a session id in %% ?GEN_UNIQUE_ID_MAX_TRIES either the RAND code is broken or someone %% is trying to open roughly very close to 2^128 (or 2^256) SSL %% sessions to our server" @@ -519,7 +519,7 @@ new_id(Port, Tries, Cache, CacheCb) -> case CacheCb:lookup(Cache, {Port, Id}) of undefined -> Now = erlang:monotonic_time(), - %% New sessions can not be set to resumable + %% New sessions cannot be set to resumable %% until handshake is compleate and the %% other session values are set. CacheCb:update(Cache, {Port, Id}, #session{session_id = Id, diff --git a/lib/ssl/src/ssl_pkix_db.erl b/lib/ssl/src/ssl_pkix_db.erl index b6fae36ff9..e7e4af942a 100644 --- a/lib/ssl/src/ssl_pkix_db.erl +++ b/lib/ssl/src/ssl_pkix_db.erl @@ -27,6 +27,7 @@ -include("ssl_internal.hrl"). -include_lib("public_key/include/public_key.hrl"). -include_lib("kernel/include/file.hrl"). +-include_lib("kernel/include/logger.hrl"). -export([create/1, create_pem_cache/1, add_crls/3, remove_crls/2, remove/1, add_trusted_certs/3, @@ -311,7 +312,7 @@ decode_certs(Ref, Cert) -> error:_ -> Report = io_lib:format("SSL WARNING: Ignoring a CA cert as " "it could not be correctly decoded.~n", []), - error_logger:info_report(Report), + ?LOG_NOTICE(Report), undefined end. diff --git a/lib/ssl/src/ssl_record.hrl b/lib/ssl/src/ssl_record.hrl index ed007f58d7..e8ce50040f 100644 --- a/lib/ssl/src/ssl_record.hrl +++ b/lib/ssl/src/ssl_record.hrl @@ -74,7 +74,7 @@ -define(INITIAL_BYTES, 5). -define(MAX_SEQENCE_NUMBER, 18446744073709551615). %% (1 bsl 64) - 1 = 18446744073709551615 -%% Sequence numbers can not wrap so when max is about to be reached we should renegotiate. +%% Sequence numbers cannot wrap so when max is about to be reached we should renegotiate. %% We will renegotiate a little before so that there will be sequence numbers left %% for the rehandshake and a little data. Currently we decided to renegotiate a little more %% often as we can have a cheaper test to check if it is time to renegotiate. It will still diff --git a/lib/ssl/src/tls_connection.erl b/lib/ssl/src/tls_connection.erl index 4d1122f804..8320d3f7f3 100644 --- a/lib/ssl/src/tls_connection.erl +++ b/lib/ssl/src/tls_connection.erl @@ -38,7 +38,8 @@ -include("ssl_api.hrl"). -include("ssl_internal.hrl"). -include("ssl_srp.hrl"). --include_lib("public_key/include/public_key.hrl"). +-include_lib("public_key/include/public_key.hrl"). +-include_lib("kernel/include/logger.hrl"). %% Internal application API @@ -130,6 +131,7 @@ next_record(#state{protocol_buffers = = Buffers, connection_states = ConnStates0, ssl_options = #ssl_options{padding_check = Check}} = State) -> + case tls_record:decode_cipher_text(CT, ConnStates0, Check) of {Plain, ConnStates} -> {Plain, State#state{protocol_buffers = @@ -265,9 +267,19 @@ send_handshake(Handshake, State) -> queue_handshake(Handshake, #state{negotiated_version = Version, tls_handshake_history = Hist0, flight_buffer = Flight0, - connection_states = ConnectionStates0} = State0) -> + connection_states = ConnectionStates0, + ssl_options = SslOpts} = State0) -> {BinHandshake, ConnectionStates, Hist} = encode_handshake(Handshake, Version, ConnectionStates0, Hist0), + Report = #{direction => outbound, + protocol => 'tls_record', + message => BinHandshake}, + HandshakeMsg = #{direction => outbound, + protocol => 'handshake', + message => Handshake}, + ssl_logger:debug(SslOpts#ssl_options.log_level, HandshakeMsg, #{domain => [otp,ssl,handshake]}), + ssl_logger:debug(SslOpts#ssl_options.log_level, Report, #{domain => [otp,ssl,tls_record]}), + State0#state{connection_states = ConnectionStates, tls_handshake_history = Hist, flight_buffer = Flight0 ++ [BinHandshake]}. @@ -279,10 +291,15 @@ send_handshake_flight(#state{socket = Socket, {State0#state{flight_buffer = []}, []}. queue_change_cipher(Msg, #state{negotiated_version = Version, - flight_buffer = Flight0, - connection_states = ConnectionStates0} = State0) -> + flight_buffer = Flight0, + connection_states = ConnectionStates0, + ssl_options = SslOpts} = State0) -> {BinChangeCipher, ConnectionStates} = encode_change_cipher(Msg, Version, ConnectionStates0), + Report = #{direction => outbound, + protocol => 'tls_record', + message => BinChangeCipher}, + ssl_logger:debug(SslOpts#ssl_options.log_level, Report, #{domain => [otp,ssl,tls_record]}), State0#state{connection_states = ConnectionStates, flight_buffer = Flight0 ++ [BinChangeCipher]}. @@ -310,10 +327,16 @@ empty_connection_state(ConnectionEnd, BeastMitigation) -> send_alert(Alert, #state{negotiated_version = Version, socket = Socket, transport_cb = Transport, - connection_states = ConnectionStates0} = State0) -> + connection_states = ConnectionStates0, + ssl_options = SslOpts} = State0) -> {BinMsg, ConnectionStates} = encode_alert(Alert, Version, ConnectionStates0), + send(Transport, Socket, BinMsg), + Report = #{direction => outbound, + protocol => 'tls_record', + message => BinMsg}, + ssl_logger:debug(SslOpts#ssl_options.log_level, Report, #{domain => [otp,ssl,tls_record]}), State0#state{connection_states = ConnectionStates}. %%-------------------------------------------------------------------- @@ -416,6 +439,14 @@ init({call, From}, {start, Timeout}, {BinMsg, ConnectionStates, Handshake} = encode_handshake(Hello, HelloVersion, ConnectionStates0, Handshake0), send(Transport, Socket, BinMsg), + Report = #{direction => outbound, + protocol => 'tls_record', + message => BinMsg}, + HelloMsg = #{direction => outbound, + protocol => 'handshake', + message => Hello}, + ssl_logger:debug(SslOpts#ssl_options.log_level, HelloMsg, #{domain => [otp,ssl,handshake]}), + ssl_logger:debug(SslOpts#ssl_options.log_level, Report, #{domain => [otp,ssl,tls_record]}), State1 = State0#state{connection_states = ConnectionStates, negotiated_version = Version, %% Requested version session = @@ -647,11 +678,11 @@ initial_state(Role, Host, Port, Socket, {SSLOptions, SocketOptions, Tracker}, Us next_tls_record(Data, StateName, #state{protocol_buffers = #protocol_buffers{tls_record_buffer = Buf0, - tls_cipher_texts = CT0} = Buffers} - = State0) -> - case tls_record:get_tls_records(Data, + tls_cipher_texts = CT0} = Buffers, + ssl_options = SslOpts} = State0) -> + case tls_record:get_tls_records(Data, acceptable_record_versions(StateName, State0), - Buf0) of + Buf0, SslOpts) of {Records, Buf1} -> CT1 = CT0 ++ Records, next_record(State0#state{protocol_buffers = @@ -662,10 +693,18 @@ next_tls_record(Data, StateName, #state{protocol_buffers = end. +%% TLS 1.3 Client/Server +%% - Ignore TLSPlaintext.legacy_record_version +%% - Verify that TLSCiphertext.legacy_record_version is set to 0x0303 for all records +%% other than an initial ClientHello, where it MAY also be 0x0301. acceptable_record_versions(hello, _) -> - [tls_record:protocol_version(Vsn) || Vsn <- ?ALL_AVAILABLE_VERSIONS]; + [tls_record:protocol_version(Vsn) || Vsn <- ?ALL_TLS_RECORD_VERSIONS]; +acceptable_record_versions(_, #state{negotiated_version = {Major, Minor}}) + when Major > 3; Major =:= 3, Minor >= 4 -> + [{3, 3}]; acceptable_record_versions(_, #state{negotiated_version = Version}) -> [Version]. + handle_record_alert(Alert, _) -> Alert. diff --git a/lib/ssl/src/tls_handshake.erl b/lib/ssl/src/tls_handshake.erl index f1eecb2875..7004c7a2f4 100644 --- a/lib/ssl/src/tls_handshake.erl +++ b/lib/ssl/src/tls_handshake.erl @@ -31,6 +31,7 @@ -include("ssl_internal.hrl"). -include("ssl_cipher.hrl"). -include_lib("public_key/include/public_key.hrl"). +-include_lib("kernel/include/logger.hrl"). %% Handshake handling -export([client_hello/8, hello/4]). @@ -60,6 +61,18 @@ client_hello(Host, Port, ConnectionStates, } = SslOpts, Cache, CacheCb, Renegotiation, OwnCert) -> Version = tls_record:highest_protocol_version(Versions), + + %% In TLS 1.3, the client indicates its version preferences in the + %% "supported_versions" extension (Section 4.2.1) and the + %% legacy_version field MUST be set to 0x0303, which is the version + %% number for TLS 1.2. + LegacyVersion = + case tls_record:is_higher(Version, {3,2}) of + true -> + {3,3}; + false -> + Version + end, #{security_parameters := SecParams} = ssl_record:pending_connection_state(ConnectionStates, read), AvailableCipherSuites = ssl_handshake:available_suites(UserSuites, Version), @@ -70,7 +83,7 @@ client_hello(Host, Port, ConnectionStates, CipherSuites = ssl_handshake:cipher_suites(AvailableCipherSuites, Renegotiation, Fallback), Id = ssl_session:client_id({Host, Port, SslOpts}, Cache, CacheCb, OwnCert), #client_hello{session_id = Id, - client_version = Version, + client_version = LegacyVersion, cipher_suites = CipherSuites, compression_methods = ssl_record:compressions(), random = SecParams#security_parameters.client_random, @@ -92,6 +105,65 @@ client_hello(Host, Port, ConnectionStates, %% %% Description: Handles a received hello message %%-------------------------------------------------------------------- + + +%% TLS 1.3 - Section 4.1.3 +%% TLS 1.3 clients receiving a ServerHello indicating TLS 1.2 or below +%% MUST check that the last eight bytes are not equal to either of these +%% values. +hello(#server_hello{server_version = {Major, Minor}, + random = <<_:24/binary,Down:8/binary>>}, + #ssl_options{versions = [{M,N}|_]}, _, _) + when (M > 3 orelse M =:= 3 andalso N >= 4) andalso %% TLS 1.3 client + (Major =:= 3 andalso Minor =:= 3 andalso %% Negotiating TLS 1.2 + Down =:= ?RANDOM_OVERRIDE_TLS12) orelse + + (M > 3 orelse M =:= 3 andalso N >= 4) andalso %% TLS 1.3 client + (Major =:= 3 andalso Minor < 3 andalso %% Negotiating TLS 1.1 or prior + Down =:= ?RANDOM_OVERRIDE_TLS11) -> + ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER); + +%% TLS 1.2 clients SHOULD also check that the last eight bytes are not +%% equal to the second value if the ServerHello indicates TLS 1.1 or below. +hello(#server_hello{server_version = {Major, Minor}, + random = <<_:24/binary,Down:8/binary>>}, + #ssl_options{versions = [{M,N}|_]}, _, _) + when (M =:= 3 andalso N =:= 3) andalso %% TLS 1.2 client + (Major =:= 3 andalso Minor < 3 andalso %% Negotiating TLS 1.1 or prior + Down =:= ?RANDOM_OVERRIDE_TLS11) -> + ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER); + + +%% TLS 1.3 - 4.2.1. Supported Versions +%% If the "supported_versions" extension in the ServerHello contains a +%% version not offered by the client or contains a version prior to TLS +%% 1.3, the client MUST abort the handshake with an "illegal_parameter" +%% alert. +%%-------------------------------------------------------------------- +%% TLS 1.2 Client +%% +%% - If "supported_version" is present (ServerHello): +%% - Abort handshake with an "illegal_parameter" alert +hello(#server_hello{server_version = Version, + extensions = #hello_extensions{ + server_hello_selected_version = + #server_hello_selected_version{selected_version = Version} + }}, + #ssl_options{versions = SupportedVersions}, + _ConnectionStates0, _Renegotiation) -> + case tls_record:is_higher({3,4}, Version) of + true -> + ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER); + false -> + case tls_record:is_acceptable_version(Version, SupportedVersions) of + true -> + %% Implement TLS 1.3 statem ??? + ?ALERT_REC(?FATAL, ?PROTOCOL_VERSION); + false -> + ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER) + end + end; + hello(#server_hello{server_version = Version, random = Random, cipher_suite = CipherSuite, compression_method = Compression, @@ -106,6 +178,37 @@ hello(#server_hello{server_version = Version, random = Random, false -> ?ALERT_REC(?FATAL, ?PROTOCOL_VERSION) end; + + +%% TLS 1.2 Server +%% - If "supported_versions" is present (ClientHello): +%% - Select version from "supported_versions" (ignore ClientHello.legacy_version) +%% - If server only supports versions greater than "supported_versions": +%% - Abort handshake with a "protocol_version" alert (*) +%% - If "supported_versions" is absent (ClientHello): +%% - Negotiate the minimum of ClientHello.legacy_version and TLS 1.2 (**) +%% - If server only supports versions greater than ClientHello.legacy_version: +%% - Abort handshake with a "protocol_version" alert +%% +%% (*) Sends alert even if there is a gap in supported versions +%% e.g. Server 1.0,1.2 Client 1.1,1.3 +%% (**) Current implementation can negotiate a version not supported by the client +%% e.g. Server 1.0,1.2 Client 1.1 -> ServerHello 1.0 +hello(#client_hello{client_version = _ClientVersion, + cipher_suites = CipherSuites, + extensions = #hello_extensions{ + client_hello_versions = + #client_hello_versions{versions = ClientVersions} + }} = Hello, + #ssl_options{versions = Versions} = SslOpts, + Info, Renegotiation) -> + try + Version = ssl_handshake:select_supported_version(ClientVersions, Versions), + do_hello(Version, Versions, CipherSuites, Hello, SslOpts, Info, Renegotiation) + catch + _:_ -> + ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, malformed_handshake_data) + end; hello(#client_hello{client_version = ClientVersion, cipher_suites = CipherSuites} = Hello, @@ -113,18 +216,7 @@ hello(#client_hello{client_version = ClientVersion, Info, Renegotiation) -> try Version = ssl_handshake:select_version(tls_record, ClientVersion, Versions), - case ssl_cipher:is_fallback(CipherSuites) of - true -> - Highest = tls_record:highest_protocol_version(Versions), - case tls_record:is_higher(Highest, Version) of - true -> - ?ALERT_REC(?FATAL, ?INAPPROPRIATE_FALLBACK); - false -> - handle_client_hello(Version, Hello, SslOpts, Info, Renegotiation) - end; - false -> - handle_client_hello(Version, Hello, SslOpts, Info, Renegotiation) - end + do_hello(Version, Versions, CipherSuites, Hello, SslOpts, Info, Renegotiation) catch _:_ -> ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, malformed_handshake_data) @@ -241,6 +333,31 @@ handle_server_hello_extensions(Version, SessionId, Random, CipherSuite, {ConnectionStates, ProtoExt, Protocol} -> {Version, SessionId, ConnectionStates, ProtoExt, Protocol} end. + + +do_hello(undefined, _Versions, _CipherSuites, _Hello, _SslOpts, _Info, _Renegotiation) -> + ?ALERT_REC(?FATAL, ?PROTOCOL_VERSION); +do_hello(Version, Versions, CipherSuites, Hello, SslOpts, Info, Renegotiation) -> + case tls_record:is_higher({3,4}, Version) of + true -> %% TLS 1.2 and older + case ssl_cipher:is_fallback(CipherSuites) of + true -> + Highest = tls_record:highest_protocol_version(Versions), + case tls_record:is_higher(Highest, Version) of + true -> + ?ALERT_REC(?FATAL, ?INAPPROPRIATE_FALLBACK); + false -> + handle_client_hello(Version, Hello, SslOpts, Info, Renegotiation) + end; + false -> + handle_client_hello(Version, Hello, SslOpts, Info, Renegotiation) + end; + false -> + %% Implement TLS 1.3 statem ??? + ?ALERT_REC(?FATAL, ?PROTOCOL_VERSION) + end. + + %%-------------------------------------------------------------------- enc_handshake(#hello_request{}, _Version) -> {?HELLO_REQUEST, <<>>}; @@ -272,6 +389,10 @@ get_tls_handshake_aux(Version, <<?BYTE(Type), ?UINT24(Length), Raw = <<?BYTE(Type), ?UINT24(Length), Body/binary>>, try decode_handshake(Version, Type, Body) of Handshake -> + Report = #{direction => inbound, + protocol => 'handshake', + message => Handshake}, + ssl_logger:debug(Opts#ssl_options.log_level, Report, #{domain => [otp,ssl,handshake]}), get_tls_handshake_aux(Version, Rest, Opts, [{Handshake,Raw} | Acc]) catch _:_ -> diff --git a/lib/ssl/src/tls_record.erl b/lib/ssl/src/tls_record.erl index f1aca8c801..444759aafa 100644 --- a/lib/ssl/src/tls_record.erl +++ b/lib/ssl/src/tls_record.erl @@ -30,9 +30,10 @@ -include("ssl_alert.hrl"). -include("tls_handshake.hrl"). -include("ssl_cipher.hrl"). +-include_lib("kernel/include/logger.hrl"). %% Handling of incoming data --export([get_tls_records/3, init_connection_states/2]). +-export([get_tls_records/4, init_connection_states/2]). %% Encoding TLS records -export([encode_handshake/3, encode_alert_record/3, @@ -75,24 +76,24 @@ init_connection_states(Role, BeastMitigation) -> pending_write => Pending}. %%-------------------------------------------------------------------- --spec get_tls_records(binary(), [tls_version()], binary()) -> {[binary()], binary()} | #alert{}. +-spec get_tls_records(binary(), [tls_version()], binary(), ssl_options()) -> {[binary()], binary()} | #alert{}. %% %% and returns it as a list of tls_compressed binaries also returns leftover %% Description: Given old buffer and new data from TCP, packs up a records %% data %%-------------------------------------------------------------------- -get_tls_records(Data, Versions, Buffer) -> +get_tls_records(Data, Versions, Buffer, SslOpts) -> BinData = list_to_binary([Buffer, Data]), case erlang:byte_size(BinData) of N when N >= 3 -> case assert_version(BinData, Versions) of true -> - get_tls_records_aux(BinData, []); + get_tls_records_aux(BinData, [], SslOpts); false -> ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC) end; _ -> - get_tls_records_aux(BinData, []) + get_tls_records_aux(BinData, [], SslOpts) end. %%==================================================================== @@ -229,6 +230,8 @@ decode_cipher_text(#ssl_tls{type = Type, version = Version, %% Description: Creates a protocol version record from a version atom %% or vice versa. %%-------------------------------------------------------------------- +protocol_version('tlsv1.3') -> + {3, 4}; protocol_version('tlsv1.2') -> {3, 3}; protocol_version('tlsv1.1') -> @@ -239,6 +242,8 @@ protocol_version(sslv3) -> {3, 0}; protocol_version(sslv2) -> %% Backwards compatibility {2, 0}; +protocol_version({3, 4}) -> + 'tlsv1.3'; protocol_version({3, 3}) -> 'tlsv1.2'; protocol_version({3, 2}) -> @@ -399,33 +404,61 @@ assert_version(<<?BYTE(_), ?BYTE(MajVer), ?BYTE(MinVer), _/binary>>, Versions) - get_tls_records_aux(<<?BYTE(?APPLICATION_DATA),?BYTE(MajVer),?BYTE(MinVer), ?UINT16(Length), Data:Length/binary, Rest/binary>>, - Acc) -> + Acc, SslOpts) -> + RawTLSRecord = <<?BYTE(?APPLICATION_DATA),?BYTE(MajVer),?BYTE(MinVer), + ?UINT16(Length), Data:Length/binary>>, + Report = #{direction => inbound, + protocol => 'tls_record', + message => [RawTLSRecord]}, + ssl_logger:debug(SslOpts#ssl_options.log_level, Report, #{domain => [otp,ssl,tls_record]}), get_tls_records_aux(Rest, [#ssl_tls{type = ?APPLICATION_DATA, version = {MajVer, MinVer}, - fragment = Data} | Acc]); + fragment = Data} | Acc], + SslOpts); get_tls_records_aux(<<?BYTE(?HANDSHAKE),?BYTE(MajVer),?BYTE(MinVer), - ?UINT16(Length), - Data:Length/binary, Rest/binary>>, Acc) -> + ?UINT16(Length), + Data:Length/binary, Rest/binary>>, Acc, SslOpts) -> + RawTLSRecord = <<?BYTE(?HANDSHAKE),?BYTE(MajVer),?BYTE(MinVer), + ?UINT16(Length), Data:Length/binary>>, + Report = #{direction => inbound, + protocol => 'tls_record', + message => [RawTLSRecord]}, + ssl_logger:debug(SslOpts#ssl_options.log_level, Report, #{domain => [otp,ssl,tls_record]}), get_tls_records_aux(Rest, [#ssl_tls{type = ?HANDSHAKE, version = {MajVer, MinVer}, - fragment = Data} | Acc]); + fragment = Data} | Acc], + SslOpts); get_tls_records_aux(<<?BYTE(?ALERT),?BYTE(MajVer),?BYTE(MinVer), ?UINT16(Length), Data:Length/binary, - Rest/binary>>, Acc) -> + Rest/binary>>, Acc, SslOpts) -> + RawTLSRecord = <<?BYTE(?ALERT),?BYTE(MajVer),?BYTE(MinVer), + ?UINT16(Length), Data:Length/binary>>, + Report = #{direction => inbound, + protocol => 'tls_record', + message => [RawTLSRecord]}, + ssl_logger:debug(SslOpts#ssl_options.log_level, Report, #{domain => [otp,ssl,tls_record]}), get_tls_records_aux(Rest, [#ssl_tls{type = ?ALERT, version = {MajVer, MinVer}, - fragment = Data} | Acc]); + fragment = Data} | Acc], + SslOpts); get_tls_records_aux(<<?BYTE(?CHANGE_CIPHER_SPEC),?BYTE(MajVer),?BYTE(MinVer), ?UINT16(Length), Data:Length/binary, Rest/binary>>, - Acc) -> + Acc, SslOpts) -> + RawTLSRecord = <<?BYTE(?CHANGE_CIPHER_SPEC),?BYTE(MajVer),?BYTE(MinVer), + ?UINT16(Length), Data:Length/binary>>, + Report = #{direction => inbound, + protocol => 'tls_record', + message => [RawTLSRecord]}, + ssl_logger:debug(SslOpts#ssl_options.log_level, Report, #{domain => [otp,ssl,tls_record]}), get_tls_records_aux(Rest, [#ssl_tls{type = ?CHANGE_CIPHER_SPEC, version = {MajVer, MinVer}, - fragment = Data} | Acc]); + fragment = Data} | Acc], + SslOpts); get_tls_records_aux(<<0:1, _CT:7, ?BYTE(_MajVer), ?BYTE(_MinVer), ?UINT16(Length), _/binary>>, - _Acc) when Length > ?MAX_CIPHER_TEXT_LENGTH -> + _Acc, _SslOpts) when Length > ?MAX_CIPHER_TEXT_LENGTH -> ?ALERT_REC(?FATAL, ?RECORD_OVERFLOW); -get_tls_records_aux(Data, Acc) -> +get_tls_records_aux(Data, Acc, _SslOpts) -> case size(Data) =< ?MAX_CIPHER_TEXT_LENGTH + ?INITIAL_BYTES of true -> {lists:reverse(Acc), Data}; diff --git a/lib/ssl/src/tls_v1.erl b/lib/ssl/src/tls_v1.erl index d6b500748e..6ef6040761 100644 --- a/lib/ssl/src/tls_v1.erl +++ b/lib/ssl/src/tls_v1.erl @@ -74,7 +74,7 @@ finished(Role, Version, PrfAlgo, MasterSecret, Handshake) prf(?MD5SHA, MasterSecret, finished_label(Role), [MD5, SHA], 12); finished(Role, Version, PrfAlgo, MasterSecret, Handshake) - when Version == 3 -> + when Version == 3; Version == 4 -> %% RFC 5246 - 7.4.9. Finished %% struct { %% opaque verify_data[12]; @@ -85,6 +85,7 @@ finished(Role, Version, PrfAlgo, MasterSecret, Handshake) Hash = crypto:hash(mac_algo(PrfAlgo), Handshake), prf(PrfAlgo, MasterSecret, finished_label(Role), Hash, 12). + -spec certificate_verify(md5sha | sha, integer(), [binary()]) -> binary(). certificate_verify(md5sha, _Version, Handshake) -> @@ -154,7 +155,7 @@ setup_keys(Version, _PrfAlgo, MasterSecret, ServerRandom, ClientRandom, HashSize %% TLS v1.2 setup_keys(Version, PrfAlgo, MasterSecret, ServerRandom, ClientRandom, HashSize, KeyMatLen, IVSize) - when Version == 3 -> + when Version == 3; Version == 4 -> %% RFC 5246 - 6.3. Key calculation %% key_block = PRF(SecurityParameters.master_secret, %% "key expansion", @@ -192,7 +193,7 @@ mac_hash(Method, Mac_write_secret, Seq_num, Type, {Major, Minor}, Fragment]), Mac. --spec suites(1|2|3) -> [ssl_cipher:cipher_suite()]. +-spec suites(1|2|3|4) -> [ssl_cipher:cipher_suite()]. suites(Minor) when Minor == 1; Minor == 2 -> [ @@ -244,8 +245,15 @@ suites(3) -> %% ?TLS_DH_DSS_WITH_AES_256_GCM_SHA384, %% ?TLS_DH_RSA_WITH_AES_128_GCM_SHA256, %% ?TLS_DH_DSS_WITH_AES_128_GCM_SHA256 - ] ++ suites(2). + ] ++ suites(2); + + +suites(4) -> + suites(3). + +signature_algs({3, 4}, HashSigns) -> + signature_algs({3, 3}, HashSigns); signature_algs({3, 3}, HashSigns) -> CryptoSupports = crypto:supports(), Hashes = proplists:get_value(hashs, CryptoSupports), @@ -273,6 +281,8 @@ signature_algs({3, 3}, HashSigns) -> end, [], HashSigns), lists:reverse(Supported). +default_signature_algs({3, 4}) -> + default_signature_algs({3, 3}); default_signature_algs({3, 3} = Version) -> Default = [%% SHA2 {sha512, ecdsa}, diff --git a/lib/ssl/test/ssl_basic_SUITE.erl b/lib/ssl/test/ssl_basic_SUITE.erl index 013786d863..480988b6e4 100644 --- a/lib/ssl/test/ssl_basic_SUITE.erl +++ b/lib/ssl/test/ssl_basic_SUITE.erl @@ -3527,7 +3527,7 @@ honor_cipher_order(Config, Honor, ServerCiphers, ClientCiphers, Expected) -> %%-------------------------------------------------------------------- tls_ciphersuite_vs_version() -> - [{doc,"Test a SSLv3 client can not negotiate a TLSv* cipher suite."}]. + [{doc,"Test a SSLv3 client cannot negotiate a TLSv* cipher suite."}]. tls_ciphersuite_vs_version(Config) when is_list(Config) -> {_ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), diff --git a/lib/ssl/test/ssl_certificate_verify_SUITE.erl b/lib/ssl/test/ssl_certificate_verify_SUITE.erl index 63e9d07d0b..c0981a9eaf 100644 --- a/lib/ssl/test/ssl_certificate_verify_SUITE.erl +++ b/lib/ssl/test/ssl_certificate_verify_SUITE.erl @@ -513,7 +513,7 @@ verify_fun_always_run_client(Config) when is_list(Config) -> Port = ssl_test_lib:inet_port(Server), %% If user verify fun is called correctly we fail the connection. - %% otherwise we can not tell this case apart form where we miss + %% otherwise we cannot tell this case apart form where we miss %% to call users verify fun FunAndState = {fun(_,{extension, _}, UserState) -> {unknown, UserState}; @@ -552,7 +552,7 @@ verify_fun_always_run_server(Config) when is_list(Config) -> {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), %% If user verify fun is called correctly we fail the connection. - %% otherwise we can not tell this case apart form where we miss + %% otherwise we cannot tell this case apart form where we miss %% to call users verify fun FunAndState = {fun(_,{extension, _}, UserState) -> {unknown, UserState}; diff --git a/lib/ssl/vsn.mk b/lib/ssl/vsn.mk index 10be907b4f..75d959accf 100644 --- a/lib/ssl/vsn.mk +++ b/lib/ssl/vsn.mk @@ -1 +1 @@ -SSL_VSN = 9.0 +SSL_VSN = 9.1 |