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/components/error_page/common/localized_error.cc b/components/error_page/common/localized_error.cc
index 9047cdc..32cb9e9 100644
--- a/components/error_page/common/localized_error.cc
+++ b/components/error_page/common/localized_error.cc
@@ -248,6 +248,12 @@
SUGGEST_CONTACT_ADMINISTRATOR,
SHOW_NO_BUTTONS,
},
+ {net::ERR_SSL_VERSION_INTERFERENCE,
+ IDS_ERRORPAGES_HEADING_NOT_AVAILABLE,
+ IDS_ERRORPAGES_SUMMARY_CONNECTION_FAILED,
+ SUGGEST_CHECK_CONNECTION | SUGGEST_FIREWALL_CONFIG | SUGGEST_PROXY_CONFIG,
+ SHOW_BUTTON_RELOAD,
+ },
{net::ERR_SSL_WEAK_SERVER_EPHEMERAL_DH_KEY,
IDS_ERRORPAGES_HEADING_INSECURE_CONNECTION,
IDS_ERRORPAGES_SUMMARY_SSL_SECURITY_ERROR,
diff --git a/net/base/net_error_list.h b/net/base/net_error_list.h
index 1398543..6fa8f24 100644
--- a/net/base/net_error_list.h
+++ b/net/base/net_error_list.h
@@ -390,6 +390,20 @@
// visible, because the normal Read() method is used as a fallback.
NET_ERROR(READ_IF_READY_NOT_IMPLEMENTED, -174)
+// This error is emitted if TLS 1.3 is enabled, connecting with it failed, but
+// retrying at a downgraded maximum version succeeded. This could mean:
+//
+// 1. This is a transient network error that will be resolved when the user
+// reloads.
+//
+// 2. The user is behind a buggy network middlebox, firewall, or proxy which is
+// interfering with TLS 1.3.
+//
+// 3. The server is buggy and does not implement TLS version negotiation
+// correctly. TLS 1.3 was tweaked to avoid a common server bug here, so this
+// is unlikely.
+NET_ERROR(SSL_VERSION_INTERFERENCE, -175)
+
// Certificate error codes
//
// The values of certificate error codes must be consecutive.
diff --git a/net/log/net_log_event_type_list.h b/net/log/net_log_event_type_list.h
index bbd14afb..53c84f4 100644
--- a/net/log/net_log_event_type_list.h
+++ b/net/log/net_log_event_type_list.h
@@ -563,6 +563,15 @@
// }
EVENT_TYPE(SSL_CIPHER_FALLBACK)
+// An SSL connection needs to be retried with a lower protocol version to detect
+// if the error was due to a middlebox interfering with the protocol version we
+// offered.
+// The following parameters are attached to the event:
+// {
+// "net_error": <Net integer error code which triggered the probe>,
+// }
+EVENT_TYPE(SSL_VERSION_INTERFERENCE_PROBE)
+
// We found that our prediction of the server's certificates was correct and
// we merged the verification with the SSLHostInfo. (Note: now obsolete.)
EVENT_TYPE(SSL_VERIFICATION_MERGED)
diff --git a/net/socket/ssl_client_socket_impl.cc b/net/socket/ssl_client_socket_impl.cc
index 085ef63..ab4f113 100644
--- a/net/socket/ssl_client_socket_impl.cc
+++ b/net/socket/ssl_client_socket_impl.cc
@@ -1066,6 +1066,11 @@
if (result < 0)
return result;
+ if (ssl_config_.version_interference_probe) {
+ DCHECK_LT(ssl_config_.version_max, TLS1_3_VERSION);
+ return ERR_SSL_VERSION_INTERFERENCE;
+ }
+
SSLContext::GetInstance()->session_cache()->ResetLookupCount(
GetSessionCacheKey());
// Check that if token binding was negotiated, then extended master secret
@@ -1694,17 +1699,13 @@
std::string SSLClientSocketImpl::GetSessionCacheKey() const {
std::string result = host_and_port_.ToString();
- result.append("/");
+ result.push_back('/');
result.append(ssl_session_cache_shard_);
- result.append("/");
- if (ssl_config_.deprecated_cipher_suites_enabled)
- result.append("deprecated");
-
- result.append("/");
- if (ssl_config_.channel_id_enabled)
- result.append("channelid");
-
+ result.push_back('/');
+ result.push_back(ssl_config_.deprecated_cipher_suites_enabled ? '1' : '0');
+ result.push_back(ssl_config_.channel_id_enabled ? '1' : '0');
+ result.push_back(ssl_config_.version_interference_probe ? '1' : '0');
return result;
}
diff --git a/net/socket/ssl_client_socket_pool.cc b/net/socket/ssl_client_socket_pool.cc
index b335e46b..a557e5b 100644
--- a/net/socket/ssl_client_socket_pool.cc
+++ b/net/socket/ssl_client_socket_pool.cc
@@ -4,6 +4,7 @@
#include "net/socket/ssl_client_socket_pool.h"
+#include <cstdlib>
#include <utility>
#include "base/bind.h"
@@ -130,7 +131,9 @@
? "pm/" + context.ssl_session_cache_shard
: context.ssl_session_cache_shard)),
callback_(
- base::Bind(&SSLConnectJob::OnIOComplete, base::Unretained(this))) {}
+ base::Bind(&SSLConnectJob::OnIOComplete, base::Unretained(this))),
+ version_interference_probe_(false),
+ version_interference_error_(OK) {}
SSLConnectJob::~SSLConnectJob() {
}
@@ -236,7 +239,10 @@
}
int SSLConnectJob::DoTransportConnectComplete(int result) {
- connection_attempts_ = transport_socket_handle_->connection_attempts();
+ connection_attempts_.insert(
+ connection_attempts_.end(),
+ transport_socket_handle_->connection_attempts().begin(),
+ transport_socket_handle_->connection_attempts().end());
if (result == OK) {
next_state_ = STATE_SSL_CONNECT;
transport_socket_handle_->socket()->GetPeerAddress(&server_address_);
@@ -321,13 +327,22 @@
connect_timing_.ssl_start = base::TimeTicks::Now();
+ SSLConfig ssl_config = params_->ssl_config();
+ if (version_interference_probe_) {
+ DCHECK_EQ(SSL_PROTOCOL_VERSION_TLS1_3, ssl_config.version_max);
+ ssl_config.version_max = SSL_PROTOCOL_VERSION_TLS1_2;
+ ssl_config.version_interference_probe = true;
+ }
ssl_socket_ = client_socket_factory_->CreateSSLClientSocket(
- std::move(transport_socket_handle_), params_->host_and_port(),
- params_->ssl_config(), context_);
+ std::move(transport_socket_handle_), params_->host_and_port(), ssl_config,
+ context_);
return ssl_socket_->Connect(callback_);
}
int SSLConnectJob::DoSSLConnectComplete(int result) {
+ // Version interference probes should not result in success.
+ DCHECK(!version_interference_probe_ || result != OK);
+
// TODO(rvargas): Remove ScopedTracker below once crbug.com/462784 is fixed.
tracked_objects::ScopedTracker tracking_profile(
FROM_HERE_WITH_EXPLICIT_FUNCTION(
@@ -346,6 +361,32 @@
return ERR_ALPN_NEGOTIATION_FAILED;
}
+ // Perform a TLS 1.3 version interference probe on various connection
+ // errors. The retry will never produce a successful connection but may map
+ // errors to ERR_SSL_VERSION_INTERFERENCE, which signals a probable
+ // version-interfering middlebox.
+ if (params_->ssl_config().version_max == SSL_PROTOCOL_VERSION_TLS1_3 &&
+ !params_->ssl_config().deprecated_cipher_suites_enabled &&
+ !version_interference_probe_) {
+ if (result == ERR_CONNECTION_CLOSED || result == ERR_SSL_PROTOCOL_ERROR ||
+ result == ERR_SSL_VERSION_OR_CIPHER_MISMATCH ||
+ result == ERR_CONNECTION_RESET ||
+ result == ERR_SSL_BAD_RECORD_MAC_ALERT) {
+ // Report the error code for each time a version interference probe is
+ // triggered.
+ UMA_HISTOGRAM_SPARSE_SLOWLY("Net.SSLVersionInterferenceProbeTrigger",
+ std::abs(result));
+ net_log().AddEventWithNetErrorCode(
+ NetLogEventType::SSL_VERSION_INTERFERENCE_PROBE, result);
+
+ ResetStateForRetry();
+ version_interference_probe_ = true;
+ version_interference_error_ = result;
+ next_state_ = GetInitialState(params_->GetConnectionType());
+ return OK;
+ }
+ }
+
const std::string& host = params_->host_and_port().host();
bool is_google =
host == "google.com" ||
@@ -450,6 +491,14 @@
std::abs(result));
}
+ if (result == ERR_SSL_VERSION_INTERFERENCE) {
+ // Record the error code version interference was detected at.
+ DCHECK(version_interference_probe_);
+ DCHECK_NE(OK, version_interference_error_);
+ UMA_HISTOGRAM_SPARSE_SLOWLY("Net.SSLVersionInterferenceError",
+ std::abs(version_interference_error_));
+ }
+
if (result == OK || IsCertificateError(result)) {
SetSocket(std::move(ssl_socket_));
} else if (result == ERR_SSL_CLIENT_AUTH_CERT_NEEDED) {
@@ -480,6 +529,13 @@
return DoLoop(OK);
}
+void SSLConnectJob::ResetStateForRetry() {
+ transport_socket_handle_.reset();
+ ssl_socket_.reset();
+ error_response_info_ = HttpResponseInfo();
+ server_address_ = IPEndPoint();
+}
+
SSLClientSocketPool::SSLConnectJobFactory::SSLConnectJobFactory(
TransportClientSocketPool* transport_pool,
SOCKSClientSocketPool* socks_pool,
diff --git a/net/socket/ssl_client_socket_pool.h b/net/socket/ssl_client_socket_pool.h
index 5dfc041..c4913df 100644
--- a/net/socket/ssl_client_socket_pool.h
+++ b/net/socket/ssl_client_socket_pool.h
@@ -151,6 +151,8 @@
// Otherwise, it returns a net error code.
int ConnectInternal() override;
+ void ResetStateForRetry();
+
scoped_refptr<SSLSocketParams> params_;
TransportClientSocketPool* const transport_pool_;
SOCKSClientSocketPool* const socks_pool_;
@@ -172,6 +174,12 @@
// through an HTTPS CONNECT request or a SOCKS proxy).
IPEndPoint server_address_;
+ bool version_interference_probe_;
+
+ // The error which triggered a TLS 1.3 version interference probe, or OK if
+ // none was triggered.
+ int version_interference_error_;
+
DISALLOW_COPY_AND_ASSIGN(SSLConnectJob);
};
diff --git a/net/socket/ssl_client_socket_unittest.cc b/net/socket/ssl_client_socket_unittest.cc
index b5fd0a9..6067721 100644
--- a/net/socket/ssl_client_socket_unittest.cc
+++ b/net/socket/ssl_client_socket_unittest.cc
@@ -2841,6 +2841,37 @@
EXPECT_EQ(SSLInfo::HANDSHAKE_FULL, ssl_info.handshake_type);
}
+// Tests that the version_interference_probe option rejects successful
+// connections and passes errors through.
+TEST_F(SSLClientSocketTest, VersionInterferenceProbe) {
+ ASSERT_TRUE(StartTestServer(SpawnedTestServer::SSLOptions()));
+
+ SSLConfig ssl_config;
+ ssl_config.version_max = SSL_PROTOCOL_VERSION_TLS1_2;
+ ssl_config.version_interference_probe = true;
+
+ // Successful connections map to a dedicated error.
+ int rv;
+ ASSERT_TRUE(CreateAndConnectSSLClientSocket(ssl_config, &rv));
+ EXPECT_THAT(rv, IsError(ERR_SSL_VERSION_INTERFERENCE));
+
+ // Failed connections pass through.
+ TestCompletionCallback callback;
+ std::unique_ptr<StreamSocket> real_transport(
+ new TCPClientSocket(addr(), NULL, NULL, NetLogSource()));
+ std::unique_ptr<SynchronousErrorStreamSocket> transport(
+ new SynchronousErrorStreamSocket(std::move(real_transport)));
+ rv = callback.GetResult(transport->Connect(callback.callback()));
+ EXPECT_THAT(rv, IsOk());
+ SynchronousErrorStreamSocket* raw_transport = transport.get();
+ std::unique_ptr<SSLClientSocket> sock(CreateSSLClientSocket(
+ std::move(transport), spawned_test_server()->host_port_pair(),
+ ssl_config));
+ raw_transport->SetNextWriteError(ERR_CONNECTION_RESET);
+ rv = callback.GetResult(sock->Connect(callback.callback()));
+ EXPECT_THAT(rv, IsError(ERR_CONNECTION_RESET));
+}
+
TEST_F(SSLClientSocketTest, RequireECDHE) {
// Run test server without ECDHE.
SpawnedTestServer::SSLOptions ssl_options;
diff --git a/net/ssl/ssl_config.cc b/net/ssl/ssl_config.cc
index 726ce48..528ff2a 100644
--- a/net/ssl/ssl_config.cc
+++ b/net/ssl/ssl_config.cc
@@ -28,6 +28,7 @@
version_min(kDefaultSSLVersionMin),
version_max(kDefaultSSLVersionMax),
deprecated_cipher_suites_enabled(false),
+ version_interference_probe(false),
channel_id_enabled(true),
false_start_enabled(true),
signed_cert_timestamps_enabled(true),
diff --git a/net/ssl/ssl_config.h b/net/ssl/ssl_config.h
index 7e6283d..eee5622 100644
--- a/net/ssl/ssl_config.h
+++ b/net/ssl/ssl_config.h
@@ -114,6 +114,12 @@
// it. https://crbug.com/684730.
bool deprecated_cipher_suites_enabled;
+ // Enables the version interference probing mode. While TLS 1.3 has avoided
+ // most endpoint intolerance, middlebox interference with TLS 1.3 is
+ // rampant. This causes the connection to be discarded on success with
+ // ERR_SSL_VERSION_INTERFERENCE.
+ bool version_interference_probe;
+
bool channel_id_enabled; // True if TLS channel ID extension is enabled.
// List of Token Binding key parameters supported by the client. If empty,
diff --git a/net/test/spawned_test_server/base_test_server.h b/net/test/spawned_test_server/base_test_server.h
index 60a9c3b..438bf17 100644
--- a/net/test/spawned_test_server/base_test_server.h
+++ b/net/test/spawned_test_server/base_test_server.h
@@ -154,9 +154,10 @@
// server. Do not change them.
enum TLSIntolerantLevel {
TLS_INTOLERANT_NONE = 0,
- TLS_INTOLERANT_ALL = 1, // Intolerant of all TLS versions.
+ TLS_INTOLERANT_ALL = 1, // Intolerant of all TLS versions.
TLS_INTOLERANT_TLS1_1 = 2, // Intolerant of TLS 1.1 or higher.
TLS_INTOLERANT_TLS1_2 = 3, // Intolerant of TLS 1.2 or higher.
+ TLS_INTOLERANT_TLS1_3 = 4, // Intolerant of TLS 1.3 or higher.
};
// Values which control how the server reacts in response to a ClientHello
diff --git a/net/tools/testserver/testserver.py b/net/tools/testserver/testserver.py
index 8fd7236..c22625ea 100755
--- a/net/tools/testserver/testserver.py
+++ b/net/tools/testserver/testserver.py
@@ -2193,6 +2193,7 @@
'fallback. 1 means all TLS versions will be '
'aborted. 2 means TLS 1.1 or higher will be '
'aborted. 3 means TLS 1.2 or higher will be '
+ 'aborted. 4 means TLS 1.3 or higher will be '
'aborted.')
self.option_parser.add_option('--tls-intolerance-type',
dest='tls_intolerance_type',
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()
diff --git a/third_party/tlslite/README.chromium b/third_party/tlslite/README.chromium
index c2d1f27..c6104f5 100644
--- a/third_party/tlslite/README.chromium
+++ b/third_party/tlslite/README.chromium
@@ -56,3 +56,4 @@
- patches/token_binding_version.patch: Update Token Binding version number.
- patches/renegotiation_indication.patch: Implement the renegotiation
indication extension (RFC 5746) without supporting renegotiation.
+- patches/tls13_intolerance.patch: Extend the intolerance simulation to TLS 1.3.
diff --git a/third_party/tlslite/patches/tls13_intolerance.patch b/third_party/tlslite/patches/tls13_intolerance.patch
new file mode 100644
index 0000000..6f19571
--- /dev/null
+++ b/third_party/tlslite/patches/tls13_intolerance.patch
@@ -0,0 +1,66 @@
+diff --git a/third_party/tlslite/tlslite/constants.py b/third_party/tlslite/tlslite/constants.py
+index 82e8c075fe2a..8fb75d0948e4 100644
+--- a/third_party/tlslite/tlslite/constants.py
++++ b/third_party/tlslite/tlslite/constants.py
+@@ -58,6 +58,7 @@ class ExtensionType: # RFC 6066 / 4366
+ signed_cert_timestamps = 18 # RFC 6962
+ extended_master_secret = 23 # RFC 7627
+ token_binding = 24 # draft-ietf-tokbind-negotiation
++ supported_versions = 43 # draft-ietf-tls-tls13-18
+ tack = 0xF300
+ supports_npn = 13172
+ channel_id = 30032
+diff --git a/third_party/tlslite/tlslite/messages.py b/third_party/tlslite/tlslite/messages.py
+index ac7e563021d9..b29db939c2a8 100644
+--- a/third_party/tlslite/tlslite/messages.py
++++ b/third_party/tlslite/tlslite/messages.py
+@@ -140,6 +140,7 @@ class ClientHello(HandshakeMsg):
+ self.tb_client_params = []
+ self.support_signed_cert_timestamps = False
+ self.status_request = False
++ self.has_supported_versions = False
+ self.ri = False
+
+ def create(self, version, random, session_id, cipher_suites,
+@@ -251,6 +252,11 @@ class ClientHello(HandshakeMsg):
+ if extLength != 1 or p.getFixBytes(extLength)[0] != 0:
+ raise SyntaxError()
+ self.ri = True
++ elif extType == ExtensionType.supported_versions:
++ # Ignore the extension, but make a note of it for
++ # intolerance simulation.
++ self.has_supported_versions = True
++ _ = p.getFixBytes(extLength)
+ else:
+ _ = p.getFixBytes(extLength)
+ index2 = p.index
+diff --git a/third_party/tlslite/tlslite/tlsconnection.py b/third_party/tlslite/tlslite/tlsconnection.py
+index 8ba1c6e636ab..2309d4fa8f3a 100644
+--- a/third_party/tlslite/tlslite/tlsconnection.py
++++ b/third_party/tlslite/tlslite/tlsconnection.py
+@@ -1457,6 +1457,15 @@ class TLSConnection(TLSRecordLayer):
+ self._handshakeDone(resumed=False)
+
+
++ def _isIntolerant(self, settings, clientHello):
++ if settings.tlsIntolerant is None:
++ return False
++ clientVersion = clientHello.client_version
++ if clientHello.has_supported_versions:
++ clientVersion = (3, 4)
++ return clientVersion >= settings.tlsIntolerant
++
++
+ def _serverGetClientHello(self, settings, certChain, verifierDB,
+ sessionCache, anon, fallbackSCSV):
+ #Tentatively set version to most-desirable version, so if an error
+@@ -1480,8 +1489,7 @@ class TLSConnection(TLSRecordLayer):
+ yield result
+
+ #If simulating TLS intolerance, reject certain TLS versions.
+- elif (settings.tlsIntolerant is not None and
+- clientHello.client_version >= settings.tlsIntolerant):
++ elif self._isIntolerant(settings, clientHello):
+ if settings.tlsIntoleranceType == "alert":
+ for result in self._sendError(\
+ AlertDescription.handshake_failure):
diff --git a/third_party/tlslite/tlslite/constants.py b/third_party/tlslite/tlslite/constants.py
index 82e8c075..8fb75d0 100644
--- a/third_party/tlslite/tlslite/constants.py
+++ b/third_party/tlslite/tlslite/constants.py
@@ -58,6 +58,7 @@
signed_cert_timestamps = 18 # RFC 6962
extended_master_secret = 23 # RFC 7627
token_binding = 24 # draft-ietf-tokbind-negotiation
+ supported_versions = 43 # draft-ietf-tls-tls13-18
tack = 0xF300
supports_npn = 13172
channel_id = 30032
diff --git a/third_party/tlslite/tlslite/messages.py b/third_party/tlslite/tlslite/messages.py
index ac7e563..b29db93 100644
--- a/third_party/tlslite/tlslite/messages.py
+++ b/third_party/tlslite/tlslite/messages.py
@@ -140,6 +140,7 @@
self.tb_client_params = []
self.support_signed_cert_timestamps = False
self.status_request = False
+ self.has_supported_versions = False
self.ri = False
def create(self, version, random, session_id, cipher_suites,
@@ -251,6 +252,11 @@
if extLength != 1 or p.getFixBytes(extLength)[0] != 0:
raise SyntaxError()
self.ri = True
+ elif extType == ExtensionType.supported_versions:
+ # Ignore the extension, but make a note of it for
+ # intolerance simulation.
+ self.has_supported_versions = True
+ _ = p.getFixBytes(extLength)
else:
_ = p.getFixBytes(extLength)
index2 = p.index
diff --git a/third_party/tlslite/tlslite/tlsconnection.py b/third_party/tlslite/tlslite/tlsconnection.py
index 8ba1c6e..2309d4f 100644
--- a/third_party/tlslite/tlslite/tlsconnection.py
+++ b/third_party/tlslite/tlslite/tlsconnection.py
@@ -1457,6 +1457,15 @@
self._handshakeDone(resumed=False)
+ def _isIntolerant(self, settings, clientHello):
+ if settings.tlsIntolerant is None:
+ return False
+ clientVersion = clientHello.client_version
+ if clientHello.has_supported_versions:
+ clientVersion = (3, 4)
+ return clientVersion >= settings.tlsIntolerant
+
+
def _serverGetClientHello(self, settings, certChain, verifierDB,
sessionCache, anon, fallbackSCSV):
#Tentatively set version to most-desirable version, so if an error
@@ -1480,8 +1489,7 @@
yield result
#If simulating TLS intolerance, reject certain TLS versions.
- elif (settings.tlsIntolerant is not None and
- clientHello.client_version >= settings.tlsIntolerant):
+ elif self._isIntolerant(settings, clientHello):
if settings.tlsIntoleranceType == "alert":
for result in self._sendError(\
AlertDescription.handshake_failure):
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index 9d3890930..9935710 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -38277,6 +38277,22 @@
</summary>
</histogram>
+<histogram name="Net.SSLVersionInterferenceError" enum="NetErrorCodes">
+ <owner>[email protected]</owner>
+ <summary>
+ For each detected SSL version interference, what network error the original
+ failed connection reported.
+ </summary>
+</histogram>
+
+<histogram name="Net.SSLVersionInterferenceProbeTrigger" enum="NetErrorCodes">
+ <owner>[email protected]</owner>
+ <summary>
+ For each SSL version interference probe, what network error triggered it.
+ Probes are only triggered for a small set of network errors.
+ </summary>
+</histogram>
+
<histogram name="Net.TCP_Connection_Idle_Sockets">
<obsolete>
Deprecated 04/2016 as doesn't have data nor owner.
@@ -86427,6 +86443,8 @@
<int value="-202" label="CERT_AUTHORITY_INVALID"/>
<int value="-201" label="CERT_DATE_INVALID"/>
<int value="-200" label="CERT_COMMON_NAME_INVALID"/>
+ <int value="-175" label="SSL_VERSION_INTERFERENCE"/>
+ <int value="-174" label="READ_IF_READY_NOT_IMPLEMENTED"/>
<int value="-173" label="WS_UPGRADE"/>
<int value="-172" label="SSL_OBSOLETE_CIPHER"/>
<int value="-171" label="CT_CONSISTENCY_PROOF_PARSING_FAILED"/>
@@ -104344,6 +104362,8 @@
<int value="171" label="CT_CONSISTENCY_PROOF_PARSING_FAILED"/>
<int value="172" label="SSL_OBSOLETE_CIPHER"/>
<int value="173" label="WS_UPGRADE"/>
+ <int value="174" label="READ_IF_READY_NOT_IMPLEMENTED"/>
+ <int value="175" label="SSL_VERSION_INTERFERENCE"/>
<int value="200" label="CERT_COMMON_NAME_INVALID"/>
<int value="201" label="CERT_DATE_INVALID"/>
<int value="202" label="CERT_AUTHORITY_INVALID"/>