diff --git a/apps/emqx/etc/emqx.conf b/apps/emqx/etc/emqx.conf index 267f9a7ec..df5ae9034 100644 --- a/apps/emqx/etc/emqx.conf +++ b/apps/emqx/etc/emqx.conf @@ -198,7 +198,7 @@ listeners.ssl.default { ssl.certfile = "{{ platform_etc_dir }}/certs/cert.pem" ssl.cacertfile = "{{ platform_etc_dir }}/certs/cacert.pem" - ssl.versions = ["tlsv1.3", "tlsv1.2", "tlsv1.1", "tlsv1"] + # ssl.versions = ["tlsv1.3", "tlsv1.2", "tlsv1.1", "tlsv1"] # TLS 1.3: "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" # TLS 1-1.2 "ECDHE-ECDSA-AES256-GCM-SHA384,ECDHE-RSA-AES256-GCM-SHA384,ECDHE-ECDSA-AES256-SHA384,ECDHE-RSA-AES256-SHA384,ECDHE-ECDSA-DES-CBC3-SHA,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,AES256-SHA,ECDHE-ECDSA-AES128-SHA,ECDHE-RSA-AES128-SHA,DHE-DSS-AES128-SHA,ECDH-ECDSA-AES128-SHA,ECDH-RSA-AES128-SHA,AES128-SHA" # PSK: "PSK-AES128-CBC-SHA,PSK-AES256-CBC-SHA,PSK-3DES-EDE-CBC-SHA,PSK-RC4-SHA" @@ -1350,7 +1350,7 @@ example_common_ssl_options { ## Default: true ssl.honor_cipher_order = true - ssl.versions = ["tlsv1.3", "tlsv1.2", "tlsv1.1", "tlsv1"] + # ssl.versions = ["tlsv1.3", "tlsv1.2", "tlsv1.1", "tlsv1"] # TLS 1.3: "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" # TLS 1-1.2 "ECDHE-ECDSA-AES256-GCM-SHA384,ECDHE-RSA-AES256-GCM-SHA384,ECDHE-ECDSA-AES256-SHA384,ECDHE-RSA-AES256-SHA384,ECDHE-ECDSA-DES-CBC3-SHA,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,AES256-SHA,ECDHE-ECDSA-AES128-SHA,ECDHE-RSA-AES128-SHA,DHE-DSS-AES128-SHA,ECDH-ECDSA-AES128-SHA,ECDH-RSA-AES128-SHA,AES128-SHA" # PSK: "PSK-AES128-CBC-SHA,PSK-AES256-CBC-SHA,PSK-3DES-EDE-CBC-SHA,PSK-RC4-SHA" diff --git a/apps/emqx/src/emqx_schema.erl b/apps/emqx/src/emqx_schema.erl index 53e9fe835..344a1aa45 100644 --- a/apps/emqx/src/emqx_schema.erl +++ b/apps/emqx/src/emqx_schema.erl @@ -637,16 +637,16 @@ fields("listener_ssl_opts") -> server_ssl_opts_schema( #{ depth => 10 , reuse_sessions => true - , versions => tcp - , ciphers => tcp_all + , versions => tls_all_available + , ciphers => tls_all_available }, false); fields("listener_wss_opts") -> server_ssl_opts_schema( #{ depth => 10 , reuse_sessions => true - , versions => tcp - , ciphers => tcp_all + , versions => tls_all_available + , ciphers => tls_all_available }, true); fields(ssl_client_opts) -> client_ssl_opts_schema(#{}); @@ -987,13 +987,14 @@ keyfile is password-protected.""" } , {"versions", sc(hoconsc:array(typerefl:atom()), - #{ default => default_tls_vsns(maps:get(versions, Defaults, tcp)) + #{ default => default_tls_vsns(maps:get(versions, Defaults, tls_all_available)) , desc => """All TLS/DTLS versions to be supported.
NOTE: PSK ciphers are suppresed by 'tlsv1.3' version config
In case PSK cipher suites are intended, make sure to configured ['tlsv1.2', 'tlsv1.1'] here. """ + , validator => fun validate_tls_versions/1 }) } , {"ciphers", ciphers_schema(D("ciphers"))} @@ -1086,7 +1087,7 @@ client_ssl_opts_schema(Defaults) -> , desc => """Specify the host name to be used in TLS Server Name Indication extension.
For instance, when connecting to \"server.example.net\", the genuine server -which accedpts the connection and performs TSL handshake may differ from the +which accedpts the connection and performs TLS handshake may differ from the host the TLS client initially connects to, e.g. when connecting to an IP address or when the host has multiple resolvable DNS records
If not specified, it will default to the host name string which is used @@ -1099,12 +1100,12 @@ verification check.""" ]. -default_tls_vsns(dtls) -> - [<<"dtlsv1.2">>, <<"dtlsv1">>]; -default_tls_vsns(tcp) -> - [<<"tlsv1.3">>, <<"tlsv1.2">>, <<"tlsv1.1">>, <<"tlsv1">>]. +default_tls_vsns(dtls_all_available) -> + proplists:get_value(available_dtls, ssl:versions()); +default_tls_vsns(tls_all_available) -> + emqx_tls_lib:default_versions(). --spec ciphers_schema(quic | dtls | tcp_all | undefined) -> hocon_schema:field_schema(). +-spec ciphers_schema(quic | dtls_all_available | tls_all_available | undefined) -> hocon_schema:field_schema(). ciphers_schema(Default) -> sc(hoconsc:array(string()), #{ default => default_ciphers(Default) @@ -1113,7 +1114,10 @@ ciphers_schema(Default) -> (Ciphers) when is_list(Ciphers) -> Ciphers end - , validator => fun validate_ciphers/1 + , validator => case Default =:= quic of + true -> undefined; %% quic has openssl statically linked + false -> fun validate_ciphers/1 + end , desc => """TLS cipher suite names separated by comma, or as an array of strings \"TLS_AES_256_GCM_SHA384,TLS_AES_128_GCM_SHA256\" or @@ -1146,24 +1150,24 @@ RSA-PSK-DES-CBC3-SHA,RSA-PSK-RC4-SHA\"
end}). default_ciphers(undefined) -> - default_ciphers(tcp_all); + default_ciphers(tls_all_available); default_ciphers(quic) -> [ "TLS_AES_256_GCM_SHA384", "TLS_AES_128_GCM_SHA256", "TLS_CHACHA20_POLY1305_SHA256" ]; -default_ciphers(tcp_all) -> +default_ciphers(tls_all_available) -> default_ciphers('tlsv1.3') ++ default_ciphers('tlsv1.2') ++ default_ciphers(psk); -default_ciphers(dtls) -> +default_ciphers(dtls_all_available) -> %% as of now, dtls does not support tlsv1.3 ciphers default_ciphers('tlsv1.2') ++ default_ciphers('psk'); default_ciphers('tlsv1.3') -> - ["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"] - ++ default_ciphers('tlsv1.2'); + case is_tlsv13_available() of + true -> ssl:cipher_suites(exclusive, 'tlsv1.3', openssl); + false -> [] + end ++ default_ciphers('tlsv1.2'); default_ciphers('tlsv1.2') -> [ "ECDHE-ECDSA-AES256-GCM-SHA384", "ECDHE-RSA-AES256-GCM-SHA384", "ECDHE-ECDSA-AES256-SHA384", "ECDHE-RSA-AES256-SHA384", @@ -1314,9 +1318,22 @@ parse_user_lookup_fun(StrConf) -> {fun Mod:Fun/3, <<>>}. validate_ciphers(Ciphers) -> - All = ssl:cipher_suites(all, 'tlsv1.3', openssl) ++ - ssl:cipher_suites(all, 'tlsv1.2', openssl), %% includes older version ciphers + All = case is_tlsv13_available() of + true -> ssl:cipher_suites(all, 'tlsv1.3', openssl); + false -> [] + end ++ ssl:cipher_suites(all, 'tlsv1.2', openssl), case lists:filter(fun(Cipher) -> not lists:member(Cipher, All) 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()), + case lists:filter(fun(V) -> not lists:member(V, AvailableVersions) end, Versions) of + [] -> ok; + Vs -> {error, {unsupported_ssl_versions, Vs}} + end. + +is_tlsv13_available() -> + lists:member('tlsv1.3', proplists:get_value(available, ssl:versions())). diff --git a/apps/emqx/src/emqx_tls_lib.erl b/apps/emqx/src/emqx_tls_lib.erl index 24a9a15cf..683166e87 100644 --- a/apps/emqx/src/emqx_tls_lib.erl +++ b/apps/emqx/src/emqx_tls_lib.erl @@ -31,9 +31,7 @@ %% @doc Returns the default supported tls versions. -spec default_versions() -> [atom()]. -default_versions() -> - OtpRelease = list_to_integer(erlang:system_info(otp_release)), - integral_versions(default_versions(OtpRelease)). +default_versions() -> available_versions(). %% @doc Validate a given list of desired tls versions. %% raise an error exception if non of them are available. @@ -51,7 +49,7 @@ integral_versions(Desired) when ?IS_STRING(Desired) -> integral_versions(Desired) when is_binary(Desired) -> integral_versions(parse_versions(Desired)); integral_versions(Desired) -> - {_, Available} = lists:keyfind(available, 1, ssl:versions()), + Available = available_versions(), case lists:filter(fun(V) -> lists:member(V, Available) end, Desired) of [] -> erlang:error(#{ reason => no_available_tls_version , desired => Desired @@ -103,11 +101,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 -> - ['tlsv1.3' | default_versions(22)]; + proplists:get_value(available, ssl:versions()); default_versions(_) -> - ['tlsv1.2', 'tlsv1.1', tlsv1]. + lists:delete('tlsv1.3', proplists:get_value(available, ssl:versions())). %% Deduplicate a list without re-ordering the elements. dedup([]) -> []; diff --git a/apps/emqx/test/emqx_schema_tests.erl b/apps/emqx/test/emqx_schema_tests.erl index 3fb0c0130..4585089e2 100644 --- a/apps/emqx/test/emqx_schema_tests.erl +++ b/apps/emqx/test/emqx_schema_tests.erl @@ -19,8 +19,8 @@ -include_lib("eunit/include/eunit.hrl"). ssl_opts_dtls_test() -> - Sc = emqx_schema:server_ssl_opts_schema(#{versions => dtls, - ciphers => dtls}, false), + Sc = emqx_schema:server_ssl_opts_schema(#{versions => dtls_all_available, + ciphers => dtls_all_available}, false), Checked = validate(Sc, #{<<"versions">> => [<<"dtlsv1.2">>, <<"dtlsv1">>]}), ?assertMatch(#{versions := ['dtlsv1.2', 'dtlsv1'], ciphers := ["ECDHE-ECDSA-AES256-GCM-SHA384" | _] @@ -73,8 +73,8 @@ bad_cipher_test() -> Sc = emqx_schema:server_ssl_opts_schema(#{}, false), Reason = {bad_ciphers, ["foo"]}, ?assertThrow({_Sc, [{validation_error, #{reason := Reason}}]}, - [validate(Sc, #{<<"versions">> => [<<"tlsv1.2">>], - <<"ciphers">> => [<<"foo">>]})]), + validate(Sc, #{<<"versions">> => [<<"tlsv1.2">>], + <<"ciphers">> => [<<"foo">>]})), ok. validate(Schema, Data0) -> @@ -95,3 +95,10 @@ ciperhs_schema_test() -> WSc = #{roots => [{ciphers, Sc}]}, ?assertThrow({_, [{validation_error, _}]}, hocon_schema:check_plain(WSc, #{<<"ciphers">> => <<"foo,bar">>})). + +bad_tls_version_test() -> + Sc = emqx_schema:server_ssl_opts_schema(#{}, false), + Reason = {unsupported_ssl_versions, [foo]}, + ?assertThrow({_Sc, [{validation_error, #{reason := Reason}}]}, + validate(Sc, #{<<"versions">> => [<<"foo">>]})), + ok. diff --git a/apps/emqx_gateway/src/emqx_gateway_schema.erl b/apps/emqx_gateway/src/emqx_gateway_schema.erl index bdc92bf57..bb0bf9dbe 100644 --- a/apps/emqx_gateway/src/emqx_gateway_schema.erl +++ b/apps/emqx_gateway/src/emqx_gateway_schema.erl @@ -193,8 +193,8 @@ fields(dtls_opts) -> emqx_schema:server_ssl_opts_schema( #{ depth => 10 , reuse_sessions => true - , versions => dtls - , ciphers => dtls + , versions => dtls_all_available + , ciphers => dtls_all_available }, false). authentication() ->