Add a dedicated error code for TLS 1.3 interference.
From the previous TLS 1.3 launch attempt, we learned that many
firewall, proxy, etc., products are buggy and interfere with TLS 1.3's
deployment, holding back a security and performance improvement across
the web.
To make diagnosing such issues easier, this CL implements a dedicated
error code based on a retry probe. On SSL connection failure, if TLS 1.3
was enabled and the error code is one of a handful which, in the past,
have potentially signaled version intolerance, we retry the connection
with TLS 1.3 disabled. If this connection succeeds, we still reject the
connection (otherwise a network attacker can break the security of the
version negotiation, cf. POODLE) and return
ERR_SSL_VERSION_INTERFERENCE.
This error code should hopefully give an easier target for search
metrics and others, as we otherwise cannot reliably classify
individual errors.
Unfortunately, such a probe is inherently flaky and is itself not
reliable. This error could mean one of three things:
1. This is a transient network error that will be resolved when the user
reloads.
2. The server is buggy and does not implement TLS version negotiation
correctly.
3. The user is behind a buggy network middlebox, firewall, or proxy which is
interfering with TLS 1.3.
Based on server side probes, the lack of TLS 1.3 error reports until it
was enabled on the server, and a protocol change in TLS 1.3 intended to
avoid this, we do not believe (2) is common. (The difference between (2)
and (3) is whether the servers or middleboxes are at fault here.)
(1) is unavoidable. There is no way to reliably distinguish (1) and (3).
We can only make (1) less and less likely by spamming the user's network
with probes, which is undesirable.
Accordingly, though the error string is short and easily searchable, I
have left the network error page fairly non-descript, borrowing from the
ERR_CONNECTION_FAILED text, but with SUGGEST_PROXY_CONFIG and friends
enabled, to hint that users should, if their default reaction of mashing
reload (or the auto-reload feature) doesn't work, look there.
Screentshot:
https://drive.google.com/open?id=0B2ImyA6KAoPULVp3V0xPVEJHQms
BUG=694593,658863
CQ_INCLUDE_TRYBOTS=master.tryserver.chromium.linux:closure_compilation
Review-Url: https://codereview.chromium.org/2800853008
Cr-Commit-Position: refs/heads/master@{#464173}
diff --git a/net/url_request/url_request_unittest.cc b/net/url_request/url_request_unittest.cc
index 6583968..1eb9711 100644
--- a/net/url_request/url_request_unittest.cc
+++ b/net/url_request/url_request_unittest.cc
@@ -3423,8 +3423,10 @@
rev_checking_required_local_anchors_(
rev_checking_required_local_anchors),
token_binding_enabled_(token_binding_enabled),
- min_version_(kDefaultSSLVersionMin) {}
+ min_version_(kDefaultSSLVersionMin),
+ max_version_(kDefaultSSLVersionMax) {}
+ void set_max_version(uint16_t version) { max_version_ = version; }
void set_min_version(uint16_t version) { min_version_ = version; }
// SSLConfigService:
@@ -3434,9 +3436,8 @@
config->verify_ev_cert = ev_enabled_;
config->rev_checking_required_local_anchors =
rev_checking_required_local_anchors_;
- if (min_version_) {
- config->version_min = min_version_;
- }
+ config->version_min = min_version_;
+ config->version_max = max_version_;
if (token_binding_enabled_) {
config->token_binding_params.push_back(TB_PARAM_ECDSAP256);
}
@@ -3451,6 +3452,7 @@
const bool rev_checking_required_local_anchors_;
const bool token_binding_enabled_;
uint16_t min_version_;
+ uint16_t max_version_;
};
// TODO(svaldez): Update tests to use EmbeddedTestServer.
@@ -9369,10 +9371,21 @@
class HTTPSFallbackTest : public testing::Test {
public:
- HTTPSFallbackTest() : context_(true) {}
+ HTTPSFallbackTest()
+ : scoped_task_scheduler_(base::MessageLoop::current()), context_(true) {
+ ssl_config_service_ = new TestSSLConfigService(
+ true /* check for EV */, false /* online revocation checking */,
+ false /* require rev. checking for local anchors */,
+ false /* token binding enabled */);
+ context_.set_ssl_config_service(ssl_config_service_.get());
+ }
~HTTPSFallbackTest() override {}
protected:
+ TestSSLConfigService* ssl_config_service() {
+ return ssl_config_service_.get();
+ }
+
void DoFallbackTest(const SpawnedTestServer::SSLOptions& ssl_options) {
DCHECK(!request_);
context_.Init();
@@ -9391,15 +9404,25 @@
base::RunLoop().Run();
}
+ void ExpectConnection(int version) {
+ EXPECT_EQ(1, delegate_.response_started_count());
+ EXPECT_NE(0, delegate_.bytes_received());
+ EXPECT_EQ(version, SSLConnectionStatusToVersion(
+ request_->ssl_info().connection_status));
+ }
+
void ExpectFailure(int error) {
EXPECT_EQ(1, delegate_.response_started_count());
EXPECT_EQ(error, delegate_.request_status());
}
private:
+ // Required by ChannelIDService.
+ base::test::ScopedTaskScheduler scoped_task_scheduler_;
TestDelegate delegate_;
TestURLRequestContext context_;
std::unique_ptr<URLRequest> request_;
+ scoped_refptr<TestSSLConfigService> ssl_config_service_;
};
// Tests the TLS 1.0 fallback doesn't happen.
@@ -9424,6 +9447,30 @@
ExpectFailure(ERR_SSL_VERSION_OR_CIPHER_MISMATCH);
}
+// Tests that TLS 1.3 interference results in a dedicated error code.
+TEST_F(HTTPSFallbackTest, TLSv1_3Interference) {
+ SpawnedTestServer::SSLOptions ssl_options(
+ SpawnedTestServer::SSLOptions::CERT_OK);
+ ssl_options.tls_intolerant =
+ SpawnedTestServer::SSLOptions::TLS_INTOLERANT_TLS1_3;
+ ssl_config_service()->set_max_version(SSL_PROTOCOL_VERSION_TLS1_3);
+
+ ASSERT_NO_FATAL_FAILURE(DoFallbackTest(ssl_options));
+ ExpectFailure(ERR_SSL_VERSION_INTERFERENCE);
+}
+
+// Tests that disabling TLS 1.3 leaves TLS 1.3 interference unnoticed.
+TEST_F(HTTPSFallbackTest, TLSv1_3InterferenceDisableVersion) {
+ SpawnedTestServer::SSLOptions ssl_options(
+ SpawnedTestServer::SSLOptions::CERT_OK);
+ ssl_options.tls_intolerant =
+ SpawnedTestServer::SSLOptions::TLS_INTOLERANT_TLS1_3;
+ ssl_config_service()->set_max_version(SSL_PROTOCOL_VERSION_TLS1_2);
+
+ ASSERT_NO_FATAL_FAILURE(DoFallbackTest(ssl_options));
+ ExpectConnection(SSL_CONNECTION_VERSION_TLS1_2);
+}
+
class HTTPSSessionTest : public testing::Test {
public:
HTTPSSessionTest()