2
0
mirror of https://github.com/openvswitch/ovs synced 2025-08-22 01:51:26 +00:00

stream-ssl: Add explicit support for configuring TLSv1.3.

TLSv1.3 is currently only supported implicitly, if the --ssl-protocols
are not provided.  Or with the recent range support like "TLSv1.2+".
However, it is not possible to explicitly ask for TLSv1.3 or set a
custom list of ciphersuites for it.  Fix that by adding TLSv1.3 to the
list of available protocols and adding a new --ssl-ciphersuites option.

The new option is necessary, because --ssl-ciphers translates into
SSL_CTX_set_cipher_list() that configures ciphers for TLSv1.2 and
earlier.  SSL_CTX_set_ciphersuites() sets ciphersuites for TLSv1.3
and later.

Tests updated to exercise new options and to reduce the use of
deprecated TLSv1 and TLSv1.1.

TLSv1.3 support was introduced in OpenSSL 1.1.1.

Acked-by: Eelco Chaudron <echaudro@redhat.com>
Signed-off-by: Ilya Maximets <i.maximets@ovn.org>
This commit is contained in:
Ilya Maximets 2024-12-09 17:38:53 +01:00
parent 0e23c9cab8
commit 4d09d6b48e
9 changed files with 137 additions and 51 deletions

5
NEWS
View File

@ -20,6 +20,11 @@ Post-v3.4.0
- "TLSv1-TLSv1.2" to enable all protocols between TLSv1 and TLSv1.2.
- "TLSv1.2+" to enable protocol TLSv1.2 and later.
The value must be a list of protocols or exactly one protocol range.
* Added explicit support for TLSv1.3. It can now be enabled via
--ssl-protocols (TLSv1.3 was supported in earlier versions only when
this option was not set). TLS ciphersuites for TLSv1.3 and later can
be configured via --ssl-ciphersuites (--ssl-ciphers only applies to
TLSv1.2 and earlier).
* ovs-pki now generates 3072-bit keys by default.
- Userspace datapath:
* The default zone limit, if set, is now inherited by any zone

View File

@ -3,3 +3,5 @@
.br
[\fB\-\-ssl\-ciphers=\fIciphers\fR]
.br
[\fB\-\-ssl\-ciphersuites=\fIciphersuites\fR]
.br

View File

