Moving TLS 1.3 version negotiation into extension.

Change-Id: I73f9fd64b46f26978b897409d817b34ec9d93afd
Reviewed-on: https://boringssl-review.googlesource.com/11080
Reviewed-by: Steven Valdez <svaldez@google.com>
Reviewed-by: David Benjamin <davidben@google.com>
Commit-Queue: David Benjamin <davidben@google.com>
CQ-Verified: CQ bot account: commit-bot@chromium.org <commit-bot@chromium.org>
diff --git a/include/openssl/ssl.h b/include/openssl/ssl.h
index 3cf4e03..b0242be 100644
--- a/include/openssl/ssl.h
+++ b/include/openssl/ssl.h
@@ -562,7 +562,7 @@
 #define DTLS1_VERSION 0xfeff
 #define DTLS1_2_VERSION 0xfefd
 
-#define TLS1_3_DRAFT_VERSION 14
+#define TLS1_3_DRAFT_VERSION 0x7f0e
 
 /* SSL_CTX_set_min_proto_version sets the minimum protocol version for |ctx| to
  * |version|. If |version| is zero, the default minimum version is used. It
diff --git a/include/openssl/tls1.h b/include/openssl/tls1.h
index 3c97d26..eca5dec 100644
--- a/include/openssl/tls1.h
+++ b/include/openssl/tls1.h
@@ -209,16 +209,9 @@
 #define TLSEXT_TYPE_key_share 40
 #define TLSEXT_TYPE_pre_shared_key 41
 #define TLSEXT_TYPE_early_data 42
+#define TLSEXT_TYPE_supported_versions 43
 #define TLSEXT_TYPE_cookie 44
 
-/* TLSEXT_TYPE_draft_version is the extension used to advertise the TLS 1.3
- * draft implemented.
- *
- * See
- * https://github.com/tlswg/tls13-spec/wiki/Implementations#version-negotiation
- */
-#define TLSEXT_TYPE_draft_version 0xff02
-
 /* ExtensionType value from RFC5746 */
 #define TLSEXT_TYPE_renegotiate 0xff01
 
diff --git a/ssl/handshake_client.c b/ssl/handshake_client.c
index 584e9ea..c5b3b1f 100644
--- a/ssl/handshake_client.c
+++ b/ssl/handshake_client.c
@@ -731,6 +731,10 @@
      * key exchange, the ClientHello version is checked in the premaster secret.
      * Some servers fail when this value changes. */
     ssl->client_version = ssl->version;
+
+    if (max_version >= TLS1_3_VERSION) {
+      ssl->client_version = ssl->method->version_to_wire(TLS1_2_VERSION);
+    }
   }
 
   /* If the configured session has expired or was created at a disabled
diff --git a/ssl/handshake_server.c b/ssl/handshake_server.c
index f797585..fd0223f 100644
--- a/ssl/handshake_server.c
+++ b/ssl/handshake_server.c
@@ -564,38 +564,75 @@
     return 0;
   }
 
-  /* For TLS versions which use ClientHello.version, convert it to a version we
-   * are aware of. */
   uint16_t version = 0;
