From 22f5b625315b1229831ef8311b6f272854094c5b Mon Sep 17 00:00:00 2001 From: "Zaiming (Stone) Shi" Date: Sat, 20 Aug 2022 16:38:38 +0200 Subject: [PATCH 1/5] docs: add default ciphers to document config --- rel/emqx_conf.template.en.md | 72 ++++++++++++++++++++++++++++++++++++ rel/emqx_conf.template.zh.md | 69 ++++++++++++++++++++++++++++++++++ 2 files changed, 141 insertions(+) diff --git a/rel/emqx_conf.template.en.md b/rel/emqx_conf.template.en.md index 48fa01b61..3e56ea743 100644 --- a/rel/emqx_conf.template.en.md +++ b/rel/emqx_conf.template.en.md @@ -233,3 +233,75 @@ 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" + ] +``` + diff --git a/rel/emqx_conf.template.zh.md b/rel/emqx_conf.template.zh.md index b4a56b28f..fbfa823e0 100644 --- a/rel/emqx_conf.template.zh.md +++ b/rel/emqx_conf.template.zh.md @@ -216,3 +216,72 @@ 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" + ] +``` From 8717535d323035e61856663924d7fcb33ba6494a Mon Sep 17 00:00:00 2001 From: "Zaiming (Stone) Shi" Date: Sat, 20 Aug 2022 15:31:34 +0200 Subject: [PATCH 2/5] refactor: populate ciphers list at runtime Populating ciphers list when checking schema makes the config file example and the schmea documents quite bloated --- apps/emqx/src/emqx_listeners.erl | 6 +- apps/emqx/src/emqx_schema.erl | 16 +- apps/emqx/src/emqx_tls_lib.erl | 250 +++++++++--------- apps/emqx/test/emqx_schema_tests.erl | 11 +- apps/emqx_gateway/src/emqx_gateway_schema.erl | 6 +- apps/emqx_gateway/src/emqx_gateway_utils.erl | 9 +- rel/emqx_conf.template.en.md | 1 + rel/emqx_conf.template.zh.md | 1 + 8 files changed, 136 insertions(+), 164 deletions(-) diff --git a/apps/emqx/src/emqx_listeners.erl b/apps/emqx/src/emqx_listeners.erl index 326661e75..1c9029778 100644 --- a/apps/emqx/src/emqx_listeners.erl +++ b/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(maps:get(ssl_options, Opts, #{})). tcp_opts(Opts) -> maps:to_list( diff --git a/apps/emqx/src/emqx_schema.erl b/apps/emqx/src/emqx_schema.erl index 574305a4f..035096b45 100644 --- a/apps/emqx/src/emqx_schema.erl +++ b/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}]). @@ -2060,19 +2060,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,8 +2242,8 @@ 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. diff --git a/apps/emqx/src/emqx_tls_lib.erl b/apps/emqx/src/emqx_tls_lib.erl index b08270df9..133c51338 100644 --- a/apps/emqx/src/emqx_tls_lib.erl +++ b/apps/emqx/src/emqx_tls_lib.erl @@ -23,8 +23,7 @@ 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,6 +37,7 @@ ]). -export([ + to_server_opts/1, to_client_opts/1 ]). @@ -54,6 +54,63 @@ %% non-empty list of strings -define(IS_STRING_LIST(L), (is_list(L) andalso L =/= [] andalso ?IS_STRING(hd(L)))). +%% 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 Returns the default supported tls versions. -spec default_versions() -> [atom()]. default_versions() -> available_versions(). @@ -86,8 +143,19 @@ integral_versions(Desired) -> Filtered end. +%% @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. + %% @doc Return a list of all supported ciphers. -all_ciphers() -> all_ciphers(default_versions()). +all_ciphers() -> + all_ciphers(default_versions()). %% @doc Return a list of (openssl string format) cipher suites. -spec all_ciphers([ssl:tls_version()]) -> [string()]. @@ -96,23 +164,15 @@ all_ciphers(['tlsv1.3']) -> %% 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()). %% @doc Pre-selected TLS ciphers for given versions.. selected_ciphers(Vsns) -> @@ -126,54 +186,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()]. @@ -209,9 +226,14 @@ available_versions() -> %% 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()); + availables(); default_versions(_) -> - lists:delete('tlsv1.3', proplists:get_value(available, ssl:versions())). + lists:delete('tlsv1.3', availables()). + +availables() -> + All = ssl:versions(), + proplists:get_value(available, All) ++ + proplists:get_value(available_dtls, All). %% Deduplicate a list without re-ordering the elements. dedup([]) -> @@ -244,6 +266,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 +283,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 +492,46 @@ do_drop_invalid_certs([Key | Keys], SSL) -> end end. +%% @doc Convert hocon-checked ssl server options (map()) to +%% proplist accepted by ssl library. +to_server_opts(Opts) -> + Versions = integral_versions(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 ssl client options (map()) to %% proplist accepted by ssl library. to_client_opts(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(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. diff --git a/apps/emqx/test/emqx_schema_tests.erl b/apps/emqx/test/emqx_schema_tests.erl index a40026d4c..b081ec996 100644 --- a/apps/emqx/test/emqx_schema_tests.erl +++ b/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( diff --git a/apps/emqx_gateway/src/emqx_gateway_schema.erl b/apps/emqx_gateway/src/emqx_gateway_schema.erl index dfe937024..8850fa462 100644 --- a/apps/emqx_gateway/src/emqx_gateway_schema.erl +++ b/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 ). diff --git a/apps/emqx_gateway/src/emqx_gateway_utils.erl b/apps/emqx_gateway/src/emqx_gateway_utils.erl index 15359dea6..5a38ee0f0 100644 --- a/apps/emqx_gateway/src/emqx_gateway_utils.erl +++ b/apps/emqx_gateway/src/emqx_gateway_utils.erl @@ -455,14 +455,7 @@ 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, #{}) - ) - ) - ). + emqx_tls_lib:to_server_opts(maps:get(Name, Opts, #{})). sock_opts(Name, Opts) -> maps:to_list( diff --git a/rel/emqx_conf.template.en.md b/rel/emqx_conf.template.en.md index 3e56ea743..76d25680b 100644 --- a/rel/emqx_conf.template.en.md +++ b/rel/emqx_conf.template.en.md @@ -291,6 +291,7 @@ ciphers = "ECDH-ECDSA-AES128-SHA", "ECDH-RSA-AES128-SHA" ] +``` For PSK enabled listeners diff --git a/rel/emqx_conf.template.zh.md b/rel/emqx_conf.template.zh.md index fbfa823e0..ac4c5ce39 100644 --- a/rel/emqx_conf.template.zh.md +++ b/rel/emqx_conf.template.zh.md @@ -272,6 +272,7 @@ ciphers = "ECDH-ECDSA-AES128-SHA", "ECDH-RSA-AES128-SHA" ] +``` 配置 PSK 认证的监听器 From 7851a3aefd80610e2d5b8b39689af7fd7075d11a Mon Sep 17 00:00:00 2001 From: "Zaiming (Stone) Shi" Date: Sun, 21 Aug 2022 11:27:35 +0200 Subject: [PATCH 3/5] refactor(ssl): use 'available' for defaults prior to this change, for TLS versions, 'default's are 'available's there is no need for such an alias. now we call available_versions with a specific tag: tls, dtls, or all --- apps/emqx/src/emqx_listeners.erl | 2 +- apps/emqx/src/emqx_schema.erl | 17 ++-- apps/emqx/src/emqx_tls_lib.erl | 88 ++++++++++---------- apps/emqx/test/emqx_schema_tests.erl | 2 +- apps/emqx/test/emqx_tls_lib_tests.erl | 44 ++++++---- apps/emqx_gateway/src/emqx_gateway_utils.erl | 7 +- 6 files changed, 87 insertions(+), 73 deletions(-) diff --git a/apps/emqx/src/emqx_listeners.erl b/apps/emqx/src/emqx_listeners.erl index 1c9029778..aa2a2e0f9 100644 --- a/apps/emqx/src/emqx_listeners.erl +++ b/apps/emqx/src/emqx_listeners.erl @@ -583,7 +583,7 @@ enable_authn(Opts) -> maps:get(enable_authn, Opts, true). ssl_opts(Opts) -> - emqx_tls_lib:to_server_opts(maps:get(ssl_options, Opts, #{})). + emqx_tls_lib:to_server_opts(tls, maps:get(ssl_options, Opts, #{})). tcp_opts(Opts) -> maps:to_list( diff --git a/apps/emqx/src/emqx_schema.erl b/apps/emqx/src/emqx_schema.erl index 035096b45..0e1bd2484 100644 --- a/apps/emqx/src/emqx_schema.erl +++ b/apps/emqx/src/emqx_schema.erl @@ -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(). @@ -2248,13 +2250,10 @@ validate_ciphers(Ciphers) -> 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() -> diff --git a/apps/emqx/src/emqx_tls_lib.erl b/apps/emqx/src/emqx_tls_lib.erl index 133c51338..28bda5cfa 100644 --- a/apps/emqx/src/emqx_tls_lib.erl +++ b/apps/emqx/src/emqx_tls_lib.erl @@ -18,8 +18,8 @@ %% 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, @@ -37,8 +37,9 @@ ]). -export([ - to_server_opts/1, - to_client_opts/1 + to_server_opts/2, + to_client_opts/1, + to_client_opts/2 ]). -include("logger.hrl"). @@ -111,27 +112,23 @@ "RSA-PSK-AES128-CBC-SHA" ]). -%% @doc Returns the default supported tls versions. --spec default_versions() -> [atom()]. -default_versions() -> available_versions(). - %% @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(#{ @@ -153,11 +150,11 @@ all_ciphers_set_cached() -> Set end. -%% @doc Return a list of all supported ciphers. +%% @hidden Return a list of all supported ciphers. all_ciphers() -> - all_ciphers(default_versions()). + 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 @@ -172,7 +169,7 @@ all_ciphers(Versions) -> %% @doc All Pre-selected TLS ciphers. default_ciphers() -> - selected_ciphers(available_versions()). + selected_ciphers(available_versions(all)). %% @doc Pre-selected TLS ciphers for given versions.. selected_ciphers(Vsns) -> @@ -218,22 +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). - -%% tlsv1.3 is available from OTP-22 but we do not want to use until 23. -default_versions(OtpRelease) when OtpRelease >= 23 -> - availables(); -default_versions(_) -> - lists:delete('tlsv1.3', availables()). - -availables() -> +%% @doc Returns the default available tls/dtls versions. +available_versions(Type) -> All = ssl:versions(), - proplists:get_value(available, All) ++ - proplists:get_value(available_dtls, All). + available_versions(Type, All). + +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([]) -> @@ -494,17 +486,25 @@ do_drop_invalid_certs([Key | Keys], SSL) -> %% @doc Convert hocon-checked ssl server options (map()) to %% proplist accepted by ssl library. -to_server_opts(Opts) -> - Versions = integral_versions(maps:get(versions, Opts, undefined)), +-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 ssl client options (map()) to +%% @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, case GetD(enable, false) of @@ -514,7 +514,7 @@ to_client_opts(Opts) -> CAFile = ensure_str(Get(cacertfile)), Verify = GetD(verify, verify_none), SNI = ensure_sni(Get(server_name_indication)), - Versions = integral_versions(Get(versions)), + Versions = integral_versions(Type, Get(versions)), Ciphers = integral_ciphers(Versions, Get(ciphers)), filter([ {keyfile, KeyFile}, diff --git a/apps/emqx/test/emqx_schema_tests.erl b/apps/emqx/test/emqx_schema_tests.erl index b081ec996..9118ac226 100644 --- a/apps/emqx/test/emqx_schema_tests.erl +++ b/apps/emqx/test/emqx_schema_tests.erl @@ -134,7 +134,7 @@ ciphers_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">>]}) diff --git a/apps/emqx/test/emqx_tls_lib_tests.erl b/apps/emqx/test/emqx_tls_lib_tests.erl index 22c480637..647e7c3e2 100644 --- a/apps/emqx/test/emqx_tls_lib_tests.erl +++ b/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())). + ?assert(lists:member('tlsv1.3', emqx_tls_lib:available_versions(tls))). -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]) +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() -> diff --git a/apps/emqx_gateway/src/emqx_gateway_utils.erl b/apps/emqx_gateway/src/emqx_gateway_utils.erl index 5a38ee0f0..3a6de2031 100644 --- a/apps/emqx_gateway/src/emqx_gateway_utils.erl +++ b/apps/emqx_gateway/src/emqx_gateway_utils.erl @@ -455,7 +455,12 @@ esockd_access_rules(StrRules) -> [Access(R) || R <- StrRules]. ssl_opts(Name, Opts) -> - emqx_tls_lib:to_server_opts(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( From 9a5dda010e6d4fe1d66260c14a5626f3e49a42a5 Mon Sep 17 00:00:00 2001 From: "Zaiming (Stone) Shi" Date: Sun, 21 Aug 2022 21:23:22 +0200 Subject: [PATCH 4/5] docs: update CHNAGES-5.0.md --- CHANGES-5.0.md | 4 ++++ apps/emqx_gateway/test/emqx_exproto_SUITE.erl | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGES-5.0.md b/CHANGES-5.0.md index 4856b09f1..66529953a 100644 --- a/CHANGES-5.0.md +++ b/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 diff --git a/apps/emqx_gateway/test/emqx_exproto_SUITE.erl b/apps/emqx_gateway/test/emqx_exproto_SUITE.erl index aac779c8e..66f780d3f 100644 --- a/apps/emqx_gateway/test/emqx_exproto_SUITE.erl +++ b/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, From 97856f2e42b2ac57545fbf7cffd33f6df4726bf8 Mon Sep 17 00:00:00 2001 From: "Zaiming (Stone) Shi" Date: Wed, 24 Aug 2022 21:01:37 +0200 Subject: [PATCH 5/5] fix: allow empty string as ciphers --- apps/emqx/src/emqx_schema.erl | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/emqx/src/emqx_schema.erl b/apps/emqx/src/emqx_schema.erl index 0e1bd2484..75818ab2b 100644 --- a/apps/emqx/src/emqx_schema.erl +++ b/apps/emqx/src/emqx_schema.erl @@ -2041,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) ->