File 0758-ssl-Allow-any-03-XX-TLS-record-version-in-client-hel.patch of Package erlang
From d70a53ced9a46af43bb42213e9b5706857620698 Mon Sep 17 00:00:00 2001
From: Ingela Anderton Andin <ingela@erlang.org>
Date: Thu, 18 Nov 2021 12:19:35 +0100
Subject: [PATCH 1/2] ssl: Allow any {03,XX} TLS record version in client hello
for maximum interoperability
closes #5380
---
lib/ssl/src/tls_gen_connection.erl | 24 ++-
lib/ssl/test/Makefile | 1 +
lib/ssl/test/ssl_reject_SUITE.erl | 275 +++++++++++++++++++++++++++++
3 files changed, 294 insertions(+), 6 deletions(-)
create mode 100644 lib/ssl/test/ssl_reject_SUITE.erl
diff --git a/lib/ssl/src/tls_gen_connection.erl b/lib/ssl/src/tls_gen_connection.erl
index 59d30d7cb0..1d2bc122ef 100644
--- a/lib/ssl/src/tls_gen_connection.erl
+++ b/lib/ssl/src/tls_gen_connection.erl
@@ -442,15 +442,23 @@ encode_alert(#alert{} = Alert, Version, ConnectionStates) ->
send_alert(Alert, #state{static_env = #static_env{socket = Socket,
transport_cb = Transport},
- connection_env = #connection_env{negotiated_version = Version},
- ssl_options = #{log_level := LogLevel},
+ connection_env = #connection_env{negotiated_version = Version0},
+ ssl_options = #{log_level := LogLevel,
+ versions := Versions},
connection_states = ConnectionStates0} = StateData0) ->
+ Version = available_version(Version0, Versions),
{BinMsg, ConnectionStates} =
encode_alert(Alert, Version, ConnectionStates0),
tls_socket:send(Transport, Socket, BinMsg),
ssl_logger:debug(LogLevel, outbound, 'record', BinMsg),
StateData0#state{connection_states = ConnectionStates}.
+available_version(undefined, Versions) ->
+ [Version| _] = lists:reverse(Versions),
+ Version;
+available_version(NegotiatedVersion, _) ->
+ NegotiatedVersion.
+
%% If an ALERT sent in the connection state, should cause the TLS
%% connection to end, we need to synchronize with the tls_sender
%% process so that the ALERT if possible (that is the tls_sender process is
@@ -539,13 +547,17 @@ next_tls_record(Data, StateName,
%% After the version is negotiated all subsequent TLS records shall have
%% the proper legacy_record_version (= negotiated_version).
%% Note: TLS record version {3,4} is used internally in TLS 1.3 and at this
- %% point it is the same as the negotiated protocol version.
- %% TODO: Refactor state machine and introduce a record_protocol_version beside
- %% the negotiated_version.
+ %% point it is the same as the negotiated protocol version. TLS-1.3
+ %% uses TLS-1.2 as record version.
case StateName of
State when State =:= hello orelse
State =:= start ->
- [tls_record:protocol_version(Vsn) || Vsn <- ?ALL_AVAILABLE_VERSIONS];
+ %% Allow any {03,XX} TLS record version for the hello message
+ %% for maximum interopability and compliance with TLS-1.2 spec.
+ %% This does not allow SSL-3.0 connections, that we do not support
+ %% or interfere with TLS-1.3 extensions to handle version negotiation.
+ AllHelloVersions = [ 'sslv3' | ?ALL_AVAILABLE_VERSIONS],
+ [tls_record:protocol_version(Vsn) || Vsn <- AllHelloVersions];
_ ->
State0#state.connection_env#connection_env.negotiated_version
end,
diff --git a/lib/ssl/test/Makefile b/lib/ssl/test/Makefile
index e37fc04dee..3866e275ec 100644
--- a/lib/ssl/test/Makefile
+++ b/lib/ssl/test/Makefile
@@ -56,6 +56,7 @@ MODULES = \
openssl_sni_SUITE\
ssl_mfl_SUITE\
openssl_mfl_SUITE\
+ ssl_reject_SUITE\
ssl_renegotiate_SUITE\
openssl_renegotiate_SUITE\
openssl_reject_SUITE\
diff --git a/lib/ssl/test/ssl_reject_SUITE.erl b/lib/ssl/test/ssl_reject_SUITE.erl
new file mode 100644
index 0000000000..9ac7c47d66
--- /dev/null
+++ b/lib/ssl/test/ssl_reject_SUITE.erl
@@ -0,0 +1,275 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2021-2021. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+%%
+
+-module(ssl_reject_SUITE).
+
+-include_lib("common_test/include/ct.hrl").
+-include_lib("ssl/src/ssl_record.hrl").
+-include_lib("ssl/src/ssl_alert.hrl").
+-include_lib("ssl/src/ssl_handshake.hrl").
+
+%% Common test
+-export([all/0,
+ groups/0,
+ init_per_suite/1,
+ init_per_group/2,
+ init_per_testcase/2,
+ end_per_suite/1,
+ end_per_group/2,
+ end_per_testcase/2
+ ]).
+
+%% Test cases
+-export([reject_sslv2/0,
+ reject_sslv2/1,
+ reject_sslv3/0,
+ reject_sslv3/1,
+ accept_sslv3_record_hello/0,
+ accept_sslv3_record_hello/1
+ ]).
+
+-define(TLS_MAJOR, 3).
+-define(SSL_3_0_MAJOR, 3).
+-define(SSL_3_0_MINOR, 0).
+-define(TLS_1_0_MINOR, 1).
+-define(TLS_1_1_MINOR, 2).
+-define(TLS_1_2_MINOR, 3).
+-define(TLS_1_3_MINOR, 4).
+-define(SSL_2_0_MAJOR, 0).
+-define(SSL_2_0_MINOR, 1).
+
+%%--------------------------------------------------------------------
+%% Common Test interface functions -----------------------------------
+%%--------------------------------------------------------------------
+
+all() ->
+ [
+ {group, 'tlsv1.3'},
+ {group, 'tlsv1.2'},
+ {group, 'tlsv1.1'},
+ {group, 'tlsv1'}
+ ].
+
+groups() ->
+ [{'tlsv1.3', [], all_versions_tests()},
+ {'tlsv1.2', [], all_versions_tests()},
+ {'tlsv1.1', [], all_versions_tests()},
+ {'tlsv1', [], all_versions_tests()}
+ ].
+
+all_versions_tests() ->
+ [
+ reject_sslv2,
+ reject_sslv3,
+ accept_sslv3_record_hello
+ ].
+
+init_per_suite(Config0) ->
+ catch crypto:stop(),
+ try crypto:start() of
+ ok ->
+ ssl_test_lib:clean_start(),
+ ssl_test_lib:make_rsa_cert(Config0)
+ catch _:_ ->
+ {skip, "Crypto did not start"}
+ end.
+
+end_per_suite(_Config) ->
+ ssl:stop(),
+ application:stop(crypto),
+ application:stop(ssl).
+
+init_per_group(GroupName, Config) ->
+ ssl_test_lib:init_per_group(GroupName, Config).
+
+end_per_group(GroupName, Config) ->
+ ssl_test_lib:end_per_group(GroupName, Config).
+
+init_per_testcase(_TestCase, Config) ->
+ ct:timetrap({seconds, 10}),
+ Config.
+
+end_per_testcase(_, Config) ->
+ Config.
+
+%%--------------------------------------------------------------------
+%% Test cases -----------------------------------
+%%--------------------------------------------------------------------
+
+reject_sslv2() ->
+ [{doc,"Test that SSL v2 clients are rejected"}].
+
+reject_sslv2(Config) when is_list(Config) ->
+ ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
+
+ {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+
+ Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {options, ServerOpts}]),
+ Port = ssl_test_lib:inet_port(Server),
+
+ %% SSL-2.0 Hello
+ ClientHello = <<128,43,?CLIENT_HELLO, ?SSL_2_0_MAJOR, ?SSL_2_0_MINOR,
+ 0,18,0,0,0,16,7,0,192,3,0,128,1,0,128,6,0,64,4,0,
+ 128,2,0,128,115,245,33,148,17,175,69,226,204,214,132,216,182,
+ 41,238,196>>,
+
+ {ok, Socket} = gen_tcp:connect(Hostname, Port, [{active, false}]),
+
+ gen_tcp:send(Socket, ClientHello),
+
+ %% v2 is considered total garbage
+ ssl_test_lib:check_server_alert(Server, unexpected_message),
+ client_rejected(Socket, unexpected_message).
+
+reject_sslv3() ->
+ [{doc,"Test that SSL v3 clients are rejected"}].
+
+reject_sslv3(Config) when is_list(Config) ->
+ Version = proplists:get_value(version, Config),
+ ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
+ {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+
+ Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {options, ServerOpts}]),
+ Port = ssl_test_lib:inet_port(Server),
+
+ %% SSL-3.0 Hello
+ ClientHello =
+ <<?HANDSHAKE, ?SSL_3_0_MAJOR, ?SSL_3_0_MINOR,0,162, ?CLIENT_HELLO, 0,0,158,
+ ?TLS_MAJOR, ?SSL_3_0_MINOR, 97,160,130,59,226,182,64,143,134,112,117,
+ 64,10,57,164,101,182,215,0,199,145,232,172,194,45,242,48,176,5,153,
+ 101,54,0,0,26,0,255,192,10,192,20,192,5,192,15,192,9,192,19,192,4,192,
+ 14,0,57,0,56,0,51,0,50,1,0,0,91,0,0,0,19,0,17,0,0,14,119,119,119,46,101,
+ 114,108,97,110,103,46,111,114,103,0,10,0,58,0,56,0,14,0,13,0,25,0,28,0,
+ 11,0,12,0,27,0,24,0,9,0,10,0,26,0,22,0,23,0,8,0,6,0,7,0,20,0,21,0,4,0,5,
+ 0,18,0,19,0,1,0,2,0,3,0,15,0,16,0,17,0,11,0,2,1,0>>,
+
+ {ok, Socket} = gen_tcp:connect(Hostname, Port, [{active, false}]),
+ gen_tcp:send(Socket, ClientHello),
+ %% v3 is not a supported protocol version (but hello record could have 3.0 for legacy interop)
+ case Version of
+ 'tlsv1.3' ->
+ ssl_test_lib:check_server_alert(Server, illegal_parameter),
+ client_rejected(Socket, illegal_parameter);
+ _ ->
+ ssl_test_lib:check_server_alert(Server, protocol_version),
+ client_rejected(Socket, protocol_version)
+ end.
+
+accept_sslv3_record_hello() ->
+ [{doc,"Test that ssl v3 record in clients hellos are ignored when higher version are advertised"}].
+
+accept_sslv3_record_hello(Config) when is_list(Config) ->
+ Version = proplists:get_value(version, Config),
+ ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
+ {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+
+ Allversions = all_versions(),
+
+ Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {options, [{versions, Allversions} | ServerOpts]}]),
+ Port = ssl_test_lib:inet_port(Server),
+
+ %% TLS-1.X Hello with SSL-3.0 record version
+ ClientHello = hello_with_3_0_record(Version),
+
+ {ok, Socket} = gen_tcp:connect(Hostname, Port, [{active, false}]),
+ gen_tcp:send(Socket, ClientHello),
+ case gen_tcp:recv(Socket, 3, 5000) of
+ %% Minor needs to be a TLS version that is a version
+ %% above SSL-3.0
+ {ok, [?HANDSHAKE, ?TLS_MAJOR, Minor]} when Minor > ?SSL_3_0_MINOR ->
+ ok;
+ {error, timout} ->
+ ct:fail(ssl3_record_not_accepted)
+ end.
+%%--------------------------------------------------------------------
+%% Internal functions -----------------------------------
+%%--------------------------------------------------------------------
+
+all_versions() ->
+ [Version || Version <- ['tlsv1.3', 'tlsv1.2', 'tlsv1.1', tlsv1],
+ ssl_test_lib:sufficient_crypto_support(Version)].
+
+client_rejected(Socket, Alert) ->
+ Num = alert_num(Alert),
+ case gen_tcp:recv(Socket, 7, 6000) of
+ {ok,[?ALERT, _Major, _Minor, _Len1, _Len2, _Level, Num]} ->
+ ok;
+ Other ->
+ ct:fail(Other)
+ end.
+
+alert_num(unexpected_message) ->
+ ?UNEXPECTED_MESSAGE;
+alert_num(illegal_parameter) ->
+ ?ILLEGAL_PARAMETER;
+alert_num(protocol_version) ->
+ ?PROTOCOL_VERSION.
+
+hello_with_3_0_record('tlsv1') ->
+ <<?HANDSHAKE, ?TLS_MAJOR, ?SSL_3_0_MINOR, 0,162, ?CLIENT_HELLO,0,0,158,
+ ?TLS_MAJOR, ?TLS_1_0_MINOR, 97,160,130,59,226,182,64,143,134,112,117,
+ 64,10,57,164,101,182,215,0,199,145,232,172,194,45,242,48,176,5,153,
+ 101,54,0,0,26,0,255,192,10,192,20,192,5,192,15,192,9,192,19,192,4,192,
+ 14,0,57,0,56,0,51,0,50,1,0,0,91,0,0,0,19,0,17,0,0,14,119,119,119,46,101,
+ 114,108,97,110,103,46,111,114,103,0,10,0,58,0,56,0,14,0,13,0,25,0,28,0,
+ 11,0,12,0,27,0,24,0,9,0,10,0,26,0,22,0,23,0,8,0,6,0,7,0,20,0,21,0,4,0,5,
+ 0,18,0,19,0,1,0,2,0,3,0,15,0,16,0,17,0,11,0,2,1,0>>;
+hello_with_3_0_record('tlsv1.1') ->
+ <<?HANDSHAKE, ?TLS_MAJOR, ?SSL_3_0_MINOR, 0,162, ?CLIENT_HELLO, 0,0,158,
+ ?TLS_MAJOR, ?TLS_1_1_MINOR, 97,160,130,59,226,182,64,143,134,112,117,
+ 64,10,57,164,101,182,215,0,199,145,232,172,194,45,242,48,176,5,153,
+ 101,54,0,0,26,0,255,192,10,192,20,192,5,192,15,192,9,192,19,192,4,192,
+ 14,0,57,0,56,0,51,0,50,1,0,0,91,0,0,0,19,0,17,0,0,14,119,119,119,46,101,
+ 114,108,97,110,103,46,111,114,103,0,10,0,58,0,56,0,14,0,13,0,25,0,28,0,
+ 11,0,12,0,27,0,24,0,9,0,10,0,26,0,22,0,23,0,8,0,6,0,7,0,20,0,21,0,4,0,5,
+ 0,18,0,19,0,1,0,2,0,3,0,15,0,16,0,17,0,11,0,2,1,0>>;
+hello_with_3_0_record('tlsv1.2') ->
+ <<?HANDSHAKE, ?TLS_MAJOR, ?SSL_3_0_MINOR, 0,252, ?CLIENT_HELLO, 0,0,248,
+ ?TLS_MAJOR, ?TLS_1_2_MINOR, 97,160,7,242,30,221,248,238,18,3,225,13,40,18,16,117,30,
+ 159,250,156,175,90,184,65,177,226,217,125,205,227,110,154,0,0,88,0,255,
+ 192,44,192,48,192,173,192,175,192,36,192,40,204,169,204,168,192,43,192,
+ 47,192,172,192,174,192,46,192,50,192,38,192,42,192,45,192,49,192,35,192,
+ 39,192,37,192,41,0,159,0,163,0,107,0,106,0,158,0,162,204,170,0,103,0,64,
+ 192,10,192,20,192,5,192,15,192,9,192,19,192,4,192,14,0,57,0,56,0,51,0,50,
+ 1,0,0,119,0,0,0,19,0,17,0,0,14,119,119,119,46,101,114,108,97,110,103,46,
+ 111,114,103,0,13,0,24,0,22,6,3,6,1,5,3,5,1,4,3,4,1,3,3,3,1,2,3,2,1,2,2,0,
+ 10,0,58,0,56,0,14,0,13,0,25,0,28,0,11,0,12,0,27,0,24,0,9,0,10,0,26,0,22,
+ 0,23,0,8,0,6,0,7,0,20,0,21,0,4,0,5,0,18,0,19,0,1,0,2,0,3,0,15,0,16,0,17,
+ 0,11,0,2,1,0>>;
+hello_with_3_0_record('tlsv1.3') ->
+ <<?HANDSHAKE, ?TLS_MAJOR, ?SSL_3_0_MINOR, 0,219,?CLIENT_HELLO, 0,0,215,
+ ?TLS_MAJOR, ?TLS_1_2_MINOR, %% TLS_1.3 has LEGACY version TLS-1.2 here
+ 97,160,140,70,177,254,168,106,75,198,216,169,71,146,133,144,28,135,
+ 35,26,222,109,13,169,12,61,229,79,110,238,192,242,32,179,104,86,13,116,
+ 85,208,242,78,97,216,13,252,63,99,225,0,237,43,221,117,25,238,128,174,
+ 158,218,232,249,211,93,96,0,12,0,255,19,2,19,1,19,3,19,4,19,5,1,0,0,
+ 130,0,0,0,19,0,17,0,0,14,119,119,119,46,101,114,108,97,110,103,46,111,
+ 114,103,0,13,0,34,0,32,6,3,5,3,4,3,8,11,8,10,8,9,8,6,8,5,8,4,8,7,8,8,6,
+ 1,5,1,4,1,2,3,2,1,0,51,0,38,0,36,0,29,0,32,171,47,137,226,39,16,202,89,
+ 42,42,32,73,84,134,110,74,110,163,140,111,177,126,133,118,141,2,153,
+ 156,157,205,101,69,0,10,0,10,0,8,0,29,0,30,0,23,0,24,0,11,0,2,1,0,0,
+ ?SUPPORTED_VERSIONS_EXT,0,3,2,?TLS_MAJOR, ?TLS_1_3_MINOR>>.
--
2.31.1