Implement PR 1091 (TLS 1.3 draft '22').

This introduces a wire change to Experiment2/Experiment3 over 0RTT, however
as there is never going to be a 0RTT deployment with Experiment2/Experiment3,
this is valid.

Change-Id: Id541d195cbc4bbb3df7680ae2a02b53bb8ae3eab
Reviewed-on: https://boringssl-review.googlesource.com/22744
Commit-Queue: David Benjamin <davidben@google.com>
Reviewed-by: David Benjamin <davidben@google.com>
Reviewed-by: Steven Valdez <svaldez@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 3540a25..8c36ad5 100644
--- a/include/openssl/ssl.h
+++ b/include/openssl/ssl.h
@@ -593,6 +593,7 @@
 
 #define TLS1_3_DRAFT_VERSION 0x7f12
 #define TLS1_3_DRAFT21_VERSION 0x7f15
+#define TLS1_3_DRAFT22_VERSION 0x7e04
 #define TLS1_3_EXPERIMENT_VERSION 0x7e01
 #define TLS1_3_EXPERIMENT2_VERSION 0x7e02
 #define TLS1_3_EXPERIMENT3_VERSION 0x7e03
@@ -3255,6 +3256,7 @@
   tls13_experiment2 = 2,
   tls13_experiment3 = 3,
   tls13_draft21 = 4,
+  tls13_draft22 = 5,
 };
 
 // SSL_CTX_set_tls13_variant sets which variant of TLS 1.3 we negotiate. On the
diff --git a/ssl/handshake.cc b/ssl/handshake.cc
index ed11484..3b446a8 100644
--- a/ssl/handshake.cc
+++ b/ssl/handshake.cc
@@ -127,6 +127,7 @@
       scts_requested(false),
       needs_psk_binder(false),
       received_hello_retry_request(false),
+      sent_hello_retry_request(false),
       received_custom_extension(false),
       handshake_finalized(false),
       accept_psk_mode(false),
diff --git a/ssl/handshake_client.cc b/ssl/handshake_client.cc
index f907939..583aceb 100644
--- a/ssl/handshake_client.cc
+++ b/ssl/handshake_client.cc
@@ -500,6 +500,12 @@
     return ssl_hs_ok;
   }
 
