Add SSL_set_fallback_version.
Alas, we will need a version fallback for TLS 1.3 again.
This deprecates SSL_MODE_SEND_FALLBACK_SCSV. Rather than supplying a
boolean, have BoringSSL be aware of the real maximum version so we can
change the TLS 1.3 anti-downgrade logic to kick in, even when
max_version is set to 1.2.
The fallback version replaces the maximum version when it is set for
almost all purposes, except for downgrade protection purposes.
BUG=chromium:630165
Change-Id: I4c841dcbc6e55a282b223dfe169ac89c83c8a01f
Reviewed-on: https://boringssl-review.googlesource.com/8882
Reviewed-by: David Benjamin <davidben@google.com>
Commit-Queue: David Benjamin <davidben@google.com>
diff --git a/include/openssl/ssl.h b/include/openssl/ssl.h
index 45480e5..21b8674 100644
--- a/include/openssl/ssl.h
+++ b/include/openssl/ssl.h
@@ -681,7 +681,9 @@
* version; see RFC 7507 for details.
*
* DO NOT ENABLE THIS if your application attempts a normal handshake. Only use
- * this in explicit fallback retries, following the guidance in RFC 7507. */
+ * this in explicit fallback retries, following the guidance in RFC 7507.
+ *
+ * This flag is deprecated. Use |SSL_set_fallback_version| instead. */
#define SSL_MODE_SEND_FALLBACK_SCSV 0x00000400L
/* SSL_CTX_set_mode enables all modes set in |mode| (which should be one or more
@@ -3059,6 +3061,22 @@
OPENSSL_EXPORT void SSL_CTX_set_retain_only_sha256_of_client_certs(SSL_CTX *ctx,
int enable);
+/* SSL_set_fallback_version, on a client, sets the effective maximum protocol
+ * version. This may be used when implementing a version
+ * fallback to work around buggy servers.
+ *
+ * For purposes of the TLS protocol itself, including assembling the ClientHello
+ * and which ServerHello versions are accepted, this value is used as the
+ * maximum version. However, if this value differs from the real maximum
+ * version, as set by |SSL_set_max_version|, TLS_FALLBACK_SCSV (see RFC 7507)
+ * will be sent. Further, the TLS 1.3 anti-downgrade logic will be conditioned
+ * on the true maximum version.
+ *
+ * For instance, a fallback from a TLS 1.3 ClientHello to a TLS 1.2 ClientHello
+ * should set this value to |TLS1_2_VERSION| and call |SSL_set_max_version| with
+ * |TLS1_3_VERSION|. */
+OPENSSL_EXPORT void SSL_set_fallback_version(SSL *ssl, uint16_t version);
+
/* Deprecated functions. */
@@ -3960,6 +3978,11 @@
* is normalized in DTLS. */
uint16_t min_version;
+ /* fallback_version is the effective maximum acceptable protocol version for
+ * use with a version fallback, or zero if unset. Note this version is
+ * normalized in DTLS. */
+ uint16_t fallback_version;
+
/* method is the method table corresponding to the current protocol (DTLS or
* TLS). */
const SSL_PROTOCOL_METHOD *method;
diff --git a/ssl/handshake_client.c b/ssl/handshake_client.c
index 52ff212..6f9b2fc 100644
--- a/ssl/handshake_client.c
+++ b/ssl/handshake_client.c
@@ -548,8 +548,10 @@
return ret;
}
-int ssl_write_client_cipher_list(SSL *ssl, CBB *out, uint16_t min_version,
- uint16_t max_version) {
+static int ssl_write_client_cipher_list(SSL *ssl, CBB *out,
+ uint16_t min_version,
+ uint16_t max_version,
+ uint16_t real_max_version) {
/* Prepare disabled cipher masks. */
ssl_set_client_disabled(ssl);
@@ -596,17 +598,20 @@
ssl->s3->tmp.extensions.sent |= (1u << 0);
}
- if ((ssl->mode & SSL_MODE_SEND_FALLBACK_SCSV) &&
- !CBB_add_u16(&child, SSL3_CK_FALLBACK_SCSV & 0xffff)) {
- return 0;
+ if ((ssl->mode & SSL_MODE_SEND_FALLBACK_SCSV) ||
+ real_max_version > max_version) {
+ if (!CBB_add_u16(&child, SSL3_CK_FALLBACK_SCSV & 0xffff)) {
+ return 0;
+ }
}
return CBB_flush(out);
}
int ssl_add_client_hello_body(SSL *ssl, CBB *body) {
- uint16_t min_version, max_version;
- if (!ssl_get_version_range(ssl, &min_version, &max_version)) {
+ uint16_t min_version, max_version, real_max_version;
+ if (!ssl_get_full_version_range(ssl, &min_version, &max_version,
+ &real_max_version)) {
return 0;
}
@@ -633,7 +638,8 @@
size_t header_len =
SSL_IS_DTLS(ssl) ? DTLS1_HM_HEADER_LENGTH : SSL3_HM_HEADER_LENGTH;
- if (!ssl_write_client_cipher_list(ssl, body, min_version, max_version) ||
+ if (!ssl_write_client_cipher_list(ssl, body, min_version, max_version,
+ real_max_version) ||
!CBB_add_u8(body, 1 /* one compression method */) ||
!CBB_add_u8(body, 0 /* null compression */) ||
!ssl_add_clienthello_tlsext(ssl, body, header_len + CBB_len(body))) {
@@ -791,8 +797,9 @@
server_version = ssl->method->version_from_wire(server_wire_version);
- uint16_t min_version, max_version;
- if (!ssl_get_version_range(ssl, &min_version, &max_version) ||
+ uint16_t min_version, max_version, real_max_version;
+ if (!ssl_get_full_version_range(ssl, &min_version, &max_version,
+ &real_max_version) ||
server_version < min_version || server_version > max_version) {
OPENSSL_PUT_ERROR(SSL, SSL_R_UNSUPPORTED_PROTOCOL);
al = SSL_AD_PROTOCOL_VERSION;
@@ -843,7 +850,7 @@
* settled down. */
static const uint8_t kDowngradeTLS12[8] = {0x44, 0x4f, 0x57, 0x4e,
0x47, 0x52, 0x44, 0x01};
- if (max_version >= TLS1_3_VERSION &&
+ if (real_max_version >= TLS1_3_VERSION &&
ssl3_protocol_version(ssl) <= TLS1_2_VERSION &&
memcmp(ssl->s3->server_random + SSL3_RANDOM_SIZE - 8, kDowngradeTLS12,
8) == 0) {
diff --git a/ssl/internal.h b/ssl/internal.h
index 5d14eba..f5180a7 100644
--- a/ssl/internal.h
+++ b/ssl/internal.h
@@ -1238,9 +1238,6 @@
STACK_OF(SSL_CIPHER) *ssl_get_ciphers_by_id(SSL *ssl);
int ssl_verify_alarm_type(long type);
-int ssl_write_client_cipher_list(SSL *ssl, CBB *out, uint16_t min_version,
- uint16_t max_version);
-
int ssl3_get_finished(SSL *ssl);
int ssl3_send_change_cipher_spec(SSL *ssl);
void ssl3_cleanup_key_block(SSL *ssl);
@@ -1411,10 +1408,21 @@
* |version|. */
const SSL3_ENC_METHOD *ssl3_get_enc_method(uint16_t version);
-/* ssl_get_version_range sets |*out_min_version| and |*out_max_version| to the
- * minimum and maximum enabled protocol versions, respectively. */
+/* ssl_get_full_version_range sets |*out_min_version|, |*out_fallback_version|,
+ * and |*out_max_version| to the minimum, fallback, and maximum enabled protocol
+ * versions, respectively. The fallback version is the effective maximium
+ * version used throughout the stack and the maximum version is the true maximum
+ * for downgrade purposes. */
+int ssl_get_full_version_range(const SSL *ssl, uint16_t *out_min_version,
+ uint16_t *out_fallback_version,
+ uint16_t *out_max_version);
+
+/* ssl_get_version_range sets |*out_min_version| and
+ * |*out_effective_max_version| to the minimum and maximum enabled protocol
+ * versions, respectively. Note that, if there is a fallback version set, it
+ * returns it as the maximum version. */
int ssl_get_version_range(const SSL *ssl, uint16_t *out_min_version,
- uint16_t *out_max_version);
+ uint16_t *out_effective_max_version);
/* ssl3_protocol_version returns |ssl|'s protocol version. It is an error to
* call this function before the version is determined. */
diff --git a/ssl/ssl_lib.c b/ssl/ssl_lib.c
index 9a4e200..716a040 100644
--- a/ssl/ssl_lib.c
+++ b/ssl/ssl_lib.c
@@ -860,6 +860,10 @@
ssl->max_version = ssl->method->version_from_wire(version);
}
+void SSL_set_fallback_version(SSL *ssl, uint16_t version) {
+ ssl->fallback_version = ssl->method->version_from_wire(version);
+}
+
uint32_t SSL_CTX_set_options(SSL_CTX *ctx, uint32_t options) {
ctx->options |= options;
return ctx->options;
@@ -2562,8 +2566,9 @@
static const size_t kVersionsLen = sizeof(kVersions) / sizeof(kVersions[0]);
-int ssl_get_version_range(const SSL *ssl, uint16_t *out_min_version,
- uint16_t *out_max_version) {
+int ssl_get_full_version_range(const SSL *ssl, uint16_t *out_min_version,
+ uint16_t *out_fallback_version,
+ uint16_t *out_max_version) {
/* For historical reasons, |SSL_OP_NO_DTLSv1| aliases |SSL_OP_NO_TLSv1|, but
* DTLS 1.0 should be mapped to TLS 1.1. */
uint32_t options = ssl->options;
@@ -2623,16 +2628,32 @@
}
}
- if (!any_enabled) {
+ uint16_t fallback_version = max_version;
+ if (ssl->fallback_version != 0 && ssl->fallback_version < fallback_version) {
+ fallback_version = ssl->fallback_version;
+ }
+
+ if (!any_enabled || fallback_version < min_version) {
OPENSSL_PUT_ERROR(SSL, SSL_R_WRONG_SSL_VERSION);
return 0;
}
*out_min_version = min_version;
+ *out_fallback_version = fallback_version;
*out_max_version = max_version;
return 1;
}
+int ssl_get_version_range(const SSL *ssl, uint16_t *out_min_version,
+ uint16_t *out_effective_max_version) {
+ /* This function returns the effective maximum version and not the fallback
+ * version. */
+ uint16_t real_max_version_unused;
+ return ssl_get_full_version_range(ssl, out_min_version,
+ out_effective_max_version,
+ &real_max_version_unused);
+}
+
uint16_t ssl3_protocol_version(const SSL *ssl) {
assert(ssl->s3->have_version);
return ssl->method->version_from_wire(ssl->version);
diff --git a/ssl/test/bssl_shim.cc b/ssl/test/bssl_shim.cc
index d1aadb8..edf0fe0 100644
--- a/ssl/test/bssl_shim.cc
+++ b/ssl/test/bssl_shim.cc
@@ -1316,6 +1316,9 @@
if (config->max_version != 0) {
SSL_set_max_version(ssl.get(), (uint16_t)config->max_version);
}
+ if (config->fallback_version != 0) {
+ SSL_set_fallback_version(ssl.get(), (uint16_t)config->fallback_version);
+ }
if (config->mtu != 0) {
SSL_set_options(ssl.get(), SSL_OP_NO_QUERY_MTU);
SSL_set_mtu(ssl.get(), config->mtu);
diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go
index 4997836..fd263e6 100644
--- a/ssl/test/runner/runner.go
+++ b/ssl/test/runner/runner.go
@@ -3864,6 +3864,51 @@
shouldFail: true,
expectedLocalError: "tls: downgrade from TLS 1.3 detected",
})
+
+ // Test that FALLBACK_SCSV is sent and that the downgrade signal works
+ // behave correctly when both real maximum and fallback versions are
+ // set.
+ testCases = append(testCases, testCase{
+ name: "Downgrade-TLS12-Client-Fallback",
+ config: Config{
+ Bugs: ProtocolBugs{
+ FailIfNotFallbackSCSV: true,
+ },
+ },
+ flags: []string{
+ "-max-version", strconv.Itoa(VersionTLS13),
+ "-fallback-version", strconv.Itoa(VersionTLS12),
+ },
+ shouldFail: true,
+ expectedError: ":DOWNGRADE_DETECTED:",
+ })
+ testCases = append(testCases, testCase{
+ name: "Downgrade-TLS12-Client-FallbackEqualsMax",
+ flags: []string{
+ "-max-version", strconv.Itoa(VersionTLS12),
+ "-fallback-version", strconv.Itoa(VersionTLS12),
+ },
+ })
+
+ // On TLS 1.2 fallback, 1.3 ServerHellos are forbidden. (We would rather
+ // just have such connections fail than risk getting confused because we
+ // didn't sent the 1.3 ClientHello.)
+ testCases = append(testCases, testCase{
+ name: "Downgrade-TLS12-Fallback-CheckVersion",
+ config: Config{
+ Bugs: ProtocolBugs{
+ NegotiateVersion: VersionTLS13,
+ FailIfNotFallbackSCSV: true,
+ },
+ },
+ flags: []string{
+ "-max-version", strconv.Itoa(VersionTLS13),
+ "-fallback-version", strconv.Itoa(VersionTLS12),
+ },
+ shouldFail: true,
+ expectedError: ":UNSUPPORTED_PROTOCOL:",
+ })
+
}
func addMinimumVersionTests() {
diff --git a/ssl/test/test_config.cc b/ssl/test/test_config.cc
index b1a0792..d242f7a 100644
--- a/ssl/test/test_config.cc
+++ b/ssl/test/test_config.cc
@@ -144,6 +144,7 @@
{ "-port", &TestConfig::port },
{ "-min-version", &TestConfig::min_version },
{ "-max-version", &TestConfig::max_version },
+ { "-fallback-version", &TestConfig::fallback_version },
{ "-mtu", &TestConfig::mtu },
{ "-export-keying-material", &TestConfig::export_keying_material },
{ "-expect-total-renegotiations", &TestConfig::expect_total_renegotiations },
diff --git a/ssl/test/test_config.h b/ssl/test/test_config.h
index 73bdc6e..690fbe7 100644
--- a/ssl/test/test_config.h
+++ b/ssl/test/test_config.h
@@ -66,6 +66,7 @@
std::string expected_signed_cert_timestamps;
int min_version = 0;
int max_version = 0;
+ int fallback_version = 0;
int mtu = 0;
bool implicit_handshake = false;
bool use_early_callback = false;