diff --git a/src/emqx_listeners.erl b/src/emqx_listeners.erl index ecbc54053..270a66085 100644 --- a/src/emqx_listeners.erl +++ b/src/emqx_listeners.erl @@ -134,7 +134,18 @@ start_listener(Proto, ListenOn, Options) when Proto == https; Proto == wss -> start_http_listener(fun cowboy:start_tls/3, 'mqtt:wss', ListenOn, ranch_opts(Options), ws_opts(Options)). -start_mqtt_listener(Name, ListenOn, Options) -> +replace(Opts, Key, Value) -> [{Key, Value} | proplists:delete(Key, Opts)]. + +drop_tls13_for_old_otp(Options) -> + case proplists:get_value(ssl_options, Options) of + undefined -> Options; + SslOpts -> + SslOpts1 = emqx_tls_lib:drop_tls13_for_old_otp(SslOpts), + replace(Options, ssl_options, SslOpts1) + end. + +start_mqtt_listener(Name, ListenOn, Options0) -> + Options = drop_tls13_for_old_otp(Options0), SockOpts = esockd:parse_opt(Options), esockd:open(Name, ListenOn, merge_default(SockOpts), {emqx_connection, start_link, [Options -- SockOpts]}). @@ -151,7 +162,8 @@ ws_opts(Options) -> ProxyProto = proplists:get_value(proxy_protocol, Options, false), #{env => #{dispatch => Dispatch}, proxy_header => ProxyProto}. -ranch_opts(Options) -> +ranch_opts(Options0) -> + Options = drop_tls13_for_old_otp(Options0), NumAcceptors = proplists:get_value(acceptors, Options, 4), MaxConnections = proplists:get_value(max_connections, Options, 1024), TcpOptions = proplists:get_value(tcp_options, Options, []), diff --git a/src/emqx_tls_lib.erl b/src/emqx_tls_lib.erl index 215f0b5ca..72a955962 100644 --- a/src/emqx_tls_lib.erl +++ b/src/emqx_tls_lib.erl @@ -21,6 +21,7 @@ , default_ciphers/0 , default_ciphers/1 , integral_ciphers/2 + , drop_tls13_for_old_otp/1 ]). %% non-empty string @@ -140,3 +141,58 @@ split_by_comma(Bin) -> %% trim spaces trim_space(Bin) -> hd([I || I <- binary:split(Bin, <<" ">>), I =/= <<>>]). + +%% @doc Drop tlsv1.3 version and ciphers from ssl options +%% if running on otp 22 or earlier. +drop_tls13_for_old_otp(SslOpts) -> + case list_to_integer(erlang:system_info(otp_release)) < 23 of + true -> drop_tls13(SslOpts); + false -> SslOpts + end. + +%% The ciphers that ssl:cipher_suites(exclusive, 'tlsv1.3', openssl) +%% should return when running on otp 23. +%% But we still have to hard-code them because tlsv1.3 on otp 22 is +%% not trustworthy. +-define(TLSV13_EXCLUSIVE_CIPHERS, [ "TLS_AES_256_GCM_SHA384" + , "TLS_AES_128_GCM_SHA256" + , "TLS_CHACHA20_POLY1305_SHA256" + , "TLS_AES_128_CCM_SHA256" + , "TLS_AES_128_CCM_8_SHA256" + ]). +drop_tls13(SslOpts0) -> + SslOpts1 = case proplists:get_value(versions, SslOpts0) of + undefined -> SslOpts0; + Vsns -> replace(SslOpts0, versions, Vsns -- ['tlsv1.3']) + end, + case proplists:get_value(ciphers, SslOpts1) of + undefined -> SslOpts1; + Ciphers -> replace(SslOpts1, ciphers, Ciphers -- ?TLSV13_EXCLUSIVE_CIPHERS) + end. + +replace(Opts, Key, Value) -> [{Key, Value} | proplists:delete(Key, Opts)]. + +-if(?OTP_RELEASE > 22). +-ifdef(TEST). +-include_lib("eunit/include/eunit.hrl"). + +drop_tls13_test() -> + Versions = default_versions(), + ?assert(lists:member('tlsv1.3', Versions)), + Ciphers = default_ciphers(), + ?assert(has_tlsv13_cipher(Ciphers)), + Opts0 = [{versions, Versions}, {ciphers, Ciphers}, other, {bool, true}], + Opts = drop_tls13(Opts0), + ?assertNot(lists:member('tlsv1.3', proplists:get_value(versions, Opts))), + ?assertNot(has_tlsv13_cipher(proplists:get_value(ciphers, Opts))). + +drop_tls13_no_versions_cipers_test() -> + Opts0 = [other, {bool, true}], + Opts = drop_tls13(Opts0), + ?_assertEqual(Opts0, Opts). + +has_tlsv13_cipher(Ciphers) -> + lists:any(fun(C) -> lists:member(C, Ciphers) end, ?TLSV13_EXCLUSIVE_CIPHERS). + +-endif. +-endif.