+  ssl->s3->aead_write_ctx->SetVersionIfNullCipher(ssl->session->ssl_version);
+  if (ssl_is_draft22(ssl->session->ssl_version) &&
+      !ssl->method->add_change_cipher_spec(ssl)) {
+    return ssl_hs_error;
+  }
+
   if (!tls13_init_early_key_schedule(hs, ssl->session->master_key,
                                      ssl->session->master_key_length) ||
       !tls13_derive_early_secrets(hs) ||
diff --git a/ssl/internal.h b/ssl/internal.h
index 55bece9..6c14383 100644
--- a/ssl/internal.h
+++ b/ssl/internal.h
@@ -396,6 +396,10 @@
 // variant.
 bool ssl_is_draft21(uint16_t version);
 
+// ssl_is_draft22 returns whether the version corresponds to a draft22 TLS 1.3
+// variant.
+bool ssl_is_draft22(uint16_t version);
+
 // ssl_is_resumption_experiment returns whether the version corresponds to a
 // TLS 1.3 resumption experiment.
 bool ssl_is_resumption_experiment(uint16_t version);
@@ -1004,6 +1008,8 @@
 // Channel ID, are all enabled.
 #define SSL_MAX_HANDSHAKE_FLIGHT 7
 
+extern const uint8_t kHelloRetryRequest[SSL3_RANDOM_SIZE];
+
 // ssl_max_handshake_message_len returns the maximum number of bytes permitted
 // in a handshake message for |ssl|.
 size_t ssl_max_handshake_message_len(const SSL *ssl);
@@ -1438,6 +1444,7 @@
   bool needs_psk_binder:1;
 
   bool received_hello_retry_request:1;
+  bool sent_hello_retry_request:1;
 
   bool received_custom_extension:1;
 
diff --git a/ssl/ssl_versions.cc b/ssl/ssl_versions.cc
index 1f30b41..a356ed1 100644
--- a/ssl/ssl_versions.cc
+++ b/ssl/ssl_versions.cc
@@ -36,6 +36,7 @@
 
     case TLS1_3_DRAFT_VERSION:
     case TLS1_3_DRAFT21_VERSION:
+    case TLS1_3_DRAFT22_VERSION:
     case TLS1_3_EXPERIMENT_VERSION:
     case TLS1_3_EXPERIMENT2_VERSION:
     case TLS1_3_EXPERIMENT3_VERSION:
@@ -60,6 +61,7 @@
 // decreasing preference.
 
 static const uint16_t kTLSVersions[] = {
+    TLS1_3_DRAFT22_VERSION,
     TLS1_3_EXPERIMENT3_VERSION,
     TLS1_3_EXPERIMENT2_VERSION,
     TLS1_3_EXPERIMENT_VERSION,
@@ -109,6 +111,7 @@
   switch (version) {
     case TLS1_3_DRAFT_VERSION:
     case TLS1_3_DRAFT21_VERSION:
+    case TLS1_3_DRAFT22_VERSION:
     case TLS1_3_EXPERIMENT_VERSION:
     case TLS1_3_EXPERIMENT2_VERSION:
     case TLS1_3_EXPERIMENT3_VERSION:
@@ -142,6 +145,7 @@
     // Report TLS 1.3 draft versions as TLS 1.3 in the public API.
     case TLS1_3_DRAFT_VERSION:
     case TLS1_3_DRAFT21_VERSION:
+    case TLS1_3_DRAFT22_VERSION:
     case TLS1_3_EXPERIMENT_VERSION:
     case TLS1_3_EXPERIMENT2_VERSION:
     case TLS1_3_EXPERIMENT3_VERSION:
@@ -157,6 +161,7 @@
 static bool api_version_to_wire(uint16_t *out, uint16_t version) {
   if (version == TLS1_3_DRAFT_VERSION ||
       version == TLS1_3_DRAFT21_VERSION ||
+      version == TLS1_3_DRAFT22_VERSION ||
       version == TLS1_3_EXPERIMENT_VERSION ||
       version == TLS1_3_EXPERIMENT2_VERSION ||
       version == TLS1_3_EXPERIMENT3_VERSION) {
@@ -324,6 +329,8 @@
        version == TLS1_3_EXPERIMENT3_VERSION) ||
       (ssl->tls13_variant == tls13_draft21 &&
        version == TLS1_3_DRAFT21_VERSION) ||
+      (ssl->tls13_variant == tls13_draft22 &&
+       version == TLS1_3_DRAFT22_VERSION) ||
       (ssl->tls13_variant == tls13_default &&
        version == TLS1_3_DRAFT_VERSION)) {
     return true;
@@ -389,28 +396,35 @@
 }
 
 bool ssl_is_draft21(uint16_t version) {
-  return version == TLS1_3_DRAFT21_VERSION;
+  return version == TLS1_3_DRAFT21_VERSION || version == TLS1_3_DRAFT22_VERSION;
+}
+
+bool ssl_is_draft22(uint16_t version) {
+  return version == TLS1_3_DRAFT22_VERSION;
 }
 
 bool ssl_is_resumption_experiment(uint16_t version) {
   return version == TLS1_3_EXPERIMENT_VERSION ||
          version == TLS1_3_EXPERIMENT2_VERSION ||
-         version == TLS1_3_EXPERIMENT3_VERSION;
+         version == TLS1_3_EXPERIMENT3_VERSION ||
+         version == TLS1_3_DRAFT22_VERSION;
 }
 
 bool ssl_is_resumption_variant(enum tls13_variant_t variant) {
   return variant == tls13_experiment || variant == tls13_experiment2 ||
-         variant == tls13_experiment3;
+         variant == tls13_experiment3 || variant == tls13_draft22;
 }
 
 bool ssl_is_resumption_client_ccs_experiment(uint16_t version) {
   return version == TLS1_3_EXPERIMENT_VERSION ||
-         version == TLS1_3_EXPERIMENT2_VERSION;
+         version == TLS1_3_EXPERIMENT2_VERSION ||
+         version == TLS1_3_DRAFT22_VERSION;
 }
 
 bool ssl_is_resumption_record_version_experiment(uint16_t version) {
   return version == TLS1_3_EXPERIMENT2_VERSION ||
-         version == TLS1_3_EXPERIMENT3_VERSION;
+         version == TLS1_3_EXPERIMENT3_VERSION ||
+         version == TLS1_3_DRAFT22_VERSION;
 }
 
 }  // namespace bssl
diff --git a/ssl/test/runner/common.go b/ssl/test/runner/common.go
index 9e0deef..4564b0f 100644
--- a/ssl/test/runner/common.go
+++ b/ssl/test/runner/common.go
@@ -38,6 +38,7 @@
 	tls13ExperimentVersion  = 0x7e01
 	tls13Experiment2Version = 0x7e02
 	tls13Experiment3Version = 0x7e03
+	tls13Draft22Version     = 0x7e04
 )
 
 const (
@@ -46,10 +47,12 @@
 	TLS13Experiment2 = 2
 	TLS13Experiment3 = 3
 	TLS13Draft21     = 4
+	TLS13Draft22     = 5
 )
 
 var allTLSWireVersions = []uint16{
 	tls13DraftVersion,
+	tls13Draft22Version,
 	tls13Draft21Version,
 	tls13Experiment3Version,
 	tls13Experiment2Version,
@@ -148,6 +151,12 @@
 	scsvRenegotiation uint16 = 0x00ff
 )
 
+var tls13HelloRetryRequest = []uint8{
+	0xcf, 0x21, 0xad, 0x74, 0xe5, 0x9a, 0x61, 0x11, 0xbe, 0x1d, 0x8c,
+	0x02, 0x1e, 0x65, 0xb8, 0x91, 0xc2, 0xa2, 0x11, 0x16, 0x7a, 0xbb,
+	0x8c, 0x5e, 0x07, 0x9e, 0x09, 0xe2, 0xc8, 0xa8, 0x33, 0x9c,
+}
+
 // CurveID is the type of a TLS identifier for an elliptic curve. See
 // http://www.iana.org/assignments/tls-parameters/tls-parameters.xml#tls-parameters-8
 type CurveID uint16
@@ -619,6 +628,10 @@
 	// messages.
 	FragmentAcrossChangeCipherSpec bool
 
+	// SendExtraChangeCipherSpec causes the implementation to send extra
+	// ChangeCipherSpec messages.
+	SendExtraChangeCipherSpec int
+
 	// SendUnencryptedFinished, if true, causes the Finished message to be
 	// send unencrypted before ChangeCipherSpec rather than after it.
 	SendUnencryptedFinished bool
@@ -1605,7 +1618,7 @@
 		switch vers {
 		case VersionSSL30, VersionTLS10, VersionTLS11, VersionTLS12:
 			return vers, true
-		case tls13DraftVersion, tls13Draft21Version, tls13ExperimentVersion, tls13Experiment2Version, tls13Experiment3Version:
+		case tls13DraftVersion, tls13Draft22Version, tls13Draft21Version, tls13ExperimentVersion, tls13Experiment2Version, tls13Experiment3Version:
 			return VersionTLS13, true
 		}
 	}
