aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSteven Valdez <svaldez@google.com>2017-11-07 17:09:52 -0500
committerCQ bot account: commit-bot@chromium.org <commit-bot@chromium.org>2017-11-11 06:24:55 +0000
commit964b2377d0e6c625a96cb95bea665512352e547a (patch)
tree2b62c224abe9769a22fb4bef85102cadb7400183
parent3bcbb375521e5628cc9bd8e38cfcb7d9d8645513 (diff)
downloadboringssl-964b2377d0e6c625a96cb95bea665512352e547a.zip
boringssl-964b2377d0e6c625a96cb95bea665512352e547a.tar.gz
boringssl-964b2377d0e6c625a96cb95bea665512352e547a.tar.bz2
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>
-rw-r--r--include/openssl/ssl.h2
-rw-r--r--ssl/handshake.cc1
-rw-r--r--ssl/handshake_client.cc6
-rw-r--r--ssl/internal.h7
-rw-r--r--ssl/ssl_versions.cc24
-rw-r--r--ssl/test/runner/common.go32
-rw-r--r--ssl/test/runner/conn.go12
-rw-r--r--ssl/test/runner/handshake_client.go21
-rw-r--r--ssl/test/runner/handshake_messages.go50
-rw-r--r--ssl/test/runner/handshake_server.go19
-rw-r--r--ssl/test/runner/runner.go92
-rw-r--r--ssl/tls13_both.cc6
-rw-r--r--ssl/tls13_client.cc99
-rw-r--r--ssl/tls13_server.cc112
-rw-r--r--ssl/tls_record.cc15
-rw-r--r--tool/client.cc4
16 files changed, 392 insertions, 110 deletions
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 @@ OPENSSL_EXPORT int DTLSv1_handle_timeout(SSL *ssl);
#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 @@ enum tls13_variant_t {
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 @@ SSL_HANDSHAKE::SSL_HANDSHAKE(SSL *ssl_arg)
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 @@ static enum ssl_hs_wait_t do_enter_early_data(SSL_HANDSHAKE *hs) {
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 @@ uint16_t ssl_protocol_version(const SSL *ssl);
// 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 @@ struct SSLMessage {
// 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 @@ struct SSL_HANDSHAKE {
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 @@ bool ssl_protocol_version_from_wire(uint16_t *out, uint16_t 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:
@@ -60,6 +61,7 @@ bool ssl_protocol_version_from_wire(uint16_t *out, uint16_t version) {
// 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 @@ static const char *ssl_version_to_string(uint16_t version) {
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 @@ static uint16_t wire_version_to_api(uint16_t version) {
// 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 uint16_t wire_version_to_api(uint16_t version) {
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 @@ bool ssl_supports_version(SSL_HANDSHAKE *hs, uint16_t version) {
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_negotiate_version(SSL_HANDSHAKE *hs, uint8_t *out_alert,
}
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 @@ const (
tls13ExperimentVersion = 0x7e01
tls13Experiment2Version = 0x7e02
tls13Experiment3Version = 0x7e03
+ tls13Draft22Version = 0x7e04
)
const (
@@ -46,10 +47,12 @@ const (
TLS13Experiment2 = 2
TLS13Experiment3 = 3
TLS13Draft21 = 4
+ TLS13Draft22 = 5
)
var allTLSWireVersions = []uint16{
tls13DraftVersion,
+ tls13Draft22Version,
tls13Draft21Version,
tls13Experiment3Version,
tls13Experiment2Version,
@@ -148,6 +151,12 @@ const (
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 @@ type ProtocolBugs struct {
// 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 @@ func wireToVersion(vers uint16, isDTLS bool) (uint16, bool) {
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 wireToVersion(vers uint16, isDTLS bool) (uint16, bool) {
}
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 @@ func (c *Config) isSupportedVersion(wireVers uint16, isDTLS bool) (uint16, bool)
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 @@ type Conn struct {
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 @@ func (c *Conn) doWriteRecord(typ recordType, data []byte) (n int, err error) {
}
if isResumptionRecordVersionExperiment(c.wireVersion) || isResumptionRecordVersionExperiment(c.out.wireVersion) {
vers = VersionTLS12
- } else {
}
if c.config.Bugs.SendRecordVersion != 0 {
@@ -1322,6 +1322,14 @@ func (c *Conn) readHandshake() (interface{}, error) {
// 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 @@ func (c *Conn) sendFakeEarlyData(len int) error {
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 @@ NextCipherSuite:
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 @@ NextCipherSuite:
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 @@ func (hs *clientHandshakeState) doTLS13Handshake() error {
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 @@ func (hs *clientHandshakeState) doTLS13Handshake() error {
}
}
- 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 @@ func (m *serverExtensions) unmarshal(data []byte, version uint16) bool {
type helloRetryRequestMsg struct {
raw []byte
vers uint16
+ isServerHello bool
+ sessionId []byte
cipherSuite uint16
hasSelectedGroup bool
selectedGroup CurveID
@@ -1420,11 +1422,25 @@ func (m *helloRetryRequestMsg) marshal() []byte {
}
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 @@ func (m *helloRetryRequestMsg) marshal() []byte {
}
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 @@ func (m *helloRetryRequestMsg) unmarshal(data []byte) bool {
}
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 @@ func (m *helloRetryRequestMsg) unmarshal(data []byte) bool {
}
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 @@ ResendHelloRetryRequest:
}
helloRetryRequest := &helloRetryRequestMsg{
vers: c.wireVersion,
+ sessionId: hs.clientHello.sessionId,
cipherSuite: cipherSuite,
duplicateExtensions: config.Bugs.DuplicateHelloRetryRequestExtensions,
}
@@ -587,6 +588,10 @@ ResendHelloRetryRequest:
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 @@ ResendHelloRetryRequest:
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 @@ ResendHelloRetryRequest:
}
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 @@ ResendHelloRetryRequest:
}
}
- 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 @@ var tlsVersions = []tlsVersion{
tls13Variant: TLS13Draft21,
},
{
+ name: "TLS13Draft22",
+ version: VersionTLS13,
+ excludeFlag: "-no-tls13",
+ versionWire: tls13Draft22Version,
+ tls13Variant: TLS13Draft22,
+ },
+ {
name: "TLS13Experiment",
version: VersionTLS13,
excludeFlag: "-no-tls13",
@@ -4160,6 +4167,37 @@ func addStateMachineCoverageTests(config stateMachineTestConfig) {
})
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 @@ func addVersionNegotiationTests() {
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 @@ func addTLS13HandshakeTests() {
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 @@ namespace bssl {
// 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 @@ enum client_hs_state_t {
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 @@ static enum ssl_hs_wait_t do_read_hello_retry_request(SSL_HANDSHAKE *hs) {
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 @@ static enum ssl_hs_wait_t do_read_hello_retry_request(SSL_HANDSHAKE *hs) {
}
- 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 @@ static enum ssl_hs_wait_t do_read_hello_retry_request(SSL_HANDSHAKE *hs) {
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 @@ static enum ssl_hs_wait_t do_read_server_hello(SSL_HANDSHAKE *hs) {
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 @@ static enum ssl_hs_wait_t do_process_change_cipher_spec(SSL_HANDSHAKE *hs) {
}
}
+ ssl->method->next_message(ssl);
hs->tls13_state = state_read_encrypted_extensions;
return ssl_hs_ok;
}
@@ -642,9 +666,7 @@ static enum ssl_hs_wait_t do_send_end_of_early_data(SSL_HANDSHAKE *hs) {
}
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 @@ enum ssl_hs_wait_t tls13_client_handshake(SSL_HANDSHAKE *hs) {
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 @@ const char *tls13_client_handshake_state(SSL_HANDSHAKE *hs) {
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 @@ enum server_hs_state_t {
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_select_session(SSL_HANDSHAKE *hs) {
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 @@ static enum ssl_hs_wait_t do_send_server_hello(SSL_HANDSHAKE *hs) {
}
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 @@ static enum ssl_hs_wait_t do_read_second_client_flight(SSL_HANDSHAKE *hs) {
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 (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;
+ 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);
}
- 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 @@ enum ssl_hs_wait_t tls13_server_handshake(SSL_HANDSHAKE *hs) {
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 @@ const char *tls13_server_handshake_state(SSL_HANDSHAKE *hs) {
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 @@ enum ssl_open_record_t tls_open_record(SSL *ssl, uint8_t *out_type,
*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 @@ static bool GetTLS13Variant(tls13_variant_t *out, const std::string &in) {
*out = tls13_experiment3;
return true;
}
+ if (in == "draft22") {
+ *out = tls13_draft22;
+ return true;
+ }
return false;
}