[DTLS 1.3] Use HelloRetryRequest in place of HelloVerifyRequest.
This change has DTLS 1.3 clients reject connections from servers that
negotiate 1.3 after sending HVR. It also enables HRR tests in DTLS 1.3.
Bug: 42290594
Change-Id: I7bd5ab0e968e6b4c301a5697b1166703c28d9ebc
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/71348
Commit-Queue: Nick Harper <nharper@chromium.org>
Commit-Queue: David Benjamin <davidben@google.com>
Auto-Submit: Nick Harper <nharper@chromium.org>
Reviewed-by: David Benjamin <davidben@google.com>
diff --git a/ssl/handshake.cc b/ssl/handshake.cc
index 165614f..a9ec634 100644
--- a/ssl/handshake.cc
+++ b/ssl/handshake.cc
@@ -149,7 +149,8 @@
cert_compression_negotiated(false),
apply_jdk11_workaround(false),
can_release_private_key(false),
- channel_id_negotiated(false) {
+ channel_id_negotiated(false),
+ received_hello_verify_request(false) {
assert(ssl);
// Draw entropy for all GREASE values at once. This avoids calling
diff --git a/ssl/handshake_client.cc b/ssl/handshake_client.cc
index 3bfc7ae..f914add 100644
--- a/ssl/handshake_client.cc
+++ b/ssl/handshake_client.cc
@@ -648,12 +648,6 @@
return ssl_hs_ok;
}
- // TODO(crbug.com/boringssl/715): At the point when we read an HVR, we don't
- // know whether the connection is DTLS 1.2 (or earlier) or DTLS 1.3 - that's
- // determined when we read the supported_versions in the ServerHello. If we
- // receive HVR and then the ServerHello selects DTLS 1.3, that is an error and
- // we should close the connection.
-
CBS hello_verify_request = msg.body, cookie;
uint16_t server_version;
if (!CBS_get_u16(&hello_verify_request, &server_version) ||
@@ -668,6 +662,7 @@
ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
return ssl_hs_error;
}
+ hs->received_hello_verify_request = true;
ssl->method->next_message(ssl);
@@ -738,15 +733,6 @@
return ssl_hs_error;
}
- // TODO(crbug.com/boringssl/715): Check that if the server picked DTLS 1.3,
- // that it didn't also previously send an HVR, as that is not allowed by RFC
- // 9147. (DTLS 1.25 still uses HVR instead of HRR.) Also add a runner test to
- // test that we handle that case properly.
- //
- // See
- // https://boringssl-review.googlesource.com/c/boringssl/+/68027/3/ssl/handshake_client.cc
- // for an example of what this check might look like.
-
assert(ssl->s3->have_version == ssl->s3->initial_handshake_complete);
if (!ssl->s3->have_version) {
ssl->version = server_version;
@@ -760,6 +746,13 @@
return ssl_hs_error;
}
+ if (hs->received_hello_verify_request &&
+ ssl_protocol_version(ssl) > TLS1_2_VERSION) {
+ OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_MESSAGE);
+ ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_PROTOCOL_VERSION);
+ return ssl_hs_error;
+ }
+
if (ssl_protocol_version(ssl) >= TLS1_3_VERSION) {
hs->state = state_tls13;
return ssl_hs_ok;
diff --git a/ssl/internal.h b/ssl/internal.h
index 67ed227..8038e51 100644
--- a/ssl/internal.h
+++ b/ssl/internal.h
@@ -2014,7 +2014,8 @@
// dtls_cookie is the value of the cookie in DTLS HelloVerifyRequest. If
// empty, either none was received or HelloVerifyRequest contained an empty
- // cookie.
+ // cookie. Check the received_hello_verify_request field to distinguish an
+ // empty cookie from no HelloVerifyRequest message being received.
Array<uint8_t> dtls_cookie;
// ech_client_outer contains the outer ECH extension to send in the
@@ -2222,6 +2223,10 @@
// handshake.
bool channel_id_negotiated : 1;
+ // received_hello_verify_request is true if we received a HelloVerifyRequest
+ // message from the server.
+ bool received_hello_verify_request : 1;
+
// client_version is the value sent or received in the ClientHello version.
uint16_t client_version = 0;
@@ -3077,7 +3082,7 @@
static constexpr bool kAllowUniquePtr = true;
UniquePtr<SSLAEADContext> aead_write_ctx;
- uint64_t write_sequence;
+ uint64_t write_sequence = 0;
};
struct DTLS1_STATE {
diff --git a/ssl/test/runner/common.go b/ssl/test/runner/common.go
index 9c775e9..2074335 100644
--- a/ssl/test/runner/common.go
+++ b/ssl/test/runner/common.go
@@ -713,6 +713,11 @@
// HelloVerifyRequest message.
SkipHelloVerifyRequest bool
+ // ForceHelloVerifyRequest causes a DTLS server to send a
+ // HelloVerifyRequest message in DTLS 1.3 or other cases where it
+ // otherwise wouldn't.
+ ForceHelloVerifyRequest bool
+
// HelloVerifyRequestCookieLength, if non-zero, is the length of the cookie
// to request in HelloVerifyRequest.
HelloVerifyRequestCookieLength int
diff --git a/ssl/test/runner/conn.go b/ssl/test/runner/conn.go
index 4a31a3c..38b1354 100644
--- a/ssl/test/runner/conn.go
+++ b/ssl/test/runner/conn.go
@@ -277,12 +277,17 @@
}
}
-// resetCipher changes the cipher state back to no encryption to be able
+// resetCipher resets the cipher state back to no encryption to be able
// to send an unencrypted ClientHello in response to HelloRetryRequest
// after 0-RTT data was rejected.
func (hc *halfConn) resetCipher() {
+ // In all cases, the cipher is set to nil so that second ClientHello
+ // will be sent with no encryption (instead of with early data keys).
hc.cipher = nil
- hc.incEpoch()
+ // TODO(crbug.com/42290594): When handling 0-RTT rejections in DTLS, we
+ // need to reset the epoch to 0 and reset the sequence number to where
+ // it was prior to sending early data (this is different than resetting
+ // it to 0).
}
// incSeq increments the sequence number.
diff --git a/ssl/test/runner/handshake_messages.go b/ssl/test/runner/handshake_messages.go
index 5b287ae..eadb360 100644
--- a/ssl/test/runner/handshake_messages.go
+++ b/ssl/test/runner/handshake_messages.go
@@ -1792,6 +1792,7 @@
}
type helloRetryRequestMsg struct {
+ isDTLS bool
raw []byte
vers uint16
sessionID []byte
@@ -1814,7 +1815,11 @@
retryRequestMsg := cryptobyte.NewBuilder(nil)
retryRequestMsg.AddUint8(typeServerHello)
retryRequestMsg.AddUint24LengthPrefixed(func(retryRequest *cryptobyte.Builder) {
- retryRequest.AddUint16(VersionTLS12)
+ legacyVersion := uint16(VersionTLS12)
+ if m.isDTLS {
+ legacyVersion = VersionDTLS12
+ }
+ retryRequest.AddUint16(legacyVersion)
retryRequest.AddBytes(tls13HelloRetryRequest)
addUint8LengthPrefixedBytes(retryRequest, m.sessionID)
retryRequest.AddUint16(m.cipherSuite)
diff --git a/ssl/test/runner/handshake_server.go b/ssl/test/runner/handshake_server.go
index 87cedb9..b3c1f98 100644
--- a/ssl/test/runner/handshake_server.go
+++ b/ssl/test/runner/handshake_server.go
@@ -150,6 +150,20 @@
return nil
}
+func (c *Conn) shouldSendHelloVerifyRequest() bool {
+ if !c.isDTLS {
+ return false
+ }
+ if c.config.Bugs.ForceHelloVerifyRequest {
+ return true
+ }
+ if c.config.Bugs.SkipHelloVerifyRequest {
+ return false
+ }
+ // Don't send HVR for DTLS 1.3; do send it for DTLS <= 1.2.
+ return c.vers < VersionTLS13
+}
+
// readClientHello reads a ClientHello message from the client and determines
// the protocol version.
func (hs *serverHandshakeState) readClientHello() error {
@@ -233,74 +247,6 @@
}
}
- if c.isDTLS && !config.Bugs.SkipHelloVerifyRequest {
- // Per RFC 6347, the version field in HelloVerifyRequest SHOULD
- // be always DTLS 1.0
- cookieLen := c.config.Bugs.HelloVerifyRequestCookieLength
- if cookieLen == 0 {
- cookieLen = 32
- }
- if c.config.Bugs.EmptyHelloVerifyRequestCookie {
- cookieLen = 0
- }
- helloVerifyRequest := &helloVerifyRequestMsg{
- vers: VersionDTLS10,
- cookie: make([]byte, cookieLen),
- }
- if _, err := io.ReadFull(c.config.rand(), helloVerifyRequest.cookie); err != nil {
- c.sendAlert(alertInternalError)
- return errors.New("dtls: short read from Rand: " + err.Error())
- }
- c.writeRecord(recordTypeHandshake, helloVerifyRequest.marshal())
- c.flushHandshake()
-
- if err := c.simulatePacketLoss(nil); err != nil {
- return err
- }
- msg, err := c.readHandshake()
- if err != nil {
- return err
- }
- newClientHello, ok := msg.(*clientHelloMsg)
- if !ok {
- c.sendAlert(alertUnexpectedMessage)
- return unexpectedMessageError(hs.clientHello, msg)
- }
- if !bytes.Equal(newClientHello.cookie, helloVerifyRequest.cookie) {
- return errors.New("dtls: invalid cookie")
- }
- if err := checkClientHellosEqual(hs.clientHello.raw, newClientHello.raw, c.isDTLS, nil); err != nil {
- return err
- }
- hs.clientHello = newClientHello
- }
-
- if config.Bugs.RequireSameRenegoClientVersion && c.clientVersion != 0 {
- if c.clientVersion != hs.clientHello.vers {
- return fmt.Errorf("tls: client offered different version on renego")
- }
- }
-
- if config.Bugs.FailIfPostQuantumOffered {
- for _, offeredCurve := range hs.clientHello.supportedCurves {
- if isPqGroup(offeredCurve) {
- return errors.New("tls: post-quantum group was offered")
- }
- }
- }
-
- if expected := config.Bugs.ExpectedKeyShares; expected != nil {
- if len(expected) != len(hs.clientHello.keyShares) {
- return fmt.Errorf("tls: expected %d key shares, but found %d", len(expected), len(hs.clientHello.keyShares))
- }
-
- for i, group := range expected {
- if found := hs.clientHello.keyShares[i].group; found != group {
- return fmt.Errorf("tls: key share #%d is for group %d, not %d", i, found, group)
- }
- }
- }
-
c.clientVersion = hs.clientHello.vers
// Use the versions extension if supplied, otherwise use the legacy ClientHello version.
@@ -355,10 +301,92 @@
if !ok {
panic("Could not map wire version")
}
- c.haveVers = true
clientProtocol, ok := wireToVersion(c.clientVersion, c.isDTLS)
+ if c.shouldSendHelloVerifyRequest() {
+ // Per RFC 6347, the version field in HelloVerifyRequest SHOULD
+ // be always DTLS 1.0
+ cookieLen := c.config.Bugs.HelloVerifyRequestCookieLength
+ if cookieLen == 0 {
+ cookieLen = 32
+ }
+ if c.config.Bugs.EmptyHelloVerifyRequestCookie {
+ cookieLen = 0
+ }
+ helloVerifyRequest := &helloVerifyRequestMsg{
+ vers: VersionDTLS10,
+ cookie: make([]byte, cookieLen),
+ }
+ if _, err := io.ReadFull(c.config.rand(), helloVerifyRequest.cookie); err != nil {
+ c.sendAlert(alertInternalError)
+ return errors.New("dtls: short read from Rand: " + err.Error())
+ }
+ c.writeRecord(recordTypeHandshake, helloVerifyRequest.marshal())
+ c.flushHandshake()
+
+ if err := c.simulatePacketLoss(nil); err != nil {
+ return err
+ }
+ msg, err := c.readHandshake()
+ if err != nil {
+ return err
+ }
+ newClientHello, ok := msg.(*clientHelloMsg)
+ if !ok {
+ c.sendAlert(alertUnexpectedMessage)
+ return unexpectedMessageError(hs.clientHello, msg)
+ }
+ if !bytes.Equal(newClientHello.cookie, helloVerifyRequest.cookie) {
+ return errors.New("dtls: invalid cookie")
+ }
+ if err := checkClientHellosEqual(hs.clientHello.raw, newClientHello.raw, c.isDTLS, nil); err != nil {
+ return err
+ }
+ hs.clientHello = newClientHello
+ }
+ // The version for the connection is selected before sending
+ // HelloVerifyRequest, but haveVers isn't set until after we send it and
+ // receive the second ClientHello. This is intentional because once the
+ // version for the Conn has been negotiated, we enforce that the version
+ // in the record layer matches the negotiated version.
+ //
+ // At the time the server has selected a version and decided to send a
+ // HelloVerifyRequest to the client, only the server knows the selected
+ // version. Even though there is a version field in HelloVerifyRequest,
+ // it is only used to indicate packet formatting and is not part of
+ // version negotiation. Thus, when the client sends its second
+ // ClientHello (in response to HelloVerifyRequest), it does not know the
+ // version selected for this connection. Therefore, this server can't
+ // enforce that the client used the correct version in the record layer.
+ c.haveVers = true
+
+ if config.Bugs.RequireSameRenegoClientVersion && c.clientVersion != 0 {
+ if c.clientVersion != hs.clientHello.vers {
+ return fmt.Errorf("tls: client offered different version on renego")
+ }
+ }
+
+ if config.Bugs.FailIfPostQuantumOffered {
+ for _, offeredCurve := range hs.clientHello.supportedCurves {
+ if isPqGroup(offeredCurve) {
+ return errors.New("tls: post-quantum group was offered")
+ }
+ }
+ }
+
+ if expected := config.Bugs.ExpectedKeyShares; expected != nil {
+ if len(expected) != len(hs.clientHello.keyShares) {
+ return fmt.Errorf("tls: expected %d key shares, but found %d", len(expected), len(hs.clientHello.keyShares))
+ }
+
+ for i, group := range expected {
+ if found := hs.clientHello.keyShares[i].group; found != group {
+ return fmt.Errorf("tls: key share #%d is for group %d, not %d", i, found, group)
+ }
+ }
+ }
+
// Reject < 1.2 ClientHellos with signature_algorithms.
if ok && clientProtocol < VersionTLS12 && len(hs.clientHello.signatureAlgorithms) > 0 {
return fmt.Errorf("tls: client included signature_algorithms before TLS 1.2")
@@ -700,6 +728,7 @@
cipherSuite = config.Bugs.SendHelloRetryRequestCipherSuite
}
helloRetryRequest := &helloRetryRequestMsg{
+ isDTLS: c.isDTLS,
vers: c.wireVersion,
sessionID: hs.clientHello.sessionID,
cipherSuite: cipherSuite,
diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go
index 78e3aa7..f3a9462 100644
--- a/ssl/test/runner/runner.go
+++ b/ssl/test/runner/runner.go
@@ -4973,41 +4973,76 @@
})
}
- // TLS 1.3 basic handshake shapes. DTLS 1.3 isn't supported yet.
- // TODO(crbug.com/boringssl/715): Enable these tests.
- if config.protocol != dtls {
- tests = append(tests, testCase{
- name: "TLS13-1RTT-Client",
- config: Config{
- MaxVersion: VersionTLS13,
- MinVersion: VersionTLS13,
- },
- resumeSession: true,
- resumeRenewedSession: true,
+ // TLS 1.3 basic handshake shapes.
+ tests = append(tests, testCase{
+ name: "TLS13-1RTT-Client",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ MinVersion: VersionTLS13,
+ },
+ resumeSession: true,
+ resumeRenewedSession: true,
+ // 0-RTT being disabled overrides all other 0-RTT reasons.
+ flags: []string{"-expect-early-data-reason", "disabled"},
+ })
+
+ tests = append(tests, testCase{
+ testType: serverTest,
+ name: "TLS13-1RTT-Server",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ MinVersion: VersionTLS13,
+ },
+ resumeSession: true,
+ resumeRenewedSession: true,
+ flags: []string{
+ // TLS 1.3 uses tickets, so the session should not be
+ // cached statefully.
+ "-expect-no-session-id",
// 0-RTT being disabled overrides all other 0-RTT reasons.
- flags: []string{"-expect-early-data-reason", "disabled"},
- })
+ "-expect-early-data-reason", "disabled",
+ },
+ })
- tests = append(tests, testCase{
- testType: serverTest,
- name: "TLS13-1RTT-Server",
- config: Config{
- MaxVersion: VersionTLS13,
- MinVersion: VersionTLS13,
+ tests = append(tests, testCase{
+ name: "TLS13-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,
},
- resumeSession: true,
- resumeRenewedSession: true,
- flags: []string{
- // TLS 1.3 uses tickets, so the session should not be
- // cached statefully.
- "-expect-no-session-id",
- // 0-RTT being disabled overrides all other 0-RTT reasons.
- "-expect-early-data-reason", "disabled",
- },
- })
+ },
+ // Cover HelloRetryRequest during an ECDHE-PSK resumption.
+ resumeSession: true,
+ flags: []string{"-expect-hrr"},
+ })
+ tests = append(tests, testCase{
+ testType: serverTest,
+ name: "TLS13-HelloRetryRequest-Server",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ MinVersion: VersionTLS13,
+ // Require a HelloRetryRequest for every curve.
+ DefaultCurves: []CurveID{},
+ },
+ // Cover HelloRetryRequest during an ECDHE-PSK resumption.
+ resumeSession: true,
+ flags: []string{"-expect-hrr"},
+ })
+
+ // TODO(crbug.com/boringssl/715): The -NoResume tests here are copies
+ // of the above tests, but without resumeSession set. These exist to
+ // test HRR in DTLS 1.3, because tests DTLS 1.3 tests with resumption
+ // enabled are skipped due to lack of support for resumption. Once we
+ // support resumption in DTLS 1.3, these can be deleted.
+ if config.protocol == dtls {
tests = append(tests, testCase{
- name: "TLS13-HelloRetryRequest-Client",
+ name: "TLS13-HelloRetryRequest-Client-NoResume",
config: Config{
MaxVersion: VersionTLS13,
MinVersion: VersionTLS13,
@@ -5018,56 +5053,53 @@
ExpectMissingKeyShare: true,
},
},
- // Cover HelloRetryRequest during an ECDHE-PSK resumption.
- resumeSession: true,
- flags: []string{"-expect-hrr"},
+ flags: []string{"-expect-hrr"},
})
-
tests = append(tests, testCase{
testType: serverTest,
- name: "TLS13-HelloRetryRequest-Server",
+ name: "TLS13-HelloRetryRequest-Server-NoResume",
config: Config{
MaxVersion: VersionTLS13,
MinVersion: VersionTLS13,
// Require a HelloRetryRequest for every curve.
DefaultCurves: []CurveID{},
},
- // Cover HelloRetryRequest during an ECDHE-PSK resumption.
- resumeSession: true,
- flags: []string{"-expect-hrr"},
+ flags: []string{"-expect-hrr"},
})
+ }
- // Tests that specify a MaxEarlyDataSize don't work with QUIC.
- if config.protocol != quic {
- tests = append(tests, testCase{
- testType: clientTest,
- name: "TLS13-EarlyData-TooMuchData-Client",
- config: Config{
- MaxVersion: VersionTLS13,
- MinVersion: VersionTLS13,
- MaxEarlyDataSize: 2,
+ // TLS 1.3 early data tests. DTLS 1.3 doesn't support early data yet.
+ // These tests are disabled for QUIC as well because they test features
+ // that do not apply to QUIC's use of TLS 1.3.
+ //
+ // TODO(crbug.com/boringssl/715): Enable these tests for DTLS once we
+ // support early data in DTLS 1.3.
+ if config.protocol != dtls && config.protocol != quic {
+ tests = append(tests, testCase{
+ testType: clientTest,
+ name: "TLS13-EarlyData-TooMuchData-Client",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ MinVersion: VersionTLS13,
+ MaxEarlyDataSize: 2,
+ },
+ resumeConfig: &Config{
+ MaxVersion: VersionTLS13,
+ MinVersion: VersionTLS13,
+ MaxEarlyDataSize: 2,
+ Bugs: ProtocolBugs{
+ ExpectEarlyData: [][]byte{[]byte(shimInitialWrite[:2])},
},
- resumeConfig: &Config{
- MaxVersion: VersionTLS13,
- MinVersion: VersionTLS13,
- MaxEarlyDataSize: 2,
- Bugs: ProtocolBugs{
- ExpectEarlyData: [][]byte{[]byte(shimInitialWrite[:2])},
- },
- },
- resumeShimPrefix: shimInitialWrite[2:],
- resumeSession: true,
- earlyData: true,
- })
- }
+ },
+ resumeShimPrefix: shimInitialWrite[2:],
+ resumeSession: true,
+ earlyData: true,
+ })
// Unfinished writes can only be tested when operations are async. EarlyData
// can't be tested as part of an ImplicitHandshake in this case since
// otherwise the early data will be sent as normal data.
- //
- // Note application data is external in QUIC, so unfinished writes do not
- // apply.
- if config.async && !config.implicitHandshake && config.protocol != quic {
+ if config.async && !config.implicitHandshake {
tests = append(tests, testCase{
testType: clientTest,
name: "TLS13-EarlyData-UnfinishedWrite-Client",
@@ -5106,25 +5138,23 @@
}
// Early data has no size limit in QUIC.
- if config.protocol != quic {
- tests = append(tests, testCase{
- testType: serverTest,
- name: "TLS13-MaxEarlyData-Server",
- config: Config{
- MaxVersion: VersionTLS13,
- MinVersion: VersionTLS13,
- Bugs: ProtocolBugs{
- SendEarlyData: [][]byte{bytes.Repeat([]byte{1}, 14336+1)},
- ExpectEarlyDataAccepted: true,
- },
+ tests = append(tests, testCase{
+ testType: serverTest,
+ name: "TLS13-MaxEarlyData-Server",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ MinVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ SendEarlyData: [][]byte{bytes.Repeat([]byte{1}, 14336+1)},
+ ExpectEarlyDataAccepted: true,
},
- messageCount: 2,
- resumeSession: true,
- earlyData: true,
- shouldFail: true,
- expectedError: ":TOO_MUCH_READ_EARLY_DATA:",
- })
- }
+ },
+ messageCount: 2,
+ resumeSession: true,
+ earlyData: true,
+ shouldFail: true,
+ expectedError: ":TOO_MUCH_READ_EARLY_DATA:",
+ })
}
// TLS client auth.
@@ -6245,8 +6275,6 @@
}
}
if config.protocol == dtls {
- // TODO(crbug.com/boringssl/715): DTLS 1.3 will want a similar
- // thing for HelloRetryRequest.
tests = append(tests, testCase{
name: "SkipHelloVerifyRequest",
config: Config{
@@ -6256,6 +6284,29 @@
},
},
})
+ tests = append(tests, testCase{
+ name: "DTLS13-HelloVerifyRequest",
+ config: Config{
+ MinVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ ForceHelloVerifyRequest: true,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":INVALID_MESSAGE:",
+ })
+ tests = append(tests, testCase{
+ name: "DTLS13-HelloVerifyRequestEmptyCookie",
+ config: Config{
+ MinVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ ForceHelloVerifyRequest: true,
+ EmptyHelloVerifyRequestCookie: true,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":INVALID_MESSAGE:",
+ })
}
for _, test := range tests {