@@ -1614,19 +1627,27 @@
 }
 
 func isDraft21(vers uint16) bool {
-	return vers == tls13Draft21Version
+	return vers == tls13Draft21Version || vers == tls13Draft22Version
+}
+
+func isDraft22(vers uint16) bool {
+	return vers == tls13Draft22Version
 }
 
 func isResumptionExperiment(vers uint16) bool {
-	return vers == tls13ExperimentVersion || vers == tls13Experiment2Version || vers == tls13Experiment3Version
+	return vers == tls13ExperimentVersion || vers == tls13Experiment2Version || vers == tls13Experiment3Version || vers == tls13Draft22Version
 }
 
 func isResumptionClientCCSExperiment(vers uint16) bool {
-	return vers == tls13ExperimentVersion || vers == tls13Experiment2Version
+	return vers == tls13ExperimentVersion || vers == tls13Experiment2Version || vers == tls13Draft22Version
 }
 
 func isResumptionRecordVersionExperiment(vers uint16) bool {
-	return vers == tls13Experiment2Version || vers == tls13Experiment3Version
+	return vers == tls13Experiment2Version || vers == tls13Experiment3Version || vers == tls13Draft22Version
+}
+
+func isResumptionRecordVersionVariant(variant int) bool {
+	return variant == TLS13Experiment2 || variant == TLS13Experiment3 || variant == TLS13Draft22
 }
 
 // isSupportedVersion checks if the specified wire version is acceptable. If so,
@@ -1636,6 +1657,7 @@
 	if (c.TLS13Variant != TLS13Experiment && wireVers == tls13ExperimentVersion) ||
 		(c.TLS13Variant != TLS13Experiment2 && wireVers == tls13Experiment2Version) ||
 		(c.TLS13Variant != TLS13Experiment3 && wireVers == tls13Experiment3Version) ||
