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

Merge pull request #5811 from zmstone/fix-dynamic-resolution-for-tlsv1.3

fix(schema): check tlsv1.3 availability
Zaiming (Stone) Shi 4 лет назад
Родитель
Сommit
7c7892f096

Разница между файлами не показана из-за своего большого размера
+ 2 - 2
apps/emqx/etc/emqx.conf


+ 38 - 21
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.<br>
 NOTE: PSK ciphers are suppresed by 'tlsv1.3' version config<br>
 In case PSK cipher suites are intended, make sure to configured
 <code>['tlsv1.2', 'tlsv1.1']</code> 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.<br>
 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 <br>
 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
 <code>\"TLS_AES_256_GCM_SHA384,TLS_AES_128_GCM_SHA256\"</code> or
@@ -1146,24 +1150,24 @@ RSA-PSK-DES-CBC3-SHA,RSA-PSK-RC4-SHA\"</code><br>
        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())).

+ 10 - 6
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([]) -> [];

+ 11 - 4
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.

+ 2 - 2
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() ->