@ -2,20 +2,25 @@
Specifies a range or a comma- or space-delimited list of the SSL/TLS protocols
\fB\*(PN\fR will enable for SSL/TLS connections. Supported
\fIprotocols\fR include \fBTLSv1\fR (deprecated), \fBTLSv1.1\fR (deprecated),
and \fBTLSv1.2\fR. Ranges can be provided in a form of two protocol names
separated with a dash, or as a single protocol name with a plus sign.
For example, use \fBTLSv1-TLSv1.2\fR to allow \fBTLSv1\fR, \fBTLSv1.1\fR
and \fBTLSv1.2\fR. Use \fBTLSv1.2+\fR to allow \fBTLSv1.2\fR and any later
protocol. The option accepts a list of protocols or exactly one range.
The range is a preferred way of specifying protocols and the option always
behaves as if the range between the minimum and the maximum specified version
is provided, i.e., if the option is set to \fBTLSv1,TLSv1.2\fR, the
\fBTLSv1.1\fR will also be enabled as if it was a range.
\fBTLSv1.2\fR and \fBTLSv1.3\fR. Ranges can be provided in a form of two
protocol names separated with a dash, or as a single protocol name with a plus
sign. For example, use \fBTLSv1.1-TLSv1.3\fR to allow \fBTLSv1.1\fR,
\fBTLSv1.2\fR and \fBTLSv1.3\fR. Use \fBTLSv1.2+\fR to allow \fBTLSv1.2\fR and
any later protocol. The option accepts a list of protocols or exactly one
range. The range is a preferred way of specifying protocols and the option
always behaves as if the range between the minimum and the maximum specified
version is provided, i.e., if the option is set to \fBTLSv1.1,TLSv1.3\fR, the
\fBTLSv1.2\fR will also be enabled as if it was a range.
Regardless of order, the highest protocol supported by both sides will
be chosen when making the connection. The default when this option is
omitted is \fBTLSv1.2\fR or later.
.
.IP "\fB\-\-ssl\-ciphers=\fIciphers\fR"
Specifies, in OpenSSL cipher string format, the ciphers \fB\*(PN\fR will
support for SSL/TLS connections. The default when this option is omitted is
\fBDEFAULT:@SECLEVEL=2\fR.
Specifies, in OpenSSL cipher string format, the ciphers \fB\*(PN\fR will
support for SSL/TLS connections with TLSv1.2 and earlier. The default when
this option is omitted is \fBDEFAULT:@SECLEVEL=2\fR.
.
.IP "\fB\-\-ssl\-ciphersuites=\fIciphersuites\fR"
Specifies, in OpenSSL ciphersuite string format, the ciphersuites
\fB\*(PN\fR will support for SSL/TLS connections with TLSv1.3 and later.
Default value from OpenSSL will be used when this option is omitted.

View File

@ -89,3 +89,10 @@ stream_ssl_set_ciphers(const char *arg OVS_UNUSED)
/* Ignore this option since it seems harmless to set SSL/TLS ciphers if
* SSL/TLS won't be used. */
}
void
stream_ssl_set_ciphersuites(const char *arg OVS_UNUSED)
{
/* Ignore this option since it seems harmless to set TLS ciphersuites if
* SSL/TLS won't be used. */
}

View File

@ -166,6 +166,7 @@ static struct ssl_config_file certificate;
static struct ssl_config_file ca_cert;
static char *ssl_protocols = "TLSv1.2+";
static char *ssl_ciphers = "DEFAULT:@SECLEVEL=2";
static char *ssl_ciphersuites = ""; /* Using default ones, unless specified. */
/* Ordinarily, the SSL client and server verify each other's certificates using
* a CA certificate. Setting this to false disables this behavior. (This is a
@ -1220,8 +1221,8 @@ stream_ssl_set_key_and_cert(const char *private_key_file,
}
}
/* Sets SSL/TLS ciphers based on string input. Aborts with an error message
* if 'arg' is invalid. */
/* Sets SSL/TLS ciphers for TLSv1.2 and earlier based on string input.
* Aborts with an error message if 'arg' is not valid. */
void
stream_ssl_set_ciphers(const char *arg)
{
@ -1235,6 +1236,21 @@ stream_ssl_set_ciphers(const char *arg)
ssl_ciphers = xstrdup(arg);
}
/* Sets TLS ciphersuites for TLSv1.3 and later based on string input.
* Aborts with an error message if 'arg' is not valid. */
void
stream_ssl_set_ciphersuites(const char *arg)
{
if (ssl_init() || !arg || !strcmp(ssl_ciphersuites, arg)) {
return;
}
if (SSL_CTX_set_ciphersuites(ctx, arg) == 0) {
VLOG_ERR("SSL_CTX_set_ciphersuites: %s",
ERR_error_string(ERR_get_error(), NULL));
}
ssl_ciphersuites = xstrdup(arg);
}
/* Set SSL/TLS protocols based on the string input. Aborts with an error
* message if 'arg' is invalid. */
void
@ -1254,6 +1270,7 @@ stream_ssl_set_protocols(const char *arg)
{"TLSv1", TLS1_VERSION, true },
{"TLSv1.1", TLS1_1_VERSION, true },
{"TLSv1.2", TLS1_2_VERSION, false},
{"TLSv1.3", TLS1_3_VERSION, false},
};
char *dash = strchr(arg, '-');
bool or_later = false;

View File

@ -27,17 +27,20 @@ void stream_ssl_set_key_and_cert(const char *private_key_file,
const char *certificate_file);
void stream_ssl_set_protocols(const char *arg);
void stream_ssl_set_ciphers(const char *arg);
void stream_ssl_set_ciphersuites(const char *arg);
#define SSL_OPTION_ENUMS \
OPT_SSL_PROTOCOLS, \
OPT_SSL_CIPHERS
OPT_SSL_CIPHERS, \
OPT_SSL_CIPHERSUITES
#define STREAM_SSL_LONG_OPTIONS \
{"private-key", required_argument, NULL, 'p'}, \
{"certificate", required_argument, NULL, 'c'}, \
{"ca-cert", required_argument, NULL, 'C'}, \
{"ssl-protocols", required_argument, NULL, OPT_SSL_PROTOCOLS}, \
{"ssl-ciphers", required_argument, NULL, OPT_SSL_CIPHERS}
{"ssl-ciphers", required_argument, NULL, OPT_SSL_CIPHERS}, \
{"ssl-ciphersuites", required_argument, NULL, OPT_SSL_CIPHERSUITES}
#define STREAM_SSL_OPTION_HANDLERS \
case 'p': \
@ -58,6 +61,10 @@ void stream_ssl_set_ciphers(const char *arg);
\
case OPT_SSL_CIPHERS: \
stream_ssl_set_ciphers(optarg); \
break; \
\
case OPT_SSL_CIPHERSUITES: \
stream_ssl_set_ciphersuites(optarg); \
break;
#define STREAM_SSL_CASES \

View File

@ -159,8 +159,11 @@ stream_usage(const char *name, bool active, bool passive,
"to read or create\n");
}
printf("SSL/TLS options:\n"
" --ssl-protocols=PROTOS range of SSL/TLS protocols to enable\n"
" --ssl-ciphers=CIPHERS list of SSL/TLS ciphers to enable\n");
" --ssl-protocols=PROTOS range of SSL/TLS protocols to enable\n"
" --ssl-ciphers=CIPHERS list of SSL/TLS ciphers to enable\n"
" with TLSv1.2 and earlier\n"
" --ssl-ciphersuites=SUITES list of SSL/TLS ciphersuites to\n"
" enable with TLSv1.3 and later\n");
#endif
}