+		(c.TLS13Variant != TLS13Draft22 && wireVers == tls13Draft22Version) ||
 		(c.TLS13Variant != TLS13Draft21 && wireVers == tls13Draft21Version) ||
 		(c.TLS13Variant != TLS13Default && wireVers == tls13DraftVersion) {
 		return 0, false
diff --git a/ssl/test/runner/conn.go b/ssl/test/runner/conn.go
index 6a2c59c..5359462 100644
--- a/ssl/test/runner/conn.go
+++ b/ssl/test/runner/conn.go
@@ -39,6 +39,7 @@
 	vers                 uint16     // TLS version
 	haveVers             bool       // version has been negotiated
 	config               *Config    // configuration passed to constructor
+	hadHelloRetryRequest bool
 	handshakeComplete    bool
 	skipEarlyData        bool // On a server, indicates that the client is sending early data that must be skipped over.
 	didResume            bool // whether this connection was a session resumption
@@ -1152,7 +1153,6 @@
 		}
 		if isResumptionRecordVersionExperiment(c.wireVersion) || isResumptionRecordVersionExperiment(c.out.wireVersion) {
 			vers = VersionTLS12
-		} else {
 		}
 
 		if c.config.Bugs.SendRecordVersion != 0 {
@@ -1322,6 +1322,14 @@
 	// so pass in a fresh copy that won't be overwritten.
 	data = append([]byte(nil), data...)
 
+	if data[0] == typeServerHello && len(data) >= 38 {
+		vers := uint16(data[4])<<8 | uint16(data[5])
+		if vers == VersionTLS12 && bytes.Equal(data[6:38], tls13HelloRetryRequest) {
+			m = new(helloRetryRequestMsg)
+			m.(*helloRetryRequestMsg).isServerHello = true
+		}
+	}
+
 	if !m.unmarshal(data) {
 		return nil, c.in.setErrorLocked(c.sendAlert(alertUnexpectedMessage))
 	}
@@ -1936,7 +1944,7 @@
 	payload[0] = byte(recordTypeApplicationData)
 	payload[1] = 3
 	payload[2] = 1
-	if c.config.TLS13Variant == TLS13Experiment2 || c.config.TLS13Variant == TLS13Experiment3 {
+	if isResumptionRecordVersionVariant(c.config.TLS13Variant) {
 		payload[1] = 3
 		payload[2] = 3
 	}
diff --git a/ssl/test/runner/handshake_client.go b/ssl/test/runner/handshake_client.go
index ba34647..7c8dbb5 100644
--- a/ssl/test/runner/handshake_client.go
+++ b/ssl/test/runner/handshake_client.go
@@ -419,6 +419,12 @@
 			earlyLabel = earlyTrafficLabelDraft21
 		}
 
+		if !c.config.Bugs.SkipChangeCipherSpec && isDraft22(session.wireVersion) {
+			c.wireVersion = session.wireVersion
+			c.writeRecord(recordTypeChangeCipherSpec, []byte{1})
+			c.wireVersion = 0
+		}
+
 		earlyTrafficSecret := finishedHash.deriveSecret(earlyLabel)
 		c.useOutTrafficSecret(session.wireVersion, pskCipherSuite, earlyTrafficSecret)
 		for _, earlyData := range c.config.Bugs.SendEarlyData {
@@ -482,6 +488,13 @@
 	helloRetryRequest, haveHelloRetryRequest := msg.(*helloRetryRequestMsg)
 	var secondHelloBytes []byte
 	if haveHelloRetryRequest {
+		c.hadHelloRetryRequest = true
+		if isDraft22(c.wireVersion) {
+			if err := c.readRecord(recordTypeChangeCipherSpec); err != nil {
+				return err
+			}
+		}
+
 		c.out.resetCipher()
 		if len(helloRetryRequest.cookie) > 0 {
 			hello.tls13Cookie = helloRetryRequest.cookie
@@ -761,7 +774,7 @@
 		hs.finishedHash.addEntropy(zeroSecret)
 	}
 
-	if isResumptionExperiment(c.wireVersion) {
+	if isResumptionExperiment(c.wireVersion) && !c.hadHelloRetryRequest {
 		if err := c.readRecord(recordTypeChangeCipherSpec); err != nil {
 			return err
 		}
@@ -979,7 +992,11 @@
 		}
 	}
 
-	if isResumptionClientCCSExperiment(c.wireVersion) {
+	if !c.config.Bugs.SkipChangeCipherSpec && isResumptionClientCCSExperiment(c.wireVersion) && !hs.hello.hasEarlyData {
+		c.writeRecord(recordTypeChangeCipherSpec, []byte{1})
+	}
+
+	for i := 0; i < c.config.Bugs.SendExtraChangeCipherSpec; i++ {
 		c.writeRecord(recordTypeChangeCipherSpec, []byte{1})
 	}
 
diff --git a/ssl/test/runner/handshake_messages.go b/ssl/test/runner/handshake_messages.go
index aa6b463..d5e3951 100644
--- a/ssl/test/runner/handshake_messages.go
+++ b/ssl/test/runner/handshake_messages.go
@@ -1406,6 +1406,8 @@
 type helloRetryRequestMsg struct {
 	raw                 []byte
 	vers                uint16
+	isServerHello       bool
+	sessionId           []byte
 	cipherSuite         uint16
 	hasSelectedGroup    bool
 	selectedGroup       CurveID
@@ -1420,11 +1422,25 @@
 	}
 
 	retryRequestMsg := newByteBuilder()
-	retryRequestMsg.addU8(typeHelloRetryRequest)
+	if isDraft22(m.vers) {
+		retryRequestMsg.addU8(typeServerHello)
+	} else {
+		retryRequestMsg.addU8(typeHelloRetryRequest)
+	}
 	retryRequest := retryRequestMsg.addU24LengthPrefixed()
-	retryRequest.addU16(m.vers)
-	if isDraft21(m.vers) {
+
+	if isDraft22(m.vers) {
+		retryRequest.addU16(VersionTLS12)
+		retryRequest.addBytes(tls13HelloRetryRequest)
+		sessionId := retryRequest.addU8LengthPrefixed()
+		sessionId.addBytes(m.sessionId)
 		retryRequest.addU16(m.cipherSuite)
+		retryRequest.addU8(0)
+	} else {
+		retryRequest.addU16(m.vers)
+		if isDraft21(m.vers) {
+			retryRequest.addU16(m.cipherSuite)
+		}
 	}
 	extensions := retryRequest.addU16LengthPrefixed()
 
@@ -1434,6 +1450,11 @@
 	}
 
 	for i := 0; i < count; i++ {
+		if isDraft22(m.vers) {
+			extensions.addU16(extensionSupportedVersions)
+			extensions.addU16(2) // Length
+			extensions.addU16(m.vers)
+		}
 		if m.hasSelectedGroup {
 			extensions.addU16(extensionKeyShare)
 			extensions.addU16(2) // length
@@ -1461,9 +1482,25 @@
 	}
 	m.vers = uint16(data[4])<<8 | uint16(data[5])
 	data = data[6:]
-	if isDraft21(m.vers) {
+	if m.isServerHello {
+		if len(data) < 33 {
+			return false
+		}
+		data = data[32:] // Random
+		sessionIdLen := int(data[0])
+		if sessionIdLen > 32 || len(data) < 1+sessionIdLen+3 {
+			return false
+		}
+		m.sessionId = data[1 : 1+sessionIdLen]
+		data = data[1+sessionIdLen:]
 		m.cipherSuite = uint16(data[0])<<8 | uint16(data[1])
 		data = data[2:]
+		data = data[1:] // Compression Method
+	} else {
+		if isDraft21(m.vers) {
+			m.cipherSuite = uint16(data[0])<<8 | uint16(data[1])
+			data = data[2:]
+		}
 	}
 	extLen := int(data[0])<<8 | int(data[1])
 	data = data[2:]
@@ -1482,6 +1519,11 @@
 		}
 
 		switch extension {
+		case extensionSupportedVersions:
+			if length != 2 || !m.isServerHello {
+				return false
+			}
+			m.vers = uint16(data[0])<<8 | uint16(data[1])
 		case extensionKeyShare:
 			if length != 2 {
 				return false
diff --git a/ssl/test/runner/handshake_server.go b/ssl/test/runner/handshake_server.go
index dd6f48f..2513a00 100644
--- a/ssl/test/runner/handshake_server.go
+++ b/ssl/test/runner/handshake_server.go
@@ -526,6 +526,7 @@
 	}
 	helloRetryRequest := &helloRetryRequestMsg{
 		vers:                c.wireVersion,
+		sessionId:           hs.clientHello.sessionId,
 		cipherSuite:         cipherSuite,
 		duplicateExtensions: config.Bugs.DuplicateHelloRetryRequestExtensions,
 	}
@@ -587,6 +588,10 @@
 		c.writeRecord(recordTypeHandshake, helloRetryRequest.marshal())
 		c.flushHandshake()
 
+		if !c.config.Bugs.SkipChangeCipherSpec && isDraft22(c.wireVersion) {
+			c.writeRecord(recordTypeChangeCipherSpec, []byte{1})
+		}
+
 		if hs.clientHello.hasEarlyData {
 			c.skipEarlyData = true
 		}
@@ -686,6 +691,12 @@
 				earlyLabel = earlyTrafficLabelDraft21
 			}
 
+			if isDraft22(c.wireVersion) {
+				if err := c.readRecord(recordTypeChangeCipherSpec); err != nil {
+					return err
+				}
+			}
+
 			earlyTrafficSecret := hs.finishedHash.deriveSecret(earlyLabel)
 			if err := c.useInTrafficSecret(c.wireVersion, hs.suite, earlyTrafficSecret); err != nil {
 				return err
@@ -782,7 +793,11 @@
 	}
 	c.flushHandshake()
 
-	if isResumptionExperiment(c.wireVersion) {
+	if !c.config.Bugs.SkipChangeCipherSpec && isResumptionExperiment(c.wireVersion) && !sendHelloRetryRequest {
+		c.writeRecord(recordTypeChangeCipherSpec, []byte{1})
+	}
+
+	for i := 0; i < c.config.Bugs.SendExtraChangeCipherSpec; i++ {
 		c.writeRecord(recordTypeChangeCipherSpec, []byte{1})
 	}
 
@@ -982,7 +997,7 @@
 		}
 	}
 
-	if isResumptionClientCCSExperiment(c.wireVersion) && !c.skipEarlyData {
+	if isResumptionClientCCSExperiment(c.wireVersion) && !c.skipEarlyData && !encryptedExtensions.extensions.hasEarlyData {
 		if err := c.readRecord(recordTypeChangeCipherSpec); err != nil {
 			return err
 		}
diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go
index ce50fa8..b6aa4a1 100644
--- a/ssl/test/runner/runner.go
+++ b/ssl/test/runner/runner.go
@@ -1312,6 +1312,13 @@
 		tls13Variant: TLS13Draft21,
 	},
 	{
+		name:         "TLS13Draft22",
+		version:      VersionTLS13,
+		excludeFlag:  "-no-tls13",
+		versionWire:  tls13Draft22Version,
+		tls13Variant: TLS13Draft22,
+	},
+	{
 		name:         "TLS13Experiment",
 		version:      VersionTLS13,
 		excludeFlag:  "-no-tls13",
@@ -4160,6 +4167,37 @@
 		})
 
 		tests = append(tests, testCase{
+			name: "TLS13Draft22-HelloRetryRequest-Client",
+			config: Config{
+				MaxVersion: VersionTLS13,
+				MinVersion: VersionTLS13,
+				// P-384 requires a HelloRetryRequest against BoringSSL's default
+				// configuration. Assert this with ExpectMissingKeyShare.
+				CurvePreferences: []CurveID{CurveP384},
+				Bugs: ProtocolBugs{
+					ExpectMissingKeyShare: true,
+				},
+			},
+			tls13Variant: TLS13Draft22,
+			// Cover HelloRetryRequest during an ECDHE-PSK resumption.
+			resumeSession: true,
+		})
+
+		tests = append(tests, testCase{
+			testType: serverTest,
+			name:     "TLS13Draft22-HelloRetryRequest-Server",
+			config: Config{
+				MaxVersion: VersionTLS13,
+				MinVersion: VersionTLS13,
+				// Require a HelloRetryRequest for every curve.
+				DefaultCurves: []CurveID{},
+			},
+			tls13Variant: TLS13Draft22,
+			// Cover HelloRetryRequest during an ECDHE-PSK resumption.
+			resumeSession: true,
+		})
+
+		tests = append(tests, testCase{
 			testType: clientTest,
 			name:     "TLS13-EarlyData-TooMuchData-Client",
 			config: Config{
@@ -5196,7 +5234,7 @@
 				serverVers := expectedServerVersion
 				if expectedServerVersion >= VersionTLS13 {
 					serverVers = VersionTLS10
-					if runnerVers.tls13Variant == TLS13Experiment2 || runnerVers.tls13Variant == TLS13Experiment3 {
+					if runnerVers.tls13Variant == TLS13Experiment2 || runnerVers.tls13Variant == TLS13Experiment3 || runnerVers.tls13Variant == TLS13Draft22 {
 						serverVers = VersionTLS12
 					}
 				}
@@ -11734,6 +11772,58 @@
 		expectedError: ":UNEXPECTED_EXTENSION:",
 	})
 
+	testCases = append(testCases, testCase{
+		testType: clientTest,
+		name: "TLS13Draft22-SkipChangeCipherSpec-Client",
+		config: Config{
+			MaxVersion: VersionTLS13,
+			Bugs: ProtocolBugs{
+				SkipChangeCipherSpec: true,
+			},
+		},
+		tls13Variant: TLS13Draft22,
+	})
+
+	testCases = append(testCases, testCase{
+		testType: serverTest,
+		name: "TLS13Draft22-SkipChangeCipherSpec-Server",
+		config: Config{
+			MaxVersion: VersionTLS13,
+			Bugs: ProtocolBugs{
+				SkipChangeCipherSpec: true,
+			},
+		},
+		tls13Variant: TLS13Draft22,
+	})
+
+	testCases = append(testCases, testCase{
+		testType: clientTest,
+		name: "TLS13Draft22-TooManyChangeCipherSpec-Client",
+		config: Config{
+			MaxVersion: VersionTLS13,
+			Bugs: ProtocolBugs{
+				SendExtraChangeCipherSpec: 33,
+			},
+		},
+		tls13Variant: TLS13Draft22,
+		shouldFail:       true,
+		expectedError:    ":TOO_MANY_EMPTY_FRAGMENTS:",
+	})
+
+	testCases = append(testCases, testCase{
+		testType: serverTest,
+		name: "TLS13Draft22-TooManyChangeCipherSpec-Server",
+		config: Config{
+			MaxVersion: VersionTLS13,
+			Bugs: ProtocolBugs{
+				SendExtraChangeCipherSpec: 33,
+			},
+		},
+		tls13Variant: TLS13Draft22,
+		shouldFail:       true,
+		expectedError:    ":TOO_MANY_EMPTY_FRAGMENTS:",
+	})
+
 	fooString := "foo"
 	barString := "bar"
 
diff --git a/ssl/tls13_both.cc b/ssl/tls13_both.cc
index 5796bf4..57acbcb 100644
--- a/ssl/tls13_both.cc
+++ b/ssl/tls13_both.cc
@@ -37,6 +37,12 @@
 // without being able to return application data.
 static const uint8_t kMaxKeyUpdates = 32;
 
+const uint8_t kHelloRetryRequest[SSL3_RANDOM_SIZE] = {
+    0xcf, 0x21, 0xad, 0x74, 0xe5, 0x9a, 0x61, 0x11, 0xbe, 0x1d, 0x8c,
+    0x02, 0x1e, 0x65, 0xb8, 0x91, 0xc2, 0xa2, 0x11, 0x16, 0x7a, 0xbb,
+    0x8c, 0x5e, 0x07, 0x9e, 0x09, 0xe2, 0xc8, 0xa8, 0x33, 0x9c,
+};
+
 bool tls13_get_cert_verify_signature_input(
     SSL_HANDSHAKE *hs, Array<uint8_t> *out,
     enum ssl_cert_verify_context_t cert_verify_context) {
diff --git a/ssl/tls13_client.cc b/ssl/tls13_client.cc
index 9c09309..0013e61 100644
--- a/ssl/tls13_client.cc
+++ b/ssl/tls13_client.cc
@@ -36,7 +36,6 @@
   state_read_hello_retry_request = 0,
   state_send_second_client_hello,
   state_read_server_hello,
-  state_process_change_cipher_spec,
   state_read_encrypted_extensions,
   state_read_certificate_request,
   state_read_server_certificate,
@@ -57,23 +56,52 @@
   if (!ssl->method->get_message(ssl, &msg)) {
     return ssl_hs_read_message;
   }
-  if (msg.type != SSL3_MT_HELLO_RETRY_REQUEST) {
-    hs->tls13_state = state_read_server_hello;
-    return ssl_hs_ok;
-  }
 
-  CBS body = msg.body, extensions;
-  uint16_t server_version, cipher_suite = 0;
-  if (!CBS_get_u16(&body, &server_version) ||
-      (ssl_is_draft21(ssl->version) &&
-       !CBS_get_u16(&body, &cipher_suite)) ||
-      !CBS_get_u16_length_prefixed(&body, &extensions) ||
-      // HelloRetryRequest may not be empty.
-      CBS_len(&extensions) == 0 ||
-      CBS_len(&body) != 0) {
-    OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
-    ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
-    return ssl_hs_error;
+  CBS extensions;
+  uint16_t cipher_suite = 0;
+  if (ssl_is_draft22(ssl->version)) {
+    if (!ssl_check_message_type(ssl, msg, SSL3_MT_SERVER_HELLO)) {
+      return ssl_hs_error;
+    }
+
+    CBS body = msg.body, server_random, session_id;
+    uint16_t server_version;
+    if (!CBS_get_u16(&body, &server_version) ||
+        !CBS_get_bytes(&body, &server_random, SSL3_RANDOM_SIZE) ||
+        !CBS_get_u8_length_prefixed(&body, &session_id) ||
+        !CBS_get_u16(&body, &cipher_suite) ||
+        !CBS_skip(&body, 1) ||
+        !CBS_get_u16_length_prefixed(&body, &extensions) ||
+        CBS_len(&extensions) == 0 ||
+        CBS_len(&body) != 0) {
+      OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
+      ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
+      return ssl_hs_error;
+    }
+
+    if (!CBS_mem_equal(&server_random, kHelloRetryRequest, SSL3_RANDOM_SIZE)) {
+      hs->tls13_state = state_read_server_hello;
+      return ssl_hs_ok;
+    }
+  } else {
+    if (msg.type != SSL3_MT_HELLO_RETRY_REQUEST) {
+      hs->tls13_state = state_read_server_hello;
+      return ssl_hs_ok;
+    }
+
+    CBS body = msg.body;
+    uint16_t server_version;
+    if (!CBS_get_u16(&body, &server_version) ||
+        (ssl_is_draft21(ssl->version) &&
+         !CBS_get_u16(&body, &cipher_suite)) ||
+        !CBS_get_u16_length_prefixed(&body, &extensions) ||
+        // HelloRetryRequest may not be empty.
+        CBS_len(&extensions) == 0 ||
+        CBS_len(&body) != 0) {
+      OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
+      ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
+      return ssl_hs_error;
+    }
   }
 
   if (ssl_is_draft21(ssl->version)) {
@@ -96,11 +124,13 @@
   }
 
 
-  bool have_cookie, have_key_share;
-  CBS cookie, key_share;
+  bool have_cookie, have_key_share, have_supported_versions;
+  CBS cookie, key_share, supported_versions;
   const SSL_EXTENSION_TYPE ext_types[] = {
       {TLSEXT_TYPE_key_share, &have_key_share, &key_share},
       {TLSEXT_TYPE_cookie, &have_cookie, &cookie},
+      {TLSEXT_TYPE_supported_versions, &have_supported_versions,
+       &supported_versions},
   };
 
   uint8_t alert = SSL_AD_DECODE_ERROR;
@@ -111,6 +141,11 @@
     return ssl_hs_error;
   }
 
+  if (!ssl_is_draft22(ssl->version) && have_supported_versions) {
+    OPENSSL_PUT_ERROR(SSL, SSL_R_UNEXPECTED_EXTENSION);
+    ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_UNSUPPORTED_EXTENSION);
+    return ssl_hs_error;
+  }
   if (have_cookie) {
     CBS cookie_value;
     if (!CBS_get_u16_length_prefixed(&cookie, &cookie_value) ||
@@ -359,20 +394,8 @@
 
   if (!tls13_advance_key_schedule(hs, dhe_secret.data(), dhe_secret.size()) ||
       !ssl_hash_message(hs, msg) ||
-      !tls13_derive_handshake_secrets(hs)) {
-    return ssl_hs_error;
-  }
-
-  ssl->method->next_message(ssl);
-  hs->tls13_state = state_process_change_cipher_spec;
-  return ssl_is_resumption_experiment(ssl->version)
-             ? ssl_hs_read_change_cipher_spec
-             : ssl_hs_ok;
-}
-
-static enum ssl_hs_wait_t do_process_change_cipher_spec(SSL_HANDSHAKE *hs) {
-  SSL *const ssl = hs->ssl;
-  if (!tls13_set_traffic_key(ssl, evp_aead_open, hs->server_handshake_secret,
+      !tls13_derive_handshake_secrets(hs) ||
+      !tls13_set_traffic_key(ssl, evp_aead_open, hs->server_handshake_secret,
                              hs->hash_len)) {
     return ssl_hs_error;
   }
@@ -388,6 +411,7 @@
     }
   }
 
+  ssl->method->next_message(ssl);
   hs->tls13_state = state_read_encrypted_extensions;
   return ssl_hs_ok;
 }
@@ -642,9 +666,7 @@
   }
 
   if (hs->early_data_offered) {
-    if ((ssl_is_resumption_client_ccs_experiment(ssl->version) &&
-         !ssl->method->add_change_cipher_spec(ssl)) ||
-        !tls13_set_traffic_key(ssl, evp_aead_seal, hs->client_handshake_secret,
+    if (!tls13_set_traffic_key(ssl, evp_aead_seal, hs->client_handshake_secret,
                                hs->hash_len)) {
       return ssl_hs_error;
     }
@@ -767,9 +789,6 @@
       case state_read_server_hello:
         ret = do_read_server_hello(hs);
         break;
-      case state_process_change_cipher_spec:
-        ret = do_process_change_cipher_spec(hs);
-        break;
       case state_read_encrypted_extensions:
         ret = do_read_encrypted_extensions(hs);
         break;
@@ -824,8 +843,6 @@
       return "TLS 1.3 client send_second_client_hello";
     case state_read_server_hello:
       return "TLS 1.3 client read_server_hello";
-    case state_process_change_cipher_spec:
-      return "TLS 1.3 client process_change_cipher_spec";
     case state_read_encrypted_extensions:
       return "TLS 1.3 client read_encrypted_extensions";
     case state_read_certificate_request:
diff --git a/ssl/tls13_server.cc b/ssl/tls13_server.cc
index 9afd0d4..1040ace 100644
--- a/ssl/tls13_server.cc
+++ b/ssl/tls13_server.cc
@@ -47,7 +47,6 @@
   state_send_server_certificate_verify,
   state_send_server_finished,
   state_read_second_client_flight,
-  state_process_change_cipher_spec,
   state_process_end_of_early_data,
   state_read_client_certificate,
   state_read_client_certificate_verify,
@@ -490,23 +489,55 @@
 
 static enum ssl_hs_wait_t do_send_hello_retry_request(SSL_HANDSHAKE *hs) {
   SSL *const ssl = hs->ssl;
-  ScopedCBB cbb;
-  CBB body, extensions;
-  uint16_t group_id;
-  if (!ssl->method->init_message(ssl, cbb.get(), &body,
-                                 SSL3_MT_HELLO_RETRY_REQUEST) ||
-      !CBB_add_u16(&body, ssl->version) ||
-      (ssl_is_draft21(ssl->version) &&
-       !CBB_add_u16(&body, ssl_cipher_get_value(hs->new_cipher))) ||
-      !tls1_get_shared_group(hs, &group_id) ||
-      !CBB_add_u16_length_prefixed(&body, &extensions) ||
-      !CBB_add_u16(&extensions, TLSEXT_TYPE_key_share) ||
-      !CBB_add_u16(&extensions, 2 /* length */) ||
-      !CBB_add_u16(&extensions, group_id) ||
-      !ssl_add_message_cbb(ssl, cbb.get())) {
-    return ssl_hs_error;
+
+
+  if (ssl_is_draft22(ssl->version)) {
+    ScopedCBB cbb;
+    CBB body, session_id, extensions;
+    uint16_t group_id;
+    if (!ssl->method->init_message(ssl, cbb.get(), &body,
+                                   SSL3_MT_SERVER_HELLO) ||
+        !CBB_add_u16(&body, TLS1_2_VERSION) ||
+        !CBB_add_bytes(&body, kHelloRetryRequest, SSL3_RANDOM_SIZE) ||
+        !CBB_add_u8_length_prefixed(&body, &session_id) ||
+        !CBB_add_bytes(&session_id, hs->session_id, hs->session_id_len) ||
+        !CBB_add_u16(&body, ssl_cipher_get_value(hs->new_cipher)) ||
+        !CBB_add_u8(&body, 0 /* no compression */) ||
+        !tls1_get_shared_group(hs, &group_id) ||
+        !CBB_add_u16_length_prefixed(&body, &extensions) ||
+        !CBB_add_u16(&extensions, TLSEXT_TYPE_supported_versions) ||
+        !CBB_add_u16(&extensions, 2 /* length */) ||
+        !CBB_add_u16(&extensions, ssl->version) ||
+        !CBB_add_u16(&extensions, TLSEXT_TYPE_key_share) ||
+        !CBB_add_u16(&extensions, 2 /* length */) ||
+        !CBB_add_u16(&extensions, group_id) ||
+        !ssl_add_message_cbb(ssl, cbb.get())) {
+      return ssl_hs_error;
+    }
+
+    if (!ssl->method->add_change_cipher_spec(ssl)) {
+      return ssl_hs_error;
+    }
+  } else {
+    ScopedCBB cbb;
+    CBB body, extensions;
+    uint16_t group_id;
+    if (!ssl->method->init_message(ssl, cbb.get(), &body,
+                                   SSL3_MT_HELLO_RETRY_REQUEST) ||
+        !CBB_add_u16(&body, ssl->version) ||
+        (ssl_is_draft21(ssl->version) &&
+         !CBB_add_u16(&body, ssl_cipher_get_value(hs->new_cipher))) ||
+        !tls1_get_shared_group(hs, &group_id) ||
+        !CBB_add_u16_length_prefixed(&body, &extensions) ||
+        !CBB_add_u16(&extensions, TLSEXT_TYPE_key_share) ||
+        !CBB_add_u16(&extensions, 2 /* length */) ||
+        !CBB_add_u16(&extensions, group_id) ||
+        !ssl_add_message_cbb(ssl, cbb.get())) {
+      return ssl_hs_error;
+    }
   }
 
+  hs->sent_hello_retry_request = true;
   hs->tls13_state = state_read_second_client_hello;
   return ssl_hs_flush;
 }
@@ -576,6 +607,7 @@
   }
 
   if (ssl_is_resumption_experiment(ssl->version) &&
+      (!ssl_is_draft22(ssl->version) || !hs->sent_hello_retry_request) &&
       !ssl->method->add_change_cipher_spec(ssl)) {
     return ssl_hs_error;
   }
@@ -756,46 +788,35 @@
     hs->can_early_write = true;
     hs->can_early_read = true;
     hs->in_early_data = true;
-    hs->tls13_state = state_process_end_of_early_data;
-    return ssl_hs_read_end_of_early_data;
   }
   hs->tls13_state = state_process_end_of_early_data;
-  return ssl_hs_ok;
+  return ssl->early_data_accepted ? ssl_hs_read_end_of_early_data : ssl_hs_ok;
 }
 
 static enum ssl_hs_wait_t do_process_end_of_early_data(SSL_HANDSHAKE *hs) {
   SSL *const ssl = hs->ssl;
-  hs->tls13_state = state_process_change_cipher_spec;
   if (hs->early_data_offered) {
     // If early data was not accepted, the EndOfEarlyData and ChangeCipherSpec
     // message will be in the discarded early data.
-    if (!hs->ssl->early_data_accepted) {
-      return ssl_hs_ok;
-    }
-    if (ssl_is_draft21(ssl->version)) {
-      SSLMessage msg;
-      if (!ssl->method->get_message(ssl, &msg)) {
-        return ssl_hs_read_message;
-      }
+    if (hs->ssl->early_data_accepted) {
+      if (ssl_is_draft21(ssl->version)) {
+        SSLMessage msg;
+        if (!ssl->method->get_message(ssl, &msg)) {
+          return ssl_hs_read_message;
+        }
 
-      if (!ssl_check_message_type(ssl, msg, SSL3_MT_END_OF_EARLY_DATA)) {
-        return ssl_hs_error;
+        if (!ssl_check_message_type(ssl, msg, SSL3_MT_END_OF_EARLY_DATA)) {
+          return ssl_hs_error;
+        }
+        if (CBS_len(&msg.body) != 0) {
+          ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
+          OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
+          return ssl_hs_error;
+        }
+        ssl->method->next_message(ssl);
       }
-      if (CBS_len(&msg.body) != 0) {
-        ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
-        OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
-        return ssl_hs_error;
-      }
-      ssl->method->next_message(ssl);
     }
   }
-  return ssl_is_resumption_client_ccs_experiment(hs->ssl->version)
-             ? ssl_hs_read_change_cipher_spec
-             : ssl_hs_ok;
-}
-
-static enum ssl_hs_wait_t do_process_change_cipher_spec(SSL_HANDSHAKE *hs) {
-  SSL *const ssl = hs->ssl;
   if (!tls13_set_traffic_key(ssl, evp_aead_open, hs->client_handshake_secret,
                              hs->hash_len)) {
     return ssl_hs_error;
@@ -973,9 +994,6 @@
       case state_process_end_of_early_data:
         ret = do_process_end_of_early_data(hs);
         break;
-      case state_process_change_cipher_spec:
-        ret = do_process_change_cipher_spec(hs);
-        break;
       case state_read_client_certificate:
         ret = do_read_client_certificate(hs);
         break;
@@ -1028,8 +1046,6 @@
       return "TLS 1.3 server send_server_finished";
     case state_read_second_client_flight:
       return "TLS 1.3 server read_second_client_flight";
-    case state_process_change_cipher_spec:
-      return "TLS 1.3 server process_change_cipher_spec";
     case state_process_end_of_early_data:
       return "TLS 1.3 server process_end_of_early_data";
     case state_read_client_certificate:
diff --git a/ssl/tls_record.cc b/ssl/tls_record.cc
index a062012..7e8e968 100644
--- a/ssl/tls_record.cc
+++ b/ssl/tls_record.cc
@@ -247,6 +247,21 @@
 
   *out_consumed = in.size() - CBS_len(&cbs);
 
+  if (ssl->s3->have_version &&
+      ssl_is_resumption_experiment(ssl->version) &&
+      SSL_in_init(ssl) &&
+      type == SSL3_RT_CHANGE_CIPHER_SPEC &&
+      ciphertext_len == 1 &&
+      CBS_data(&body)[0] == 1) {
+    ssl->s3->empty_record_count++;
+    if (ssl->s3->empty_record_count > kMaxEmptyRecords) {
+      OPENSSL_PUT_ERROR(SSL, SSL_R_TOO_MANY_EMPTY_FRAGMENTS);
+      *out_alert = SSL_AD_UNEXPECTED_MESSAGE;
+      return ssl_open_record_error;
+    }
+    return ssl_open_record_discard;
+  }
+
   // Skip early data received when expecting a second ClientHello if we rejected
   // 0RTT.
   if (ssl->s3->skip_early_data &&
diff --git a/tool/client.cc b/tool/client.cc
index ea4d7fe..57e1b6e 100644
--- a/tool/client.cc
+++ b/tool/client.cc
@@ -340,6 +340,10 @@
     *out = tls13_experiment3;
     return true;
   }
+  if (in == "draft22") {
+    *out = tls13_draft22;
+    return true;
+  }
   return false;
 }