aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMaria Scott <[email protected]>2021-09-01 10:31:32 +0200
committerLoïc Hoguin <[email protected]>2021-09-01 15:20:32 +0200
commitc7a7cfb9b93f0db9c99ed21c34f67e0b6adfcb62 (patch)
tree3ca3b977798cde14580dd908caa4332c4b47b509
parentfd293a343f978acc52bfa8eb8edf2ae53c91c9a3 (diff)
downloadranch-c7a7cfb9b93f0db9c99ed21c34f67e0b6adfcb62.tar.gz
ranch-c7a7cfb9b93f0db9c99ed21c34f67e0b6adfcb62.tar.bz2
ranch-c7a7cfb9b93f0db9c99ed21c34f67e0b6adfcb62.zip
Disallow unsupported options for TLSv1.3
* beast_mitigation (also disallowed for 1.1 and 1.2) * client_renegotiation * next_protocols_advertised * padding_check (also disallowed for 1.1 and 1.2) * psk_identity * reuse_session * reuse_sessions * secure_renegotiate * user_lookup_fun
-rw-r--r--src/ranch_ssl.erl43
-rw-r--r--test/acceptor_SUITE.erl66
2 files changed, 100 insertions, 9 deletions
diff --git a/src/ranch_ssl.erl b/src/ranch_ssl.erl
index bdfd2e4..dfaa75c 100644
--- a/src/ranch_ssl.erl
+++ b/src/ranch_ssl.erl
@@ -130,10 +130,12 @@ do_listen(SocketOpts0, Logger) ->
SocketOpts2 = ranch:set_option_default(SocketOpts1, nodelay, true),
SocketOpts3 = ranch:set_option_default(SocketOpts2, send_timeout, 30000),
SocketOpts = ranch:set_option_default(SocketOpts3, send_timeout_close, true),
+ DisallowedOpts0 = disallowed_listen_options(),
+ DisallowedOpts = unsupported_tls_options(SocketOpts) ++ DisallowedOpts0,
%% We set the port to 0 because it is given in the Opts directly.
%% The port in the options takes precedence over the one in the
%% first argument.
- ssl:listen(0, ranch:filter_options(SocketOpts, disallowed_listen_options(),
+ ssl:listen(0, ranch:filter_options(SocketOpts, DisallowedOpts,
[binary, {active, false}, {packet, raw}, {reuseaddr, true}], Logger)).
%% 'binary' and 'list' are disallowed but they are handled
@@ -144,6 +146,22 @@ disallowed_listen_options() ->
fallback, server_name_indication, srp_identity
|ranch_tcp:disallowed_listen_options()].
+unsupported_tls_options(SocketOpts) ->
+ unsupported_tls_version_options(lists:usort(get_tls_versions(SocketOpts))).
+
+unsupported_tls_version_options([tlsv1|_]) ->
+ [];
+unsupported_tls_version_options(['tlsv1.1'|_]) ->
+ [beast_mitigation, padding_check];
+unsupported_tls_version_options(['tlsv1.2'|_]) ->
+ [beast_mitigation, padding_check];
+unsupported_tls_version_options(['tlsv1.3'|_]) ->
+ [beast_mitigation, client_renegotiation, next_protocols_advertised,
+ padding_check, psk_identity, reuse_session, reuse_sessions,
+ secure_renegotiate, user_lookup_fun];
+unsupported_tls_version_options(_) ->
+ [].
+
-spec accept(ssl:sslsocket(), timeout())
-> {ok, ssl:sslsocket()} | {error, closed | timeout | atom()}.
accept(LSocket, Timeout) ->
@@ -296,3 +314,26 @@ cleanup(#{socket_opts:=SocketOpts}) ->
end;
cleanup(_) ->
ok.
+
+get_tls_versions(SocketOpts) ->
+ %% Socket options need to be reversed for keyfind because later options
+ %% take precedence when contained multiple times, but keyfind will return
+ %% the earliest occurence.
+ case lists:keyfind(versions, 1, lists:reverse(SocketOpts)) of
+ {versions, Versions} ->
+ Versions;
+ false ->
+ get_tls_versions_env()
+ end.
+
+get_tls_versions_env() ->
+ case application:get_env(ssl, protocol_version) of
+ {ok, Versions} ->
+ Versions;
+ undefined ->
+ get_tls_versions_app()
+ end.
+
+get_tls_versions_app() ->
+ {supported, Versions} = lists:keyfind(supported, 1, ssl:versions()),
+ Versions.
diff --git a/test/acceptor_SUITE.erl b/test/acceptor_SUITE.erl
index 1028614..335a155 100644
--- a/test/acceptor_SUITE.erl
+++ b/test/acceptor_SUITE.erl
@@ -68,7 +68,8 @@ groups() ->
ssl_many_listen_sockets_no_reuseport,
ssl_error_eaddrinuse,
ssl_error_no_cert,
- ssl_error_eacces
+ ssl_error_eacces,
+ ssl_unsupported_tlsv13_options
]}, {misc, [
misc_bad_transport,
misc_bad_transport_options,
@@ -599,13 +600,11 @@ ssl_active_echo(_) ->
ok.
ssl_active_n_echo(_) ->
- case application:get_key(ssl, vsn) of
- {ok, "9.0"++_} ->
- {skip, "No Active N support."};
- {ok, "9.1"++_} ->
- {skip, "No Active N support."};
- {ok, _} ->
- do_ssl_active_n_echo()
+ case do_get_ssl_version() >= {9, 2, 0} of
+ true ->
+ do_ssl_active_n_echo();
+ false ->
+ {skip, "No Active N support."}
end.
do_ssl_active_n_echo() ->
@@ -875,6 +874,46 @@ ssl_error_eacces(_) ->
ok
end.
+ssl_unsupported_tlsv13_options(_) ->
+ {available, Versions} = lists:keyfind(available, 1, ssl:versions()),
+ case {lists:member('tlsv1.3', Versions), do_get_ssl_version() >= {10, 0, 0}} of
+ {true, true} ->
+ do_ssl_unsupported_tlsv13_options();
+ {false, _} ->
+ {skip, "No TLSv1.3 support."};
+ {_, false} ->
+ {skip, "No TLSv1.3 option dependency checking."}
+ end.
+
+do_ssl_unsupported_tlsv13_options() ->
+ doc("Ensure that a listener can be started when TLSv1.3 is "
+ "the only protocol and unsupported options are present."),
+ CheckOpts = [
+ {beast_mitigation, one_n_minus_one},
+ {client_renegotiation, true},
+ {next_protocols_advertised, [<<"dummy">>]},
+ {padding_check, true},
+ {psk_identity, "dummy"},
+ {secure_renegotiate, true},
+ {reuse_session, fun (_, _, _, _) -> true end},
+ {reuse_sessions, true},
+ {user_lookup_fun, {fun (_, _, _) -> error end, <<"dummy">>}}
+ ],
+ Name = name(),
+ Opts = ct_helper:get_certs_from_ets() ++ [{versions, ['tlsv1.3']}],
+ ok = lists:foreach(
+ fun (CheckOpt) ->
+ Opts1 = Opts ++ [CheckOpt],
+ {error, {options, dependency, _}} = ssl:listen(0, Opts1),
+ {ok, _} = ranch:start_listener(Name,
+ ranch_ssl, #{socket_opts => Opts1},
+ echo_protocol, []),
+ ok = ranch:stop_listener(Name)
+ end,
+ CheckOpts
+ ),
+ ok.
+
%% tcp.
tcp_10_acceptors_10_listen_sockets(_) ->
@@ -1674,3 +1713,14 @@ do_os_supports_local_sockets() ->
do_tempname() ->
list_to_binary(lists:droplast(os:cmd("mktemp -u"))).
+
+do_get_ssl_version() ->
+ {ok, Vsn} = application:get_key(ssl, vsn),
+ Vsns0 = re:split(Vsn, "\\D+", [{return, list}]),
+ Vsns1 = lists:map(fun list_to_integer/1, Vsns0),
+ case Vsns1 of
+ [] -> {0, 0, 0};
+ [Major] -> {Major, 0, 0};
+ [Major, Minor] -> {Major, Minor, 0};
+ [Major, Minor, Patch|_] -> {Major, Minor, Patch}
+ end.