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-Original-Commit-Position: refs/heads/master@{#464173}
Cr-Mirrored-From: https://chromium.googlesource.com/chromium/src
Cr-Mirrored-Commit: 095ebb57de0053925c4900bace0458f38bf5e051
diff --git a/README.chromium b/README.chromium
index c2d1f27..c6104f5 100644
--- a/README.chromium
+++ b/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/patches/tls13_intolerance.patch b/patches/tls13_intolerance.patch
new file mode 100644
index 0000000..6f19571
--- /dev/null
+++ b/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/tlslite/constants.py b/tlslite/constants.py
index 82e8c07..8fb75d0 100644
--- a/tlslite/constants.py
+++ b/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/tlslite/messages.py b/tlslite/messages.py
index ac7e563..b29db93 100644
--- a/tlslite/messages.py
+++ b/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/tlslite/tlsconnection.py b/tlslite/tlsconnection.py
index 8ba1c6e..2309d4f 100644
--- a/tlslite/tlsconnection.py
+++ b/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):