aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorFred Hebert <[email protected]>2015-06-02 10:51:22 -0400
committerFred Hebert <[email protected]>2015-06-03 11:58:41 -0400
commit916226427901f40d695d0d7d91106b0188900055 (patch)
tree73be5bfe291c530a46dd6486a07724c287d80087
parent21b8941d83516e381000387c47758bc7f040ae8b (diff)
downloadotp-916226427901f40d695d0d7d91106b0188900055.tar.gz
otp-916226427901f40d695d0d7d91106b0188900055.tar.bz2
otp-916226427901f40d695d0d7d91106b0188900055.zip
Add disable client-initiated renegotiation option
Client-initiated renegotiation is more costly for the server than the client, and this feature can be abused in denial of service attempts. Although the ssl application already takes counter-measure for these (via cooldown periods between renegotiations), it can be useful to disable the feature entirely. This patch adds the `{client_renegotiation, boolean()}' option to the server-side of the SSL application (defaulting to `true' to be compatible with the current behaviour). The option disables the ability to do any renegotiation at all in the protocol's state, reusing the existing denial code, but without opening the code path that sets up a timed message to eventually reopen it up.
-rw-r--r--lib/ssl/doc/src/ssl.xml12
-rw-r--r--lib/ssl/src/dtls_connection.erl1
-rw-r--r--lib/ssl/src/ssl.erl11
-rw-r--r--lib/ssl/src/ssl_internal.hrl1
-rw-r--r--lib/ssl/src/tls_connection.erl1
-rw-r--r--lib/ssl/test/ssl_basic_SUITE.erl57
6 files changed, 76 insertions, 7 deletions
diff --git a/lib/ssl/doc/src/ssl.xml b/lib/ssl/doc/src/ssl.xml
index 0c042f8571..923ecdd618 100644
--- a/lib/ssl/doc/src/ssl.xml
+++ b/lib/ssl/doc/src/ssl.xml
@@ -514,6 +514,18 @@ fun(srp, Username :: string(), UserState :: term()) ->
using <c>negotiated_next_protocol/1</c> method.
</item>
+ <tag>{client_renegotiation, boolean()}</tag>
+ <item>In protocols that support client-initiated renegotiation, the cost
+ of resources of such an operation is higher for the server than the
+ client. This can act as a vector for denial of service attacks. The SSL
+ application already takes measures to counter-act such attempts,
+ but client-initiated renegotiation can be stricly disabled by setting
+ this option to <c>false</c>. The default value is <c>true</c>.
+ Note that disabling renegotiation can result in long-lived connections
+ becoming unusable due to limits on the number of messages the underlying
+ cipher suite can encipher.
+ </item>
+
<tag>{psk_identity, string()}</tag>
<item>Specifies the server identity hint the server presents to the client.
</item>
diff --git a/lib/ssl/src/dtls_connection.erl b/lib/ssl/src/dtls_connection.erl
index 508983ddac..039495ef9e 100644
--- a/lib/ssl/src/dtls_connection.erl
+++ b/lib/ssl/src/dtls_connection.erl
@@ -513,6 +513,7 @@ initial_state(Role, Host, Port, Socket, {SSLOptions, SocketOptions}, User,
user_data_buffer = <<>>,
session_cache_cb = SessionCacheCb,
renegotiation = {false, first},
+ allow_renegotiate = SSLOptions#ssl_options.client_renegotiation,
start_or_recv_from = undefined,
send_queue = queue:new(),
protocol_cb = ?MODULE
diff --git a/lib/ssl/src/ssl.erl b/lib/ssl/src/ssl.erl
index 5f4ad7f013..a141426d75 100644
--- a/lib/ssl/src/ssl.erl
+++ b/lib/ssl/src/ssl.erl
@@ -645,6 +645,7 @@ handle_options(Opts0) ->
reuse_session = handle_option(reuse_session, Opts, ReuseSessionFun),
reuse_sessions = handle_option(reuse_sessions, Opts, true),
secure_renegotiate = handle_option(secure_renegotiate, Opts, false),
+ client_renegotiation = handle_option(client_renegotiation, Opts, true),
renegotiate_at = handle_option(renegotiate_at, Opts, ?DEFAULT_RENEGOTIATE_AT),
hibernate_after = handle_option(hibernate_after, Opts, undefined),
erl_dist = handle_option(erl_dist, Opts, false),
@@ -667,9 +668,9 @@ handle_options(Opts0) ->
depth, cert, certfile, key, keyfile,
password, cacerts, cacertfile, dh, dhfile,
user_lookup_fun, psk_identity, srp_identity, ciphers,
- reuse_session, reuse_sessions, ssl_imp,
- cb_info, renegotiate_at, secure_renegotiate, hibernate_after,
- erl_dist, next_protocols_advertised,
+ reuse_session, reuse_sessions, ssl_imp, cb_info,
+ renegotiate_at, secure_renegotiate, client_renegotiation,
+ hibernate_after, erl_dist, next_protocols_advertised,
client_preferred_next_protocols, log_alert,
server_name_indication, honor_cipher_order, padding_check,
fallback],
@@ -796,6 +797,8 @@ validate_option(reuse_sessions, Value) when is_boolean(Value) ->
validate_option(secure_renegotiate, Value) when is_boolean(Value) ->
Value;
+validate_option(client_renegotiation, Value) when is_boolean(Value) ->
+ Value;
validate_option(renegotiate_at, Value) when is_integer(Value) ->
erlang:min(Value, ?DEFAULT_RENEGOTIATE_AT);
@@ -1128,6 +1131,8 @@ new_ssl_options([{renegotiate_at, Value} | Rest], #ssl_options{} = Opts, RecordC
new_ssl_options(Rest, Opts#ssl_options{ renegotiate_at = validate_option(renegotiate_at, Value)}, RecordCB);
new_ssl_options([{secure_renegotiate, Value} | Rest], #ssl_options{} = Opts, RecordCB) ->
new_ssl_options(Rest, Opts#ssl_options{secure_renegotiate = validate_option(secure_renegotiate, Value)}, RecordCB);
+new_ssl_options([{client_renegotiation, Value} | Rest], #ssl_options{} = Opts, RecordCB) ->
+ new_ssl_options(Rest, Opts#ssl_options{client_renegotiation = validate_option(client_renegotiation, Value)}, RecordCB);
new_ssl_options([{hibernate_after, Value} | Rest], #ssl_options{} = Opts, RecordCB) ->
new_ssl_options(Rest, Opts#ssl_options{hibernate_after = validate_option(hibernate_after, Value)}, RecordCB);
new_ssl_options([{next_protocols_advertised, Value} | Rest], #ssl_options{} = Opts, RecordCB) ->
diff --git a/lib/ssl/src/ssl_internal.hrl b/lib/ssl/src/ssl_internal.hrl
index 88105cac5a..a506fdf0a1 100644
--- a/lib/ssl/src/ssl_internal.hrl
+++ b/lib/ssl/src/ssl_internal.hrl
@@ -105,6 +105,7 @@
reuse_sessions :: boolean(),
renegotiate_at,
secure_renegotiate,
+ client_renegotiation,
%% undefined if not hibernating, or number of ms of
%% inactivity after which ssl_connection will go into
%% hibernation
diff --git a/lib/ssl/src/tls_connection.erl b/lib/ssl/src/tls_connection.erl
index 77d3aa7889..8abe73c585 100644
--- a/lib/ssl/src/tls_connection.erl
+++ b/lib/ssl/src/tls_connection.erl
@@ -384,6 +384,7 @@ initial_state(Role, Host, Port, Socket, {SSLOptions, SocketOptions, Tracker}, Us
user_data_buffer = <<>>,
session_cache_cb = SessionCacheCb,
renegotiation = {false, first},
+ allow_renegotiate = SSLOptions#ssl_options.client_renegotiation,
start_or_recv_from = undefined,
send_queue = queue:new(),
protocol_cb = ?MODULE,
diff --git a/lib/ssl/test/ssl_basic_SUITE.erl b/lib/ssl/test/ssl_basic_SUITE.erl
index df9432a43b..4d5966fed9 100644
--- a/lib/ssl/test/ssl_basic_SUITE.erl
+++ b/lib/ssl/test/ssl_basic_SUITE.erl
@@ -162,7 +162,8 @@ renegotiate_tests() ->
client_no_wrap_sequence_number,
server_no_wrap_sequence_number,
renegotiate_dos_mitigate_active,
- renegotiate_dos_mitigate_passive].
+ renegotiate_dos_mitigate_passive,
+ renegotiate_dos_mitigate_absolute].
cipher_tests() ->
[cipher_suites,
@@ -2955,8 +2956,36 @@ renegotiate_dos_mitigate_passive(Config) when is_list(Config) ->
ssl_test_lib:close(Client).
%%--------------------------------------------------------------------
+renegotiate_dos_mitigate_absolute() ->
+ [{doc, "Mitigate DOS computational attack by not allowing client to initiate renegotiation"}].
+renegotiate_dos_mitigate_absolute(Config) when is_list(Config) ->
+ ServerOpts = ?config(server_opts, Config),
+ ClientOpts = ?config(client_opts, Config),
+
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+
+ Server =
+ ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {ssl_test_lib, send_recv_result_active, []}},
+ {options, [{client_renegotiation, false} | ServerOpts]}]),
+ Port = ssl_test_lib:inet_port(Server),
+
+ Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
+ {host, Hostname},
+ {from, self()},
+ {mfa, {?MODULE,
+ renegotiate_rejected,
+ []}},
+ {options, ClientOpts}]),
+
+ ssl_test_lib:check_result(Client, ok, Server, ok),
+ ssl_test_lib:close(Server),
+ ssl_test_lib:close(Client).
+
+%%--------------------------------------------------------------------
tcp_error_propagation_in_active_mode() ->
- [{doc,"Test that process recives {ssl_error, Socket, closed} when tcp error ocurres"}].
+ [{doc,"Test that process recives {ssl_error, Socket, closed} when tcp error occurs"}].
tcp_error_propagation_in_active_mode(Config) when is_list(Config) ->
ClientOpts = ?config(client_opts, Config),
ServerOpts = ?config(server_opts, Config),
@@ -3390,12 +3419,12 @@ renegotiate_reuse_session(Socket, Data) ->
renegotiate(Socket, Data).
renegotiate_immediately(Socket) ->
- receive
+ receive
{ssl, Socket, "Hello world"} ->
ok;
%% Handle 1/n-1 splitting countermeasure Rizzo/Duong-Beast
{ssl, Socket, "H"} ->
- receive
+ receive
{ssl, Socket, "ello world"} ->
ok
end
@@ -3407,6 +3436,26 @@ renegotiate_immediately(Socket) ->
ct:log("Renegotiated again"),
ssl:send(Socket, "Hello world"),
ok.
+
+renegotiate_rejected(Socket) ->
+ receive
+ {ssl, Socket, "Hello world"} ->
+ ok;
+ %% Handle 1/n-1 splitting countermeasure Rizzo/Duong-Beast
+ {ssl, Socket, "H"} ->
+ receive
+ {ssl, Socket, "ello world"} ->
+ ok
+ end
+ end,
+ {error, renegotiation_rejected} = ssl:renegotiate(Socket),
+ {error, renegotiation_rejected} = ssl:renegotiate(Socket),
+ ct:sleep(?RENEGOTIATION_DISABLE_TIME +1),
+ {error, renegotiation_rejected} = ssl:renegotiate(Socket),
+ ct:log("Failed to renegotiate again"),
+ ssl:send(Socket, "Hello world"),
+ ok.
+
new_config(PrivDir, ServerOpts0) ->
CaCertFile = proplists:get_value(cacertfile, ServerOpts0),