View File

@ -73,6 +73,7 @@ static char *certificate_file;
static char *ca_cert_file;
static char *ssl_protocols;
static char *ssl_ciphers;
static char *ssl_ciphersuites;
static bool bootstrap_ca_cert;
/* Try to reclaim heap memory back to system after DB compaction. */
@ -1792,17 +1793,21 @@ reconfigure_ssl(const struct shash *all_dbs)
const char *resolved_ca_cert;
const char *resolved_ssl_protocols;
const char *resolved_ssl_ciphers;
const char *resolved_ssl_ciphersuites;
resolved_private_key = query_db_string(all_dbs, private_key_file, &errors);
resolved_certificate = query_db_string(all_dbs, certificate_file, &errors);
resolved_ca_cert = query_db_string(all_dbs, ca_cert_file, &errors);
resolved_ssl_protocols = query_db_string(all_dbs, ssl_protocols, &errors);
resolved_ssl_ciphers = query_db_string(all_dbs, ssl_ciphers, &errors);
resolved_ssl_ciphersuites = query_db_string(all_dbs, ssl_ciphersuites,
&errors);
stream_ssl_set_key_and_cert(resolved_private_key, resolved_certificate);
stream_ssl_set_ca_cert_file(resolved_ca_cert, bootstrap_ca_cert);
stream_ssl_set_protocols(resolved_ssl_protocols);
stream_ssl_set_ciphers(resolved_ssl_ciphers);
stream_ssl_set_ciphersuites(resolved_ssl_ciphersuites);
return errors.string;
}
@ -2699,6 +2704,10 @@ parse_options(int argc, char *argv[],
ssl_ciphers = optarg;
break;
case OPT_SSL_CIPHERSUITES:
ssl_ciphersuites = optarg;
break;
case OPT_BOOTSTRAP_CA_CERT:
ca_cert_file = optarg;
bootstrap_ca_cert = true;

View File

@ -842,11 +842,14 @@ AT_DATA([schema],
"certificate": {"type": "string"},
"ca_cert": {"type": "string"},
"ssl_protocols" : {"type": "string"},
"ssl_ciphers" : {"type" : "string"}}}}}
"ssl_ciphers" : {"type" : "string"},
"ssl_ciphersuites" : {"type": "string"}
}}}}
]])
AT_CHECK([ovsdb-tool create db schema], [0], [stdout], [ignore])
# The !ECDHE-ECDSA-AES256-GCM-SHA384 in the ssl_ciphers is so that
# a cipher negotiation failure can be tested for later.
# a cipher negotiation failure can be tested for later. Same for
# the TLS_CHACHA20_POLY1305_SHA256 ciphersuite.
AT_CHECK(
[[ovsdb-tool transact db \
'["mydb",
@ -855,8 +858,10 @@ AT_CHECK(
"row": {"private_key": "'"$PKIDIR/testpki-privkey2.pem"'",
"certificate": "'"$PKIDIR/testpki-cert2.pem"'",
"ca_cert": "'"$PKIDIR/testpki-cacert.pem"'",
"ssl_protocols": "'"TLSv1.2,TLSv1.1"'",
"ssl_ciphers": "'"DEFAULT:@SECLEVEL=2:!ECDHE-ECDSA-AES256-GCM-SHA384"'"}}]']],
"ssl_protocols": "'"TLSv1.3,TLSv1.2"'",
"ssl_ciphers": "'"DEFAULT:@SECLEVEL=2:!ECDHE-ECDSA-AES256-GCM-SHA384"'",
"ssl_ciphersuites": "TLS_CHACHA20_POLY1305_SHA256"
}}]']],
[0], [ignore], [ignore])
on_exit 'kill `cat *.pid`'
AT_CHECK(
@ -866,11 +871,12 @@ AT_CHECK(
--ca-cert=db:mydb,SSL,ca_cert \
--ssl-protocols=db:mydb,SSL,ssl_protocols \
--ssl-ciphers=db:mydb,SSL,ssl_ciphers \
--ssl-ciphersuites=db:mydb,SSL,ssl_ciphersuites \
--remote=pssl:0:127.0.0.1 db],
[0], [ignore], [ignore])
PARSE_LISTENING_PORT([ovsdb-server.log], [SSL_PORT])
# SSL_OVSDB_CLIENT(PROTOCOL, [CIPHERS])
# SSL_OVSDB_CLIENT(PROTOCOL, [CIPHERS], [CIPHERSUITES])
m4_define([SSL_OVSDB_CLIENT], [dnl
ovsdb-client -vconsole:stream_ssl:dbg \
--private-key=$PKIDIR/testpki-privkey.pem \
@ -878,6 +884,7 @@ m4_define([SSL_OVSDB_CLIENT], [dnl
--ca-cert=$PKIDIR/testpki-cacert.pem \
--ssl-protocols=[$1] \
m4_if([$2], [], [], [--ssl-ciphers=$2]) \
m4_if([$3], [], [], [--ssl-ciphersuites=$3]) \
transact ssl:127.0.0.1:$SSL_PORT \
'[[["mydb",
{"op": "select",
@ -885,27 +892,29 @@ m4_define([SSL_OVSDB_CLIENT], [dnl
"where": [],
"columns": ["private_key"]}]]]'])
AT_CHECK(SSL_OVSDB_CLIENT([TLSv1.2,TLSv1.1]), [0], [stdout], [ignore])
cat stdout >> output
AT_CHECK(SSL_OVSDB_CLIENT([TLSv1.3,TLSv1.2]), [0], [stdout], [ignore])
AT_CHECK_UNQUOTED(
[cat output], [0],
[cat stdout], [0],
[[@<:@{"rows":@<:@{"private_key":"$PKIDIR/testpki-privkey2.pem"}@:>@}@:>@
]], [ignore])
# Check that when the server has TLSv1.1+ and the client has
# TLSv1 that the connection fails.
AT_CHECK(SSL_OVSDB_CLIENT([TLSv1]), [1], [stdout], [stderr])
cat stderr > output
# Check that connection over TLSv1.2 works.
AT_CHECK(SSL_OVSDB_CLIENT([TLSv1.2]), [0], [stdout], [ignore])
AT_CHECK_UNQUOTED(
[sed -n "/failed to connect/s/ (.*)//p" output], [0],
[ovsdb-client: failed to connect to "ssl:127.0.0.1:$SSL_PORT"
],
[ignore])
AT_CHECK([grep -q 'TLSv1 protocol is deprecated' output])
AT_CHECK([grep -q 'Enabled protocol range: TLSv1 - TLSv1' stderr])
# Check that when ciphers are not compatible, that a negotiation
# failure occurs.
AT_CHECK(SSL_OVSDB_CLIENT([TLSv1.2,TLSv1.1], [ECDHE-ECDSA-AES256-GCM-SHA384]),
[1], [stdout], [stderr])
[cat stdout], [0],
[[@<:@{"rows":@<:@{"private_key":"$PKIDIR/testpki-privkey2.pem"}@:>@}@:>@
]], [ignore])
# Check that connection over TLSv1.3 works.
AT_CHECK(SSL_OVSDB_CLIENT([TLSv1.3]), [0], [stdout], [ignore])
AT_CHECK_UNQUOTED(
[cat stdout], [0],
[[@<:@{"rows":@<:@{"private_key":"$PKIDIR/testpki-privkey2.pem"}@:>@}@:>@
]], [ignore])
# Check that, when the server has TLSv1.2+ and the client has
# TLSv1.1, connection fails.
AT_CHECK(SSL_OVSDB_CLIENT([TLSv1.1]), [1], [stdout], [stderr])
cat stderr > output
AT_CHECK_UNQUOTED(
[sed -n "/failed to connect/s/ (.*)//p" output], [0],
@ -913,6 +922,17 @@ AT_CHECK_UNQUOTED(
],
[ignore])
AT_CHECK([grep -q 'TLSv1.1 protocol is deprecated' output])
AT_CHECK([grep -q 'Enabled protocol range: TLSv1.1 - TLSv1.1' stderr])
# Check that when ciphers are not compatible, a negotiation
# failure occurs.
AT_CHECK(SSL_OVSDB_CLIENT([TLSv1.2], [ECDHE-ECDSA-AES256-GCM-SHA384]),
[1], [stdout], [stderr])
cat stderr > output
AT_CHECK_UNQUOTED(
[sed -n "/failed to connect/s/ (.*)//p" output], [0],
[ovsdb-client: failed to connect to "ssl:127.0.0.1:$SSL_PORT"
],
[ignore])
# The error message for being unable to negotiate a shared ciphersuite
# is 'sslv3 alert handshake failure'. This is not the clearest message.
# In openssl 3.2.0 all the error messages were updated to replace 'sslv3'
@ -922,22 +942,33 @@ AT_CHECK_UNQUOTED(
[stdout],
[ignore])
# Check that when ciphersuites are not compatible, a negotiation
# failure occurs.
AT_CHECK(SSL_OVSDB_CLIENT([TLSv1.3], [DEFAULT], [TLS_AES_256_GCM_SHA384]),
[1], [stdout], [stderr])
cat stderr > output
AT_CHECK_UNQUOTED(
[sed -n "/failed to connect/s/ (.*)//p" output], [0],
[ovsdb-client: failed to connect to "ssl:127.0.0.1:$SSL_PORT"
], [ignore])
AT_CHECK([grep -q -E "(sslv3|ssl/tls) alert handshake failure" output])
# Checking parsing of different protocol ranges.
AT_CHECK(SSL_OVSDB_CLIENT([TLSv1,TLSv1.2]), [0], [stdout], [stderr])
AT_CHECK([grep -q 'Enabled protocol range: TLSv1 - TLSv1.2' stderr])
AT_CHECK(SSL_OVSDB_CLIENT([TLSv1.1,TLSv1.3]), [0], [stdout], [stderr])
AT_CHECK([grep -q 'Enabled protocol range: TLSv1.1 - TLSv1.3' stderr])
AT_CHECK([grep -q \
'TLSv1.1 is not configured, but will be enabled anyway' stderr])
AT_CHECK(SSL_OVSDB_CLIENT([TLSv1-TLSv1.2]), [0], [stdout], [stderr])
AT_CHECK([grep -q 'Enabled protocol range: TLSv1 - TLSv1.2' stderr])
'TLSv1.2 is not configured, but will be enabled anyway' stderr])
AT_CHECK(SSL_OVSDB_CLIENT([TLSv1.1-TLSv1.3]), [0], [stdout], [stderr])
AT_CHECK([grep -q 'Enabled protocol range: TLSv1.1 - TLSv1.3' stderr])
AT_CHECK([grep -q 'will be enabled anyway' stderr], [1])
AT_CHECK(SSL_OVSDB_CLIENT([TLSv1.2-TLSv1]), [0], [stdout], [stderr])
AT_CHECK([grep -q 'Enabled protocol range: TLSv1 - TLSv1.2' stderr])
AT_CHECK(SSL_OVSDB_CLIENT([TLSv1.3-TLSv1.1]), [0], [stdout], [stderr])
AT_CHECK([grep -q 'Enabled protocol range: TLSv1.1 - TLSv1.3' stderr])
AT_CHECK([grep -q 'will be enabled anyway' stderr], [1])
AT_CHECK(SSL_OVSDB_CLIENT([TLSv1.1+]), [0], [stdout], [stderr])
AT_CHECK([grep -q 'Enabled protocol range: TLSv1.1 or later' stderr])
AT_CHECK(SSL_OVSDB_CLIENT([TLSv1.1-TLSv1.2,TLSv1.2+]), [0], [stdout], [stderr])
AT_CHECK(SSL_OVSDB_CLIENT([TLSv1.3+]), [0], [stdout], [stderr])
AT_CHECK([grep -q 'Enabled protocol range: TLSv1.3 or later' stderr])
AT_CHECK(SSL_OVSDB_CLIENT([TLSv1.2-TLSv1.3,TLSv1.3+]), [0], [stdout], [stderr])
AT_CHECK([grep -q 'SSL/TLS protocol not recognized' stderr])
AT_CHECK(SSL_OVSDB_CLIENT([TLSv1.1+TLSv1.2]), [0], [stdout], [stderr])
AT_CHECK(SSL_OVSDB_CLIENT([TLSv1.2+TLSv1.3]), [0], [stdout], [stderr])
AT_CHECK([grep -q 'SSL/TLS protocol not recognized' stderr])
OVSDB_SERVER_SHUTDOWN(["