Просмотр исходного кода

Merge pull request #8769 from zmstone/0820-refactor-simplify-ssl-opts-handling

0820 refactor simplify ssl opts handling
Zaiming (Stone) Shi 3 лет назад
Родитель
Сommit
2b29b1b2cd

+ 4 - 0
CHANGES-5.0.md

@@ -6,6 +6,10 @@
 * Fix `$queue` topic name error in management API return. [#8728](https://github.com/emqx/emqx/pull/8728)
 * Fix sometimes `client.connected` and `client.disconnected` could be in wrong order. [#8625](https://github.com/emqx/emqx/pull/8625)
 
+## Enhancements
+
+* Do not auto-populate default SSL cipher suites, so that the configs are less bloated. [#8769](https://github.com/emqx/emqx/pull/8769)
+
 # 5.0.5
 
 ## Bug fixes

+ 1 - 5
apps/emqx/src/emqx_listeners.erl

@@ -583,11 +583,7 @@ enable_authn(Opts) ->
     maps:get(enable_authn, Opts, true).
 
 ssl_opts(Opts) ->
-    maps:to_list(
-        emqx_tls_lib:drop_tls13_for_old_otp(
-            maps:get(ssl_options, Opts, #{})
-        )
-    ).
+    emqx_tls_lib:to_server_opts(tls, maps:get(ssl_options, Opts, #{})).
 
 tcp_opts(Opts) ->
     maps:to_list(

+ 18 - 19
apps/emqx/src/emqx_schema.erl

@@ -102,7 +102,7 @@
 
 -export([namespace/0, roots/0, roots/1, fields/1, desc/1]).
 -export([conf_get/2, conf_get/3, keys/2, filter/1]).
--export([server_ssl_opts_schema/2, client_ssl_opts_schema/1, ciphers_schema/1, default_ciphers/1]).
+-export([server_ssl_opts_schema/2, client_ssl_opts_schema/1, ciphers_schema/1]).
 -export([sc/2, map/2]).
 
 -elvis([{elvis_style, god_modules, disable}]).
@@ -1843,6 +1843,8 @@ filter(Opts) ->
 common_ssl_opts_schema(Defaults) ->
     D = fun(Field) -> maps:get(to_atom(Field), Defaults, undefined) end,
     Df = fun(Field, Default) -> maps:get(to_atom(Field), Defaults, Default) end,
+    Collection = maps:get(versions, Defaults, tls_all_available),
+    AvailableVersions = default_tls_vsns(Collection),
     [
         {"cacertfile",
             sc(
@@ -1909,9 +1911,9 @@ common_ssl_opts_schema(Defaults) ->
             sc(
                 hoconsc:array(typerefl:atom()),
                 #{
-                    default => default_tls_vsns(maps:get(versions, Defaults, tls_all_available)),
+                    default => AvailableVersions,
                     desc => ?DESC(common_ssl_opts_schema_versions),
-                    validator => fun validate_tls_versions/1
+                    validator => fun(Inputs) -> validate_tls_versions(AvailableVersions, Inputs) end
                 }
             )},
         {"ciphers", ciphers_schema(D("ciphers"))},
@@ -2022,9 +2024,9 @@ client_ssl_opts_schema(Defaults) ->
         ].
 
 default_tls_vsns(dtls_all_available) ->
-    proplists:get_value(available_dtls, ssl:versions());
+    emqx_tls_lib:available_versions(dtls);
 default_tls_vsns(tls_all_available) ->
-    emqx_tls_lib:default_versions().
+    emqx_tls_lib:available_versions(tls).
 
 -spec ciphers_schema(quic | dtls_all_available | tls_all_available | undefined) ->
     hocon_schema:field_schema().
@@ -2039,6 +2041,10 @@ ciphers_schema(Default) ->
         #{
             default => default_ciphers(Default),
             converter => fun
+                (<<>>) ->
+                    [];
+                ("") ->
+                    [];
                 (Ciphers) when is_binary(Ciphers) ->
                     binary:split(Ciphers, <<",">>, [global]);
                 (Ciphers) when is_list(Ciphers) ->
@@ -2060,19 +2066,15 @@ default_ciphers(Which) ->
         do_default_ciphers(Which)
     ).
 
-do_default_ciphers(undefined) ->
-    do_default_ciphers(tls_all_available);
 do_default_ciphers(quic) ->
     [
         "TLS_AES_256_GCM_SHA384",
         "TLS_AES_128_GCM_SHA256",
         "TLS_CHACHA20_POLY1305_SHA256"
     ];
-do_default_ciphers(dtls_all_available) ->
-    %% as of now, dtls does not support tlsv1.3 ciphers
-    emqx_tls_lib:selected_ciphers(['dtlsv1.2', 'dtlsv1']);
-do_default_ciphers(tls_all_available) ->
-    emqx_tls_lib:default_ciphers().
+do_default_ciphers(_) ->
+    %% otherwise resolve default ciphers list at runtime
+    [].
 
 %% @private return a list of keys in a parent field
 -spec keys(string(), hocon:config()) -> [string()].
@@ -2246,19 +2248,16 @@ parse_user_lookup_fun(StrConf) ->
     {fun Mod:Fun/3, undefined}.
 
 validate_ciphers(Ciphers) ->
-    All = emqx_tls_lib:all_ciphers(),
-    case lists:filter(fun(Cipher) -> not lists:member(Cipher, All) end, Ciphers) of
+    Set = emqx_tls_lib:all_ciphers_set_cached(),
+    case lists:filter(fun(Cipher) -> not sets:is_element(Cipher, Set) end, Ciphers) of
         [] -> ok;
         Bad -> {error, {bad_ciphers, Bad}}
     end.
 
-validate_tls_versions(Versions) ->
-    AvailableVersions =
-        proplists:get_value(available, ssl:versions()) ++
-            proplists:get_value(available_dtls, ssl:versions()),
+validate_tls_versions(AvailableVersions, Versions) ->
     case lists:filter(fun(V) -> not lists:member(V, AvailableVersions) end, Versions) of
         [] -> ok;
-        Vs -> {error, {unsupported_ssl_versions, Vs}}
+        Vs -> {error, {unsupported_tls_versions, Vs}}
     end.
 
 validations() ->

+ 149 - 161
apps/emqx/src/emqx_tls_lib.erl

@@ -18,13 +18,12 @@
 
 %% version & cipher suites
 -export([
-    default_versions/0,
-    integral_versions/1,
+    available_versions/1,
+    integral_versions/2,
     default_ciphers/0,
     selected_ciphers/1,
     integral_ciphers/2,
-    drop_tls13_for_old_otp/1,
-    all_ciphers/0
+    all_ciphers_set_cached/0
 ]).
 
 %% SSL files
@@ -38,7 +37,9 @@
 ]).
 
 -export([
-    to_client_opts/1
+    to_server_opts/2,
+    to_client_opts/1,
+    to_client_opts/2
 ]).
 
 -include("logger.hrl").
@@ -54,27 +55,80 @@
 %% non-empty list of strings
 -define(IS_STRING_LIST(L), (is_list(L) andalso L =/= [] andalso ?IS_STRING(hd(L)))).
 
-%% @doc Returns the default supported tls versions.
--spec default_versions() -> [atom()].
-default_versions() -> available_versions().
+%% 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"
+]).
+
+-define(SELECTED_CIPHERS, [
+    "ECDHE-ECDSA-AES256-GCM-SHA384",
+    "ECDHE-RSA-AES256-GCM-SHA384",
+    "ECDHE-ECDSA-AES256-SHA384",
+    "ECDHE-RSA-AES256-SHA384",
+    "ECDH-ECDSA-AES256-GCM-SHA384",
+    "ECDH-RSA-AES256-GCM-SHA384",
+    "ECDH-ECDSA-AES256-SHA384",
+    "ECDH-RSA-AES256-SHA384",
+    "DHE-DSS-AES256-GCM-SHA384",
+    "DHE-DSS-AES256-SHA256",
+    "AES256-GCM-SHA384",
+    "AES256-SHA256",
+    "ECDHE-ECDSA-AES128-GCM-SHA256",
+    "ECDHE-RSA-AES128-GCM-SHA256",
+    "ECDHE-ECDSA-AES128-SHA256",
+    "ECDHE-RSA-AES128-SHA256",
+    "ECDH-ECDSA-AES128-GCM-SHA256",
+    "ECDH-RSA-AES128-GCM-SHA256",
+    "ECDH-ECDSA-AES128-SHA256",
+    "ECDH-RSA-AES128-SHA256",
+    "DHE-DSS-AES128-GCM-SHA256",
+    "DHE-DSS-AES128-SHA256",
+    "AES128-GCM-SHA256",
+    "AES128-SHA256",
+    "ECDHE-ECDSA-AES256-SHA",
+    "ECDHE-RSA-AES256-SHA",
+    "DHE-DSS-AES256-SHA",
+    "ECDH-ECDSA-AES256-SHA",
+    "ECDH-RSA-AES256-SHA",
+    "ECDHE-ECDSA-AES128-SHA",
+    "ECDHE-RSA-AES128-SHA",
+    "DHE-DSS-AES128-SHA",
+    "ECDH-ECDSA-AES128-SHA",
+    "ECDH-RSA-AES128-SHA",
+
+    %% psk
+    "RSA-PSK-AES256-GCM-SHA384",
+    "RSA-PSK-AES256-CBC-SHA384",
+    "RSA-PSK-AES128-GCM-SHA256",
+    "RSA-PSK-AES128-CBC-SHA256",
+    "RSA-PSK-AES256-CBC-SHA",
+    "RSA-PSK-AES128-CBC-SHA"
+]).
 
 %% @doc Validate a given list of desired tls versions.
 %% raise an error exception if non of them are available.
 %% The input list can be a string/binary of comma separated versions.
--spec integral_versions(undefined | string() | binary() | [ssl:tls_version()]) ->
+-spec integral_versions(tls | dtls, undefined | string() | binary() | [ssl:tls_version()]) ->
     [ssl:tls_version()].
-integral_versions(undefined) ->
-    integral_versions(default_versions());
-integral_versions([]) ->
-    integral_versions(default_versions());
-integral_versions(<<>>) ->
-    integral_versions(default_versions());
-integral_versions(Desired) when ?IS_STRING(Desired) ->
-    integral_versions(iolist_to_binary(Desired));
-integral_versions(Desired) when is_binary(Desired) ->
-    integral_versions(parse_versions(Desired));
-integral_versions(Desired) ->
-    Available = available_versions(),
+integral_versions(Type, undefined) ->
+    available_versions(Type);
+integral_versions(Type, []) ->
+    available_versions(Type);
+integral_versions(Type, <<>>) ->
+    available_versions(Type);
+integral_versions(Type, Desired) when ?IS_STRING(Desired) ->
+    integral_versions(Type, iolist_to_binary(Desired));
+integral_versions(Type, Desired) when is_binary(Desired) ->
+    integral_versions(Type, parse_versions(Desired));
+integral_versions(Type, Desired) ->
+    Available = available_versions(Type),
     case lists:filter(fun(V) -> lists:member(V, Available) end, Desired) of
         [] ->
             erlang:error(#{
@@ -86,33 +140,36 @@ integral_versions(Desired) ->
             Filtered
     end.
 
-%% @doc Return a list of all supported ciphers.
-all_ciphers() -> all_ciphers(default_versions()).
+%% @doc Return a set of all ciphers
+all_ciphers_set_cached() ->
+    case persistent_term:get(?FUNCTION_NAME, false) of
+        false ->
+            S = sets:from_list(all_ciphers()),
+            persistent_term:put(?FUNCTION_NAME, S);
+        Set ->
+            Set
+    end.
+
+%% @hidden Return a list of all supported ciphers.
+all_ciphers() ->
+    all_ciphers(available_versions(all)).
 
-%% @doc Return a list of (openssl string format) cipher suites.
+%% @hidden Return a list of (openssl string format) cipher suites.
 -spec all_ciphers([ssl:tls_version()]) -> [string()].
 all_ciphers(['tlsv1.3']) ->
     %% When it's only tlsv1.3 wanted, use 'exclusive' here
     %% because 'all' returns legacy cipher suites too,
     %% which does not make sense since tlsv1.3 can not use
     %% legacy cipher suites.
-    ssl:cipher_suites(exclusive, 'tlsv1.3', openssl);
+    ?TLSV13_EXCLUSIVE_CIPHERS;
 all_ciphers(Versions) ->
     %% assert non-empty
     List = lists:append([ssl:cipher_suites(all, V, openssl) || V <- Versions]),
     [_ | _] = dedup(List).
 
 %% @doc All Pre-selected TLS ciphers.
-%% ssl:cipher_suites(all, V, openssl) is too slow. so we cache default ciphers.
 default_ciphers() ->
-    case persistent_term:get(default_ciphers, undefined) of
-        undefined ->
-            Default = selected_ciphers(available_versions()),
-            persistent_term:put(default_ciphers, Default),
-            Default;
-        Default ->
-            Default
-    end.
+    selected_ciphers(available_versions(all)).
 
 %% @doc Pre-selected TLS ciphers for given versions..
 selected_ciphers(Vsns) ->
@@ -126,54 +183,11 @@ selected_ciphers(Vsns) ->
 
 do_selected_ciphers('tlsv1.3') ->
     case lists:member('tlsv1.3', proplists:get_value(available, ssl:versions())) of
-        true -> ssl:cipher_suites(exclusive, 'tlsv1.3', openssl);
+        true -> ?TLSV13_EXCLUSIVE_CIPHERS;
         false -> []
     end ++ do_selected_ciphers('tlsv1.2');
 do_selected_ciphers(_) ->
-    [
-        "ECDHE-ECDSA-AES256-GCM-SHA384",
-        "ECDHE-RSA-AES256-GCM-SHA384",
-        "ECDHE-ECDSA-AES256-SHA384",
-        "ECDHE-RSA-AES256-SHA384",
-        "ECDH-ECDSA-AES256-GCM-SHA384",
-        "ECDH-RSA-AES256-GCM-SHA384",
-        "ECDH-ECDSA-AES256-SHA384",
-        "ECDH-RSA-AES256-SHA384",
-        "DHE-DSS-AES256-GCM-SHA384",
-        "DHE-DSS-AES256-SHA256",
-        "AES256-GCM-SHA384",
-        "AES256-SHA256",
-        "ECDHE-ECDSA-AES128-GCM-SHA256",
-        "ECDHE-RSA-AES128-GCM-SHA256",
-        "ECDHE-ECDSA-AES128-SHA256",
-        "ECDHE-RSA-AES128-SHA256",
-        "ECDH-ECDSA-AES128-GCM-SHA256",
-        "ECDH-RSA-AES128-GCM-SHA256",
-        "ECDH-ECDSA-AES128-SHA256",
-        "ECDH-RSA-AES128-SHA256",
-        "DHE-DSS-AES128-GCM-SHA256",
-        "DHE-DSS-AES128-SHA256",
-        "AES128-GCM-SHA256",
-        "AES128-SHA256",
-        "ECDHE-ECDSA-AES256-SHA",
-        "ECDHE-RSA-AES256-SHA",
-        "DHE-DSS-AES256-SHA",
-        "ECDH-ECDSA-AES256-SHA",
-        "ECDH-RSA-AES256-SHA",
-        "ECDHE-ECDSA-AES128-SHA",
-        "ECDHE-RSA-AES128-SHA",
-        "DHE-DSS-AES128-SHA",
-        "ECDH-ECDSA-AES128-SHA",
-        "ECDH-RSA-AES128-SHA",
-
-        %% psk
-        "RSA-PSK-AES256-GCM-SHA384",
-        "RSA-PSK-AES256-CBC-SHA384",
-        "RSA-PSK-AES128-GCM-SHA256",
-        "RSA-PSK-AES128-CBC-SHA256",
-        "RSA-PSK-AES256-CBC-SHA",
-        "RSA-PSK-AES128-CBC-SHA"
-    ].
+    ?SELECTED_CIPHERS.
 
 %% @doc Ensure version & cipher-suites integrity.
 -spec integral_ciphers([ssl:tls_version()], binary() | string() | [string()]) -> [string()].
@@ -201,17 +215,17 @@ ensure_tls13_cipher(true, Ciphers) ->
 ensure_tls13_cipher(false, Ciphers) ->
     Ciphers.
 
-%% default ssl versions based on available versions.
--spec available_versions() -> [atom()].
-available_versions() ->
-    OtpRelease = list_to_integer(erlang:system_info(otp_release)),
-    default_versions(OtpRelease).
+%% @doc Returns the default available tls/dtls versions.
+available_versions(Type) ->
+    All = ssl:versions(),
+    available_versions(Type, All).
 
-%% tlsv1.3 is available from OTP-22 but we do not want to use until 23.
-default_versions(OtpRelease) when OtpRelease >= 23 ->
-    proplists:get_value(available, ssl:versions());
-default_versions(_) ->
-    lists:delete('tlsv1.3', proplists:get_value(available, ssl:versions())).
+available_versions(tls, All) ->
+    proplists:get_value(available, All);
+available_versions(dtls, All) ->
+    proplists:get_value(available_dtls, All);
+available_versions(all, All) ->
+    available_versions(tls, All) ++ available_versions(dtls, All).
 
 %% Deduplicate a list without re-ordering the elements.
 dedup([]) ->
@@ -244,6 +258,8 @@ do_parse_versions([V | More], Acc) ->
             do_parse_versions(More, [Parsed | Acc])
     end.
 
+parse_version(<<"dtlsv1.2">>) -> 'dtlsv1.2';
+parse_version(<<"dtlsv1">>) -> dtlsv1;
 parse_version(<<"tlsv", Vsn/binary>>) -> parse_version(Vsn);
 parse_version(<<"v", Vsn/binary>>) -> parse_version(Vsn);
 parse_version(<<"1.3">>) -> 'tlsv1.3';
@@ -259,36 +275,6 @@ split_by_comma(Bin) ->
 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 maps:find(versions, SslOpts0) of
-            error -> SslOpts0;
-            {ok, Vsns} -> SslOpts0#{versions => (Vsns -- ['tlsv1.3'])}
-        end,
-    case maps:find(ciphers, SslOpts1) of
-        error -> SslOpts1;
-        {ok, Ciphers} -> SslOpts1#{ciphers => Ciphers -- ?TLSV13_EXCLUSIVE_CIPHERS}
-    end.
-
 %% @doc The input map is a HOCON decoded result of a struct defined as
 %% emqx_schema:server_ssl_opts_schema. (NOTE: before schema-checked).
 %% `keyfile', `certfile' and `cacertfile' can be either pem format key or certificates,
@@ -498,27 +484,54 @@ do_drop_invalid_certs([Key | Keys], SSL) ->
             end
     end.
 
-%% @doc Convert hocon-checked ssl client options (map()) to
+%% @doc Convert hocon-checked ssl server options (map()) to
 %% proplist accepted by ssl library.
+-spec to_server_opts(tls | dtls, map()) -> [{atom(), term()}].
+to_server_opts(Type, Opts) ->
+    Versions = integral_versions(Type, maps:get(versions, Opts, undefined)),
+    Ciphers = integral_ciphers(Versions, maps:get(ciphers, Opts, undefined)),
+    maps:to_list(Opts#{
+        ciphers => Ciphers,
+        versions => Versions
+    }).
+
+%% @doc Convert hocon-checked tls client options (map()) to
+%% proplist accepted by ssl library.
+-spec to_client_opts(map()) -> [{atom(), term()}].
 to_client_opts(Opts) ->
+    to_client_opts(tls, Opts).
+
+%% @doc Convert hocon-checked tls or dtls client options (map()) to
+%% proplist accepted by ssl library.
+-spec to_client_opts(tls | dtls, map()) -> [{atom(), term()}].
+to_client_opts(Type, Opts) ->
     GetD = fun(Key, Default) -> fuzzy_map_get(Key, Opts, Default) end,
     Get = fun(Key) -> GetD(Key, undefined) end,
-    KeyFile = ensure_str(Get(keyfile)),
-    CertFile = ensure_str(Get(certfile)),
-    CAFile = ensure_str(Get(cacertfile)),
-    Verify = GetD(verify, verify_none),
-    SNI = ensure_sni(Get(server_name_indication)),
-    Versions = integral_versions(Get(versions)),
-    Ciphers = integral_ciphers(Versions, Get(ciphers)),
-    filter([
-        {keyfile, KeyFile},
-        {certfile, CertFile},
-        {cacertfile, CAFile},
-        {verify, Verify},
-        {server_name_indication, SNI},
-        {versions, Versions},
-        {ciphers, Ciphers}
-    ]).
+    case GetD(enable, false) of
+        true ->
+            KeyFile = ensure_str(Get(keyfile)),
+            CertFile = ensure_str(Get(certfile)),
+            CAFile = ensure_str(Get(cacertfile)),
+            Verify = GetD(verify, verify_none),
+            SNI = ensure_sni(Get(server_name_indication)),
+            Versions = integral_versions(Type, Get(versions)),
+            Ciphers = integral_ciphers(Versions, Get(ciphers)),
+            filter([
+                {keyfile, KeyFile},
+                {certfile, CertFile},
+                {cacertfile, CAFile},
+                {verify, Verify},
+                {server_name_indication, SNI},
+                {versions, Versions},
+                {ciphers, Ciphers},
+                {reuse_sessions, Get(reuse_sessions)},
+                {depth, Get(depth)},
+                {password, ensure_str(Get(password))},
+                {secure_renegotiate, Get(secure_renegotiate)}
+            ]);
+        false ->
+            []
+    end.
 
 filter([]) -> [];
 filter([{_, undefined} | T]) -> filter(T);
@@ -556,28 +569,3 @@ ensure_ssl_file_key(SSL, RequiredKeys) ->
         [] -> ok;
         Miss -> {error, #{reason => ssl_file_option_not_found, which_options => Miss}}
     end.
-
--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 = all_ciphers(),
-    ?assert(has_tlsv13_cipher(Ciphers)),
-    Opts0 = #{versions => Versions, ciphers => Ciphers, other => true},
-    Opts = drop_tls13(Opts0),
-    ?assertNot(lists:member('tlsv1.3', maps:get(versions, Opts, undefined))),
-    ?assertNot(has_tlsv13_cipher(maps:get(ciphers, Opts, undefined))).
-
-drop_tls13_no_versions_cipers_test() ->
-    Opts0 = #{other => 0, 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.

+ 6 - 7
apps/emqx/test/emqx_schema_tests.erl

@@ -21,8 +21,7 @@
 ssl_opts_dtls_test() ->
     Sc = emqx_schema:server_ssl_opts_schema(
         #{
-            versions => dtls_all_available,
-            ciphers => dtls_all_available
+            versions => dtls_all_available
         },
         false
     ),
@@ -30,7 +29,7 @@ ssl_opts_dtls_test() ->
     ?assertMatch(
         #{
             versions := ['dtlsv1.2', 'dtlsv1'],
-            ciphers := ["ECDHE-ECDSA-AES256-GCM-SHA384" | _]
+            ciphers := []
         },
         Checked
     ).
@@ -42,7 +41,7 @@ ssl_opts_tls_1_3_test() ->
     ?assertMatch(
         #{
             versions := ['tlsv1.3'],
-            ciphers := [_ | _]
+            ciphers := []
         },
         Checked
     ).
@@ -53,7 +52,7 @@ ssl_opts_tls_for_ranch_test() ->
     ?assertMatch(
         #{
             versions := ['tlsv1.3'],
-            ciphers := [_ | _],
+            ciphers := [],
             handshake_timeout := _
         },
         Checked
@@ -125,7 +124,7 @@ validate(Schema, Data0) ->
         ),
     Checked.
 
-ciperhs_schema_test() ->
+ciphers_schema_test() ->
     Sc = emqx_schema:ciphers_schema(undefined),
     WSc = #{roots => [{ciphers, Sc}]},
     ?assertThrow(
@@ -135,7 +134,7 @@ ciperhs_schema_test() ->
 
 bad_tls_version_test() ->
     Sc = emqx_schema:server_ssl_opts_schema(#{}, false),
-    Reason = {unsupported_ssl_versions, [foo]},
+    Reason = {unsupported_tls_versions, [foo]},
     ?assertThrow(
         {_Sc, [#{kind := validation_error, reason := Reason}]},
         validate(Sc, #{<<"versions">> => [<<"foo">>]})

+ 28 - 18
apps/emqx/test/emqx_tls_lib_tests.erl

@@ -51,24 +51,34 @@ test_cipher_format(Input) ->
     ?assertEqual([?TLS_13_CIPHER, ?TLS_12_CIPHER], Ciphers).
 
 tls_versions_test() ->
-    ?assert(lists:member('tlsv1.3', emqx_tls_lib:default_versions())).
-
-tls_version_unknown_test() ->
-    ?assertEqual(
-        emqx_tls_lib:default_versions(),
-        emqx_tls_lib:integral_versions([])
-    ),
-    ?assertEqual(
-        emqx_tls_lib:default_versions(),
-        emqx_tls_lib:integral_versions(<<>>)
-    ),
-    ?assertEqual(
-        emqx_tls_lib:default_versions(),
-        emqx_tls_lib:integral_versions("foo")
-    ),
-    ?assertError(
-        #{reason := no_available_tls_version},
-        emqx_tls_lib:integral_versions([foo])
+    ?assert(lists:member('tlsv1.3', emqx_tls_lib:available_versions(tls))).
+
+tls_version_unknown_test_() ->
+    lists:flatmap(
+        fun(Type) ->
+            [
+                ?_assertEqual(
+                    emqx_tls_lib:available_versions(Type),
+                    emqx_tls_lib:integral_versions(Type, [])
+                ),
+                ?_assertEqual(
+                    emqx_tls_lib:available_versions(Type),
+                    emqx_tls_lib:integral_versions(Type, <<>>)
+                ),
+                ?_assertEqual(
+                    emqx_tls_lib:available_versions(Type),
+                    %% unknown version dropped
+                    emqx_tls_lib:integral_versions(Type, "foo")
+                ),
+                fun() ->
+                    ?assertError(
+                        #{reason := no_available_tls_version},
+                        emqx_tls_lib:integral_versions(Type, [foo])
+                    )
+                end
+            ]
+        end,
+        [tls, dtls]
     ).
 
 cipher_suites_no_duplication_test() ->

+ 2 - 4
apps/emqx_gateway/src/emqx_gateway_schema.erl

@@ -365,8 +365,7 @@ fields(ssl_server_opts) ->
         #{
             depth => 10,
             reuse_sessions => true,
-            versions => tls_all_available,
-            ciphers => tls_all_available
+            versions => tls_all_available
         },
         true
     );
@@ -502,8 +501,7 @@ fields(dtls_opts) ->
         #{
             depth => 10,
             reuse_sessions => true,
-            versions => dtls_all_available,
-            ciphers => dtls_all_available
+            versions => dtls_all_available
         },
         false
     ).

+ 6 - 8
apps/emqx_gateway/src/emqx_gateway_utils.erl

@@ -455,14 +455,12 @@ esockd_access_rules(StrRules) ->
     [Access(R) || R <- StrRules].
 
 ssl_opts(Name, Opts) ->
-    maps:to_list(
-        emqx_tls_lib:drop_tls13_for_old_otp(
-            maps:without(
-                [enable],
-                maps:get(Name, Opts, #{})
-            )
-        )
-    ).
+    Type =
+        case Name of
+            ssl -> tls;
+            dtls -> dtls
+        end,
+    emqx_tls_lib:to_server_opts(Type, maps:get(Name, Opts, #{})).
 
 sock_opts(Name, Opts) ->
     maps:to_list(

+ 2 - 2
apps/emqx_gateway/test/emqx_exproto_SUITE.erl

@@ -483,8 +483,8 @@ ssl_opts() ->
     maps:merge(
         Certs,
         #{
-            versions => emqx_tls_lib:default_versions(),
-            ciphers => emqx_tls_lib:default_ciphers(),
+            versions => emqx_tls_lib:available_versions(tls),
+            ciphers => [],
             verify => verify_peer,
             fail_if_no_peer_cert => true,
             secure_renegotiate => false,

+ 73 - 0
rel/emqx_conf.template.en.md

@@ -233,3 +233,76 @@ authentication=[{enable=true, backend="built_in_database", mechanism="password_b
 authentication=[{enable=true}]
 ```
 :::
+
+#### TLS/SSL ciphers
+
+Starting from v5.0.6, EMQX no longer pre-populate the ciphers list with a default
+set of cipher suite names.
+Instead, the default ciphers are applyed at runtime when starting the listener
+for servers, or when establishing a TLS connection as a client.
+
+Below are the default ciphers selected by EMQX.
+
+For tlsv1.3:
+```
+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"
+  ]
+```
+
+For tlsv1.2 or earlier
+
+```
+ciphers =
+  [ "ECDHE-ECDSA-AES256-GCM-SHA384",
+    "ECDHE-RSA-AES256-GCM-SHA384",
+    "ECDHE-ECDSA-AES256-SHA384",
+    "ECDHE-RSA-AES256-SHA384",
+    "ECDH-ECDSA-AES256-GCM-SHA384",
+    "ECDH-RSA-AES256-GCM-SHA384",
+    "ECDH-ECDSA-AES256-SHA384",
+    "ECDH-RSA-AES256-SHA384",
+    "DHE-DSS-AES256-GCM-SHA384",
+    "DHE-DSS-AES256-SHA256",
+    "AES256-GCM-SHA384",
+    "AES256-SHA256",
+    "ECDHE-ECDSA-AES128-GCM-SHA256",
+    "ECDHE-RSA-AES128-GCM-SHA256",
+    "ECDHE-ECDSA-AES128-SHA256",
+    "ECDHE-RSA-AES128-SHA256",
+    "ECDH-ECDSA-AES128-GCM-SHA256",
+    "ECDH-RSA-AES128-GCM-SHA256",
+    "ECDH-ECDSA-AES128-SHA256",
+    "ECDH-RSA-AES128-SHA256",
+    "DHE-DSS-AES128-GCM-SHA256",
+    "DHE-DSS-AES128-SHA256",
+    "AES128-GCM-SHA256",
+    "AES128-SHA256",
+    "ECDHE-ECDSA-AES256-SHA",
+    "ECDHE-RSA-AES256-SHA",
+    "DHE-DSS-AES256-SHA",
+    "ECDH-ECDSA-AES256-SHA",
+    "ECDH-RSA-AES256-SHA",
+    "ECDHE-ECDSA-AES128-SHA",
+    "ECDHE-RSA-AES128-SHA",
+    "DHE-DSS-AES128-SHA",
+    "ECDH-ECDSA-AES128-SHA",
+    "ECDH-RSA-AES128-SHA"
+  ]
+```
+
+For PSK enabled listeners
+
+```
+ciphers =
+  [ "RSA-PSK-AES256-GCM-SHA384",
+    "RSA-PSK-AES256-CBC-SHA384",
+    "RSA-PSK-AES128-GCM-SHA256",
+    "RSA-PSK-AES128-CBC-SHA256",
+    "RSA-PSK-AES256-CBC-SHA",
+    "RSA-PSK-AES128-CBC-SHA"
+  ]
+```
+

+ 70 - 0
rel/emqx_conf.template.zh.md

@@ -216,3 +216,73 @@ authentication=[{enable=true, backend="built_in_database", mechanism="password_b
 authentication=[{enable=true}]
 ```
 :::
+
+#### TLS/SSL ciphers
+
+从 v5.0.6 开始 EMQX 不在配置文件中详细列出所有默认的密码套件名称。
+而是在配置文件中使用一个空列表,然后在运行时替换成默认的密码套件。
+
+下面这些密码套件是 EMQX 默认支持的:
+
+tlsv1.3:
+```
+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"
+  ]
+```
+
+tlsv1.2 或更早
+
+```
+ciphers =
+  [ "ECDHE-ECDSA-AES256-GCM-SHA384",
+    "ECDHE-RSA-AES256-GCM-SHA384",
+    "ECDHE-ECDSA-AES256-SHA384",
+    "ECDHE-RSA-AES256-SHA384",
+    "ECDH-ECDSA-AES256-GCM-SHA384",
+    "ECDH-RSA-AES256-GCM-SHA384",
+    "ECDH-ECDSA-AES256-SHA384",
+    "ECDH-RSA-AES256-SHA384",
+    "DHE-DSS-AES256-GCM-SHA384",
+    "DHE-DSS-AES256-SHA256",
+    "AES256-GCM-SHA384",
+    "AES256-SHA256",
+    "ECDHE-ECDSA-AES128-GCM-SHA256",
+    "ECDHE-RSA-AES128-GCM-SHA256",
+    "ECDHE-ECDSA-AES128-SHA256",
+    "ECDHE-RSA-AES128-SHA256",
+    "ECDH-ECDSA-AES128-GCM-SHA256",
+    "ECDH-RSA-AES128-GCM-SHA256",
+    "ECDH-ECDSA-AES128-SHA256",
+    "ECDH-RSA-AES128-SHA256",
+    "DHE-DSS-AES128-GCM-SHA256",
+    "DHE-DSS-AES128-SHA256",
+    "AES128-GCM-SHA256",
+    "AES128-SHA256",
+    "ECDHE-ECDSA-AES256-SHA",
+    "ECDHE-RSA-AES256-SHA",
+    "DHE-DSS-AES256-SHA",
+    "ECDH-ECDSA-AES256-SHA",
+    "ECDH-RSA-AES256-SHA",
+    "ECDHE-ECDSA-AES128-SHA",
+    "ECDHE-RSA-AES128-SHA",
+    "DHE-DSS-AES128-SHA",
+    "ECDH-ECDSA-AES128-SHA",
+    "ECDH-RSA-AES128-SHA"
+  ]
+```
+
+配置 PSK 认证的监听器
+
+```
+ciphers = [
+  [ "RSA-PSK-AES256-GCM-SHA384",
+    "RSA-PSK-AES256-CBC-SHA384",
+    "RSA-PSK-AES128-GCM-SHA256",
+    "RSA-PSK-AES128-CBC-SHA256",
+    "RSA-PSK-AES256-CBC-SHA",
+    "RSA-PSK-AES128-CBC-SHA"
+  ]
+```