-  if (SSL_is_dtls(ssl)) {
-    if (client_hello->version <= DTLS1_2_VERSION) {
-      version = TLS1_2_VERSION;
-    } else if (client_hello->version <= DTLS1_VERSION) {
-      version = TLS1_1_VERSION;
+  /* Check supported_versions extension if it is present. */
+  CBS supported_versions;
+  if (ssl_early_callback_get_extension(client_hello, &supported_versions,
+                                       TLSEXT_TYPE_supported_versions)) {
+    CBS versions;
+    if (!CBS_get_u8_length_prefixed(&supported_versions, &versions) ||
+        CBS_len(&supported_versions) != 0 ||
+        CBS_len(&versions) == 0) {
+      OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
+      *out_alert = SSL_AD_DECODE_ERROR;
+      return 0;
+    }
+
+    int found_version = 0;
+    while (CBS_len(&versions) != 0) {
+      uint16_t ext_version;
+      if (!CBS_get_u16(&versions, &ext_version)) {
+        OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
+        *out_alert = SSL_AD_DECODE_ERROR;
+        return 0;
+      }
+      if (!ssl->method->version_from_wire(&ext_version, ext_version)) {
+        continue;
+      }
+      if (min_version <= ext_version &&
+          ext_version <= max_version) {
+        version = ext_version;
+        found_version = 1;
+        break;
+      }
+    }
+
+    if (!found_version) {
+      goto unsupported_protocol;
     }
   } else {
-    if (client_hello->version >= TLS1_3_VERSION) {
-      version = TLS1_3_VERSION;
-    } else if (client_hello->version >= TLS1_2_VERSION) {
-      version = TLS1_2_VERSION;
-    } else if (client_hello->version >= TLS1_1_VERSION) {
-      version = TLS1_1_VERSION;
-    } else if (client_hello->version >= TLS1_VERSION) {
-      version = TLS1_VERSION;
-    } else if (client_hello->version >= SSL3_VERSION) {
-      version = SSL3_VERSION;
+    /* Process ClientHello.version instead. Note that versions beyond (D)TLS 1.2
+     * do not use this mechanism. */
+    if (SSL_is_dtls(ssl)) {
+      if (client_hello->version <= DTLS1_2_VERSION) {
+        version = TLS1_2_VERSION;
+      } else if (client_hello->version <= DTLS1_VERSION) {
+        version = TLS1_1_VERSION;
+      } else {
+        goto unsupported_protocol;
+      }
+    } else {
+      if (client_hello->version >= TLS1_2_VERSION) {
+        version = TLS1_2_VERSION;
+      } else if (client_hello->version >= TLS1_1_VERSION) {
+        version = TLS1_1_VERSION;
+      } else if (client_hello->version >= TLS1_VERSION) {
+        version = TLS1_VERSION;
+      } else if (client_hello->version >= SSL3_VERSION) {
+        version = SSL3_VERSION;
+      } else {
+        goto unsupported_protocol;
+      }
     }
-  }
 
-  /* Apply our minimum and maximum version. */
-  if (version > max_version) {
-    version = max_version;
-  }
+    /* Apply our minimum and maximum version. */
+    if (version > max_version) {
+      version = max_version;
+    }
 
-  if (version < min_version) {
-    OPENSSL_PUT_ERROR(SSL, SSL_R_UNSUPPORTED_PROTOCOL);
-    *out_alert = SSL_AD_PROTOCOL_VERSION;
-    return 0;
+    if (version < min_version) {
+      goto unsupported_protocol;
+    }
   }
 
   /* Handle FALLBACK_SCSV. */
@@ -617,6 +654,11 @@
   ssl->s3->have_version = 1;
 
   return 1;
+
+unsupported_protocol:
+  OPENSSL_PUT_ERROR(SSL, SSL_R_UNSUPPORTED_PROTOCOL);
+  *out_alert = SSL_AD_PROTOCOL_VERSION;
+  return 0;
 }
 
 static int ssl3_get_client_hello(SSL *ssl) {
diff --git a/ssl/ssl_asn1.c b/ssl/ssl_asn1.c
index 6395a00..ba3b10e 100644
--- a/ssl/ssl_asn1.c
+++ b/ssl/ssl_asn1.c
@@ -543,12 +543,6 @@
     OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_SSL_SESSION);
     goto err;
   }
-  /* Only support SSLv3/TLS and DTLS. */
-  if ((ssl_version >> 8) != SSL3_VERSION_MAJOR &&
-      (ssl_version >> 8) != (DTLS1_VERSION >> 8)) {
-    OPENSSL_PUT_ERROR(SSL, SSL_R_UNKNOWN_SSL_VERSION);
-    goto err;
-  }
   ret->ssl_version = ssl_version;
 
   CBS cipher;
diff --git a/ssl/ssl_lib.c b/ssl/ssl_lib.c
index 51c16f0..63f72ca 100644
--- a/ssl/ssl_lib.c
+++ b/ssl/ssl_lib.c
@@ -951,6 +951,10 @@
     return 1;
   }
 
+  if (version == TLS1_3_VERSION) {
+    version = TLS1_3_DRAFT_VERSION;
+  }
+
   return method->version_from_wire(out, version);
 }
 
@@ -965,6 +969,10 @@
     return 1;
   }
 
+  if (version == TLS1_3_VERSION) {
+    version = TLS1_3_DRAFT_VERSION;
+  }
+
   return method->version_from_wire(out, version);
 }
 
@@ -2109,7 +2117,8 @@
 
 static const char *ssl_get_version(int version) {
   switch (version) {
-    case TLS1_3_VERSION:
+    /* Report TLS 1.3 draft version as TLS 1.3 in the public API. */
+    case TLS1_3_DRAFT_VERSION:
       return "TLSv1.3";
 
     case TLS1_2_VERSION:
@@ -2271,7 +2280,14 @@
   return ret;
 }
 
-int SSL_version(const SSL *ssl) { return ssl->version; }
+int SSL_version(const SSL *ssl) {
+  /* Report TLS 1.3 draft version as TLS 1.3 in the public API. */
+  if (ssl->version == TLS1_3_DRAFT_VERSION) {
+    return TLS1_3_VERSION;
+  }
+
+  return ssl->version;
+}
 
 SSL_CTX *SSL_get_SSL_CTX(const SSL *ssl) { return ssl->ctx; }
 
@@ -2962,7 +2978,7 @@
       version = 0;
       break;
     default:
-      version = ssl->version;
+      version = SSL_version(ssl);
   }
 
   ssl->msg_callback(is_write, version, content_type, buf, len, ssl,
diff --git a/ssl/t1_lib.c b/ssl/t1_lib.c
index baa2d45..281fc71 100644
--- a/ssl/t1_lib.c
+++ b/ssl/t1_lib.c
@@ -1966,27 +1966,6 @@
   return ext_ec_point_add_extension(ssl, out);
 }
 
-
-/* Draft Version Extension */
-
-static int ext_draft_version_add_clienthello(SSL *ssl, CBB *out) {
-  uint16_t min_version, max_version;
-  if (!ssl_get_version_range(ssl, &min_version, &max_version) ||
-      max_version < TLS1_3_VERSION) {
-    return 1;
-  }
-
-  CBB contents;
-  if (!CBB_add_u16(out, TLSEXT_TYPE_draft_version) ||
-      !CBB_add_u16_length_prefixed(out, &contents) ||
-      !CBB_add_u16(&contents, TLS1_3_DRAFT_VERSION)) {
-    return 0;
-  }
-
-  return CBB_flush(out);
-}
-
-
 /* Pre Shared Key
  *
  * https://tools.ietf.org/html/draft-ietf-tls-tls13-14 */
@@ -2279,6 +2258,41 @@
 }
 
 
+/* Supported Versions
+ *
+ * https://tools.ietf.org/html/draft-ietf-tls-tls13-16#section-4.2.1 */
+
+static int ext_supported_versions_add_clienthello(SSL *ssl, CBB *out) {
+  uint16_t min_version, max_version;
+  if (!ssl_get_version_range(ssl, &min_version, &max_version)) {
+    return 0;
+  }
+
+  if (max_version <= TLS1_2_VERSION) {
+    return 1;
+  }
+
+  CBB contents, versions;
+  if (!CBB_add_u16(out, TLSEXT_TYPE_supported_versions) ||
+      !CBB_add_u16_length_prefixed(out, &contents) ||
+      !CBB_add_u8_length_prefixed(&contents, &versions)) {
+    return 0;
+  }
+
+  for (uint16_t version = max_version; version >= min_version; version--) {
+    if (!CBB_add_u16(&versions, ssl->method->version_to_wire(version))) {
+      return 0;
+    }
+  }
+
+  if (!CBB_flush(out)) {
+    return 0;
+  }
+
+  return 1;
+}
+
+
 /* Negotiated Groups
  *
  * https://tools.ietf.org/html/rfc4492#section-5.1.2
@@ -2476,14 +2490,6 @@
     ext_ec_point_add_serverhello,
   },
   {
-    TLSEXT_TYPE_draft_version,
-    NULL,
-    ext_draft_version_add_clienthello,
-    forbid_parse_serverhello,
-    ignore_parse_clienthello,
-    dont_add_serverhello,
-  },
-  {
     TLSEXT_TYPE_key_share,
     NULL,
     ext_key_share_add_clienthello,
@@ -2499,6 +2505,14 @@
     ignore_parse_clienthello,
     dont_add_serverhello,
   },
+  {
+    TLSEXT_TYPE_supported_versions,
+    NULL,
+    ext_supported_versions_add_clienthello,
+    forbid_parse_serverhello,
+    ignore_parse_clienthello,
+    dont_add_serverhello,
+  },
   /* The final extension must be non-empty. WebSphere Application Server 7.0 is
    * intolerant to the last extension being zero-length. See
    * https://crbug.com/363583. */
diff --git a/ssl/test/runner/common.go b/ssl/test/runner/common.go
index e35d2f5..1ef79e6 100644
--- a/ssl/test/runner/common.go
+++ b/ssl/test/runner/common.go
@@ -26,9 +26,8 @@
 	VersionTLS13 = 0x0304
 )
 
-// The draft version of TLS 1.3 that is implemented here and sent in the draft
-// indicator extension.
-const tls13DraftVersion = 14
+// A draft version of TLS 1.3 that is sent over the wire for the current draft.
+const tls13DraftVersion = 0x7f0e
 
 const (
 	maxPlaintext        = 16384        // maximum plaintext payload length
@@ -93,11 +92,11 @@
 	extensionKeyShare                   uint16 = 40    // draft-ietf-tls-tls13-13
 	extensionPreSharedKey               uint16 = 41    // draft-ietf-tls-tls13-13
 	extensionEarlyData                  uint16 = 42    // draft-ietf-tls-tls13-13
+	extensionSupportedVersions          uint16 = 43    // draft-ietf-tls-tls13-16
 	extensionCookie                     uint16 = 44    // draft-ietf-tls-tls13-13
 	extensionCustom                     uint16 = 1234  // not IANA assigned
 	extensionNextProtoNeg               uint16 = 13172 // not IANA assigned
 	extensionRenegotiationInfo          uint16 = 0xff01
-	extensionTLS13Draft                 uint16 = 0xff02
 	extensionChannelID                  uint16 = 30032 // not IANA assigned
 )
 
@@ -601,6 +600,14 @@
 	// specified value in the ClientHello version field.
 	SendClientVersion uint16
 
+	// OmitSupportedVersions, if true, causes the client to omit the
+	// supported versions extension.
+	OmitSupportedVersions bool
+
+	// SendSupportedVersions, if non-empty, causes the client to send a
+	// supported versions extension with the values from array.
+	SendSupportedVersions []uint16
+
 	// NegotiateVersion, if non-zero, causes the server to negotiate the
 	// specifed TLS version rather than the version supported by either
 	// peer.
@@ -1188,24 +1195,10 @@
 	return defaultCurves
 }
 
-// mutualVersion returns the protocol version to use given the advertised
-// version of the peer.
-func (c *Config) mutualVersion(vers uint16, isDTLS bool) (uint16, bool) {
-	// There is no such thing as DTLS 1.1.
-	if isDTLS && vers == VersionTLS11 {
-		vers = VersionTLS10
-	}
-
-	minVersion := c.minVersion(isDTLS)
-	maxVersion := c.maxVersion(isDTLS)
-
-	if vers < minVersion {
-		return 0, false
-	}
-	if vers > maxVersion {
-		vers = maxVersion
-	}
-	return vers, true
+// isSupportedVersion returns true if the specified protocol version is
+// acceptable.
+func (c *Config) isSupportedVersion(vers uint16, isDTLS bool) bool {
+	return c.minVersion(isDTLS) <= vers && vers <= c.maxVersion(isDTLS)
 }
 
 // getCertificateForName returns the best certificate for the given name,
diff --git a/ssl/test/runner/dtls.go b/ssl/test/runner/dtls.go
index ca7a51d..e273bc7 100644
--- a/ssl/test/runner/dtls.go
+++ b/ssl/test/runner/dtls.go
@@ -33,8 +33,10 @@
 		}
 	} else {
 		switch vers {
-		case VersionSSL30, VersionTLS10, VersionTLS11, VersionTLS12, VersionTLS13:
+		case VersionSSL30, VersionTLS10, VersionTLS11, VersionTLS12:
 			return vers
+		case VersionTLS13:
+			return tls13DraftVersion
 		}
 	}
 
@@ -51,8 +53,10 @@
 		}
 	} else {
 		switch vers {
-		case VersionSSL30, VersionTLS10, VersionTLS11, VersionTLS12, VersionTLS13:
+		case VersionSSL30, VersionTLS10, VersionTLS11, VersionTLS12:
 			return vers, true
+		case tls13DraftVersion:
+			return VersionTLS13, true
 		}
 	}
 
diff --git a/ssl/test/runner/handshake_client.go b/ssl/test/runner/handshake_client.go
index cb24153..167fabf 100644
--- a/ssl/test/runner/handshake_client.go
+++ b/ssl/test/runner/handshake_client.go
@@ -57,6 +57,7 @@
 		return errors.New("tls: NextProtos values too large")
 	}
 
+	minVersion := c.config.minVersion(c.isDTLS)
 	maxVersion := c.config.maxVersion(c.isDTLS)
 	hello := &clientHelloMsg{
 		isDTLS:                  c.isDTLS,
@@ -74,7 +75,7 @@
 		duplicateExtension:      c.config.Bugs.DuplicateExtension,
 		channelIDSupported:      c.config.ChannelID != nil,
 		npnLast:                 c.config.Bugs.SwapNPNAndALPN,
-		extendedMasterSecret:    c.config.maxVersion(c.isDTLS) >= VersionTLS10,
+		extendedMasterSecret:    maxVersion >= VersionTLS10,
 		srtpProtectionProfiles:  c.config.SRTPProtectionProfiles,
 		srtpMasterKeyIdentifier: c.config.Bugs.SRTPMasterKeyIdentifer,
 		customExtension:         c.config.Bugs.CustomExtension,
@@ -235,8 +236,8 @@
 				}
 			}
 
-			versOk := candidateSession.vers >= c.config.minVersion(c.isDTLS) &&
-				candidateSession.vers <= c.config.maxVersion(c.isDTLS)
+			versOk := candidateSession.vers >= minVersion &&
+				candidateSession.vers <= maxVersion
 			if ticketOk && versOk && cipherSuiteOk {
 				session = candidateSession
 			}
@@ -285,6 +286,19 @@
 		}
 	}
 
+	if maxVersion == VersionTLS13 && !c.config.Bugs.OmitSupportedVersions {
+		if hello.vers >= VersionTLS13 {
+			hello.vers = VersionTLS12
+		}
+		for version := maxVersion; version >= minVersion; version-- {
+			hello.supportedVersions = append(hello.supportedVersions, versionToWire(version, c.isDTLS))
+		}
+	}
+
+	if len(c.config.Bugs.SendSupportedVersions) > 0 {
+		hello.supportedVersions = c.config.Bugs.SendSupportedVersions
+	}
+
 	if c.config.Bugs.SendClientVersion != 0 {
 		hello.vers = c.config.Bugs.SendClientVersion
 	}
@@ -366,12 +380,13 @@
 
 	serverVersion, ok := wireToVersion(serverWireVersion, c.isDTLS)
 	if ok {
-		c.vers, ok = c.config.mutualVersion(serverVersion, c.isDTLS)
+		ok = c.config.isSupportedVersion(serverVersion, c.isDTLS)
 	}
 	if !ok {
 		c.sendAlert(alertProtocolVersion)
 		return fmt.Errorf("tls: server selected unsupported protocol version %x", c.vers)
 	}
+	c.vers = serverVersion
 	c.haveVers = true
 
 	helloRetryRequest, haveHelloRetryRequest := msg.(*helloRetryRequestMsg)
diff --git a/ssl/test/runner/handshake_messages.go b/ssl/test/runner/handshake_messages.go
index 6538dc6..0b45a4d 100644
--- a/ssl/test/runner/handshake_messages.go
+++ b/ssl/test/runner/handshake_messages.go
@@ -149,6 +149,7 @@
 	ticketSupported         bool
 	sessionTicket           []uint8
 	signatureAlgorithms     []signatureAlgorithm
+	supportedVersions       []uint16
 	secureRenegotiation     []byte
 	alpnProtocols           []string
 	duplicateExtension      bool
@@ -190,6 +191,7 @@
 		m.ticketSupported == m1.ticketSupported &&
 		bytes.Equal(m.sessionTicket, m1.sessionTicket) &&
 		eqSignatureAlgorithms(m.signatureAlgorithms, m1.signatureAlgorithms) &&
+		eqUint16s(m.supportedVersions, m1.supportedVersions) &&
 		bytes.Equal(m.secureRenegotiation, m1.secureRenegotiation) &&
 		(m.secureRenegotiation == nil) == (m1.secureRenegotiation == nil) &&
 		eqStrings(m.alpnProtocols, m1.alpnProtocols) &&
@@ -340,6 +342,14 @@
 			signatureAlgorithms.addU16(uint16(sigAlg))
 		}
 	}
+	if len(m.supportedVersions) > 0 {
+		extensions.addU16(extensionSupportedVersions)
+		supportedVersionsExtension := extensions.addU16LengthPrefixed()
+		supportedVersions := supportedVersionsExtension.addU8LengthPrefixed()
+		for _, version := range m.supportedVersions {
+			supportedVersions.addU16(uint16(version))
+		}
+	}
 	if m.secureRenegotiation != nil {
 		extensions.addU16(extensionRenegotiationInfo)
 		secureRenegoExt := extensions.addU16LengthPrefixed()
@@ -400,11 +410,6 @@
 		customExt := extensions.addU16LengthPrefixed()
 		customExt.addBytes([]byte(m.customExtension))
 	}
-	if m.vers == VersionTLS13 {
-		extensions.addU16(extensionTLS13Draft)
-		extValue := extensions.addU16LengthPrefixed()
-		extValue.addU16(tls13DraftVersion)
-	}
 
 	if extensions.len() == 0 {
 		hello.discardChild()
@@ -477,6 +482,7 @@
 	m.ticketSupported = false
 	m.sessionTicket = nil
 	m.signatureAlgorithms = nil
+	m.supportedVersions = nil
 	m.alpnProtocols = nil
 	m.extendedMasterSecret = false
 	m.customExtension = ""
@@ -645,6 +651,21 @@
 				m.signatureAlgorithms[i] = signatureAlgorithm(d[0])<<8 | signatureAlgorithm(d[1])
 				d = d[2:]
 			}
+		case extensionSupportedVersions:
+			if length < 1+2 {
+				return false
+			}
+			l := int(data[0])
+			if l != length-1 || l%2 == 1 || l < 2 {
+				return false
+			}
+			n := l / 2
+			d := data[1:]
+			m.supportedVersions = make([]uint16, n)
+			for i := range m.supportedVersions {
+				m.supportedVersions[i] = uint16(d[0])<<8 | uint16(d[1])
+				d = d[2:]
+			}
 		case extensionRenegotiationInfo:
 			if length < 1 || length != int(data[0])+1 {
 				return false
diff --git a/ssl/test/runner/handshake_server.go b/ssl/test/runner/handshake_server.go
index 85d4ca8..3f166ec 100644
--- a/ssl/test/runner/handshake_server.go
+++ b/ssl/test/runner/handshake_server.go
@@ -204,7 +204,10 @@
 			return fmt.Errorf("tls: client offered different version on renego")
 		}
 	}
+
 	c.clientVersion = hs.clientHello.vers
+
+	// Convert the ClientHello wire version to a protocol version.
 	var clientVersion uint16
 	if c.isDTLS {
 		if hs.clientHello.vers <= 0xfefd {
@@ -213,9 +216,7 @@
 			clientVersion = VersionTLS10
 		}
 	} else {
-		if hs.clientHello.vers >= VersionTLS13 {
-			clientVersion = VersionTLS13
-		} else if hs.clientHello.vers >= VersionTLS12 {
+		if hs.clientHello.vers >= VersionTLS12 {
 			clientVersion = VersionTLS12
 		} else if hs.clientHello.vers >= VersionTLS11 {
 			clientVersion = VersionTLS11
@@ -230,12 +231,34 @@
 		c.vers = config.Bugs.NegotiateVersion
 	} else if c.haveVers && config.Bugs.NegotiateVersionOnRenego != 0 {
 		c.vers = config.Bugs.NegotiateVersionOnRenego
-	} else {
-		c.vers, ok = config.mutualVersion(clientVersion, c.isDTLS)
-		if !ok {
+	} else if len(hs.clientHello.supportedVersions) > 0 {
+		// Use the versions extension if supplied.
+		var foundVersion bool
+		for _, extVersion := range hs.clientHello.supportedVersions {
+			extVersion, ok = wireToVersion(extVersion, c.isDTLS)
+			if !ok {
+				continue
+			}
+			if config.isSupportedVersion(extVersion, c.isDTLS) {
+				c.vers = extVersion
+				foundVersion = true
+				break
+			}
+		}
+		if !foundVersion {
 			c.sendAlert(alertProtocolVersion)
+			return errors.New("tls: client did not offer any supported protocol versions")
+		}
+	} else {
+		// Otherwise, use the legacy ClientHello version.
+		version := clientVersion
+		if maxVersion := config.maxVersion(c.isDTLS); version > maxVersion {
+			version = maxVersion
+		}
+		if version == 0 || !config.isSupportedVersion(version, c.isDTLS) {
 			return fmt.Errorf("tls: client offered an unsupported, maximum protocol version of %x", hs.clientHello.vers)
 		}
+		c.vers = version
 	}
 	c.haveVers = true
 
@@ -500,7 +523,7 @@
 		ResendHelloRetryRequest:
 			// Send HelloRetryRequest.
 			helloRetryRequestMsg := helloRetryRequestMsg{
-				vers:          c.vers,
+				vers:          versionToWire(c.vers, c.isDTLS),
 				cipherSuite:   hs.hello.cipherSuite,
 				selectedGroup: selectedCurve,
 			}
diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go
index 19f67b1..ec20947 100644
--- a/ssl/test/runner/runner.go
+++ b/ssl/test/runner/runner.go
@@ -4084,6 +4084,7 @@
 			flags = append(flags, vers.flag)
 		}
 
+		// Test configuring the runner's maximum version.
 		for _, runnerVers := range tlsVersions {
 			protocols := []protocol{tls}
 			if runnerVers.hasDTLS && shimVers.hasDTLS {
@@ -4171,34 +4172,117 @@
 		}
 	}
 
+	// Test the version extension at all versions.
+	for _, vers := range tlsVersions {
+		protocols := []protocol{tls}
+		if vers.hasDTLS {
+			protocols = append(protocols, dtls)
+		}
+		for _, protocol := range protocols {
+			suffix := vers.name
+			if protocol == dtls {
+				suffix += "-DTLS"
+			}
+
+			wireVersion := versionToWire(vers.version, protocol == dtls)
+			testCases = append(testCases, testCase{
+				protocol: protocol,
+				testType: serverTest,
+				name:     "VersionNegotiationExtension-" + suffix,
+				config: Config{
+					Bugs: ProtocolBugs{
+						SendSupportedVersions: []uint16{0x1111, wireVersion, 0x2222},
+					},
+				},
+				expectedVersion: vers.version,
+			})
+		}
+
+	}
+
+	// If all versions are unknown, negotiation fails.
+	testCases = append(testCases, testCase{
+		testType: serverTest,
+		name:     "NoSupportedVersions",
+		config: Config{
+			Bugs: ProtocolBugs{
+				SendSupportedVersions: []uint16{0x1111},
+			},
+		},
+		shouldFail:    true,
+		expectedError: ":UNSUPPORTED_PROTOCOL:",
+	})
+	testCases = append(testCases, testCase{
+		protocol: dtls,
+		testType: serverTest,
+		name:     "NoSupportedVersions-DTLS",
+		config: Config{
+			Bugs: ProtocolBugs{
+				SendSupportedVersions: []uint16{0x1111},
+			},
+		},
+		shouldFail:    true,
+		expectedError: ":UNSUPPORTED_PROTOCOL:",
+	})
+
+	testCases = append(testCases, testCase{
+		testType: serverTest,
+		name:     "ClientHelloVersionTooHigh",
+		config: Config{
+			MaxVersion: VersionTLS13,
+			Bugs: ProtocolBugs{
+				SendClientVersion:     0x0304,
+				OmitSupportedVersions: true,
+			},
+		},
+		expectedVersion: VersionTLS12,
+	})
+
+	testCases = append(testCases, testCase{
+		testType: serverTest,
+		name:     "ConflictingVersionNegotiation",
+		config: Config{
+			MaxVersion: VersionTLS13,
+			Bugs: ProtocolBugs{
+				SendClientVersion:     0x0304,
+				SendSupportedVersions: []uint16{0x0303},
+			},
+		},
+		expectedVersion: VersionTLS12,
+	})
+
 	// Test for version tolerance.
 	testCases = append(testCases, testCase{
 		testType: serverTest,
 		name:     "MinorVersionTolerance",
 		config: Config{
 			Bugs: ProtocolBugs{
-				SendClientVersion: 0x03ff,
+				SendClientVersion:     0x03ff,
+				OmitSupportedVersions: true,
 			},
 		},
-		expectedVersion: VersionTLS13,
+		expectedVersion: VersionTLS12,
 	})
 	testCases = append(testCases, testCase{
 		testType: serverTest,
 		name:     "MajorVersionTolerance",
 		config: Config{
 			Bugs: ProtocolBugs{
-				SendClientVersion: 0x0400,
+				SendClientVersion:     0x0400,
+				OmitSupportedVersions: true,
 			},
 		},
-		expectedVersion: VersionTLS13,
+		expectedVersion: VersionTLS12,
 	})
+
 	testCases = append(testCases, testCase{
 		protocol: dtls,
 		testType: serverTest,
 		name:     "MinorVersionTolerance-DTLS",
 		config: Config{
 			Bugs: ProtocolBugs{
-				SendClientVersion: 0xfe00,
+				SendClientVersion:     0xfe00,
+				OmitSupportedVersions: true,
 			},
 		},
 		expectedVersion: VersionTLS12,
@@ -4209,7 +4293,8 @@
 		name:     "MajorVersionTolerance-DTLS",
 		config: Config{
 			Bugs: ProtocolBugs{
-				SendClientVersion: 0xfdff,
+				SendClientVersion:     0xfdff,
+				OmitSupportedVersions: true,
 			},
 		},
 		expectedVersion: VersionTLS12,
@@ -4221,7 +4306,8 @@
 		name:     "VersionTooLow",
 		config: Config{
 			Bugs: ProtocolBugs{
-				SendClientVersion: 0x0200,
+				SendClientVersion:     0x0200,
+				OmitSupportedVersions: true,
 			},
 		},
 		shouldFail:    true,
@@ -4317,12 +4403,20 @@
 					}
 				}
 
+				// Test the client enforces minimum
+				// versions. Use the NegotiateVersion bug to
+				// ensure the server does not decline to select
+				// a version first. This may occur if the
+				// versions extension is used.
 				testCases = append(testCases, testCase{
 					protocol: protocol,
 					testType: clientTest,
 					name:     "MinimumVersion-Client-" + suffix,
 					config: Config{
 						MaxVersion: runnerVers.version,
+						Bugs: ProtocolBugs{
+							NegotiateVersion: runnerVers.version,
+						},
 					},
 					flags:              flags,
 					expectedVersion:    expectedVersion,
@@ -4336,6 +4430,9 @@
 					name:     "MinimumVersion-Client2-" + suffix,
 					config: Config{
 						MaxVersion: runnerVers.version,
+						Bugs: ProtocolBugs{
+							NegotiateVersion: runnerVers.version,
+						},
 					},
 					flags:              []string{"-min-version", shimVersFlag},
 					expectedVersion:    expectedVersion,
diff --git a/ssl/tls_method.c b/ssl/tls_method.c
index 155d09a..8bcdf8f 100644
--- a/ssl/tls_method.c
+++ b/ssl/tls_method.c
@@ -71,9 +71,11 @@
     case TLS1_VERSION:
     case TLS1_1_VERSION:
     case TLS1_2_VERSION:
-    case TLS1_3_VERSION:
       *out_version = wire_version;
       return 1;
+    case TLS1_3_DRAFT_VERSION:
+      *out_version = TLS1_3_VERSION;
+      return 1;
   }
 
   return 0;
@@ -85,8 +87,9 @@
     case TLS1_VERSION:
     case TLS1_1_VERSION:
     case TLS1_2_VERSION:
-    case TLS1_3_VERSION:
       return version;
+    case TLS1_3_VERSION:
+      return TLS1_3_DRAFT_VERSION;
   }
 
   /* It is an error to use this function with an invalid version. */