summaryrefslogtreecommitdiffstats
path: root/security/nss/gtests/ssl_gtest/ssl_tls13compat_unittest.cc
diff options
context:
space:
mode:
Diffstat (limited to 'security/nss/gtests/ssl_gtest/ssl_tls13compat_unittest.cc')
-rw-r--r--security/nss/gtests/ssl_gtest/ssl_tls13compat_unittest.cc573
1 files changed, 573 insertions, 0 deletions
diff --git a/security/nss/gtests/ssl_gtest/ssl_tls13compat_unittest.cc b/security/nss/gtests/ssl_gtest/ssl_tls13compat_unittest.cc
new file mode 100644
index 0000000000..2421470a4f
--- /dev/null
+++ b/security/nss/gtests/ssl_gtest/ssl_tls13compat_unittest.cc
@@ -0,0 +1,573 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <memory>
+#include <vector>
+#include "ssl.h"
+#include "sslerr.h"
+#include "sslproto.h"
+
+#include "gtest_utils.h"
+#include "tls_connect.h"
+#include "tls_filter.h"
+#include "tls_parser.h"
+
+namespace nss_test {
+
+class Tls13CompatTest : public TlsConnectStreamTls13 {
+ protected:
+ void EnableCompatMode() {
+ client_->SetOption(SSL_ENABLE_TLS13_COMPAT_MODE, PR_TRUE);
+ }
+
+ void InstallFilters() {
+ EnsureTlsSetup();
+ client_recorders_.Install(client_);
+ server_recorders_.Install(server_);
+ }
+
+ void CheckRecordVersions() {
+ ASSERT_EQ(SSL_LIBRARY_VERSION_TLS_1_0,
+ client_recorders_.records_->record(0).header.version());
+ CheckRecordsAreTls12("client", client_recorders_.records_, 1);
+ CheckRecordsAreTls12("server", server_recorders_.records_, 0);
+ }
+
+ void CheckHelloVersions() {
+ uint32_t ver;
+ ASSERT_TRUE(server_recorders_.hello_->buffer().Read(0, 2, &ver));
+ ASSERT_EQ(SSL_LIBRARY_VERSION_TLS_1_2, static_cast<uint16_t>(ver));
+ ASSERT_TRUE(client_recorders_.hello_->buffer().Read(0, 2, &ver));
+ ASSERT_EQ(SSL_LIBRARY_VERSION_TLS_1_2, static_cast<uint16_t>(ver));
+ }
+
+ void CheckForCCS(bool expected_client, bool expected_server) {
+ client_recorders_.CheckForCCS(expected_client);
+ server_recorders_.CheckForCCS(expected_server);
+ }
+
+ void CheckForRegularHandshake() {
+ CheckRecordVersions();
+ CheckHelloVersions();
+ EXPECT_EQ(0U, client_recorders_.session_id_length());
+ EXPECT_EQ(0U, server_recorders_.session_id_length());
+ CheckForCCS(false, false);
+ }
+
+ void CheckForCompatHandshake() {
+ CheckRecordVersions();
+ CheckHelloVersions();
+ EXPECT_EQ(32U, client_recorders_.session_id_length());
+ EXPECT_EQ(32U, server_recorders_.session_id_length());
+ CheckForCCS(true, true);
+ }
+
+ private:
+ struct Recorders {
+ Recorders() : records_(nullptr), hello_(nullptr) {}
+
+ uint8_t session_id_length() const {
+ // session_id is always after version (2) and random (32).
+ uint32_t len = 0;
+ EXPECT_TRUE(hello_->buffer().Read(2 + 32, 1, &len));
+ return static_cast<uint8_t>(len);
+ }
+
+ void CheckForCCS(bool expected) const {
+ EXPECT_LT(0U, records_->count());
+ for (size_t i = 0; i < records_->count(); ++i) {
+ // Only the second record can be a CCS.
+ bool expected_match = expected && (i == 1);
+ EXPECT_EQ(expected_match,
+ ssl_ct_change_cipher_spec ==
+ records_->record(i).header.content_type());
+ }
+ }
+
+ void Install(std::shared_ptr<TlsAgent>& agent) {
+ if (records_ && records_->agent() == agent) {
+ // Avoid replacing the filters if they are already installed on this
+ // agent. This ensures that InstallFilters() can be used after
+ // MakeNewServer() without losing state on the client filters.
+ return;
+ }
+ records_.reset(new TlsRecordRecorder(agent));
+ hello_.reset(new TlsHandshakeRecorder(
+ agent, std::set<uint8_t>(
+ {kTlsHandshakeClientHello, kTlsHandshakeServerHello})));
+ agent->SetFilter(std::make_shared<ChainedPacketFilter>(
+ ChainedPacketFilterInit({records_, hello_})));
+ }
+
+ std::shared_ptr<TlsRecordRecorder> records_;
+ std::shared_ptr<TlsHandshakeRecorder> hello_;
+ };
+
+ void CheckRecordsAreTls12(const std::string& agent,
+ const std::shared_ptr<TlsRecordRecorder>& records,
+ size_t start) {
+ EXPECT_LE(start, records->count());
+ for (size_t i = start; i < records->count(); ++i) {
+ EXPECT_EQ(SSL_LIBRARY_VERSION_TLS_1_2,
+ records->record(i).header.version())
+ << agent << ": record " << i << " has wrong version";
+ }
+ }
+
+ Recorders client_recorders_;
+ Recorders server_recorders_;
+};
+
+TEST_F(Tls13CompatTest, Disabled) {
+ InstallFilters();
+ Connect();
+ CheckForRegularHandshake();
+}
+
+TEST_F(Tls13CompatTest, Enabled) {
+ EnableCompatMode();
+ InstallFilters();
+ Connect();
+ CheckForCompatHandshake();
+}
+
+TEST_F(Tls13CompatTest, EnabledZeroRtt) {
+ SetupForZeroRtt();
+ EnableCompatMode();
+ InstallFilters();
+
+ client_->Set0RttEnabled(true);
+ server_->Set0RttEnabled(true);
+ ExpectResumption(RESUME_TICKET);
+ ZeroRttSendReceive(true, true);
+ CheckForCCS(true, true);
+ Handshake();
+ ExpectEarlyDataAccepted(true);
+ CheckConnected();
+
+ CheckForCompatHandshake();
+}
+
+TEST_F(Tls13CompatTest, EnabledHrr) {
+ EnableCompatMode();
+ InstallFilters();
+
+ // Force a HelloRetryRequest. The server sends CCS immediately.
+ server_->ConfigNamedGroups({ssl_grp_ec_secp384r1});
+ client_->StartConnect();
+ server_->StartConnect();
+ client_->Handshake();
+ server_->Handshake();
+ CheckForCCS(false, true);
+
+ Handshake();
+ CheckConnected();
+ CheckForCompatHandshake();
+}
+
+TEST_F(Tls13CompatTest, EnabledStatelessHrr) {
+ EnableCompatMode();
+ InstallFilters();
+
+ // Force a HelloRetryRequest
+ server_->ConfigNamedGroups({ssl_grp_ec_secp384r1});
+ client_->StartConnect();
+ server_->StartConnect();
+ client_->Handshake();
+ server_->Handshake();
+
+ // The server should send CCS before HRR.
+ CheckForCCS(false, true);
+
+ // A new server should complete the handshake, and not send CCS.
+ MakeNewServer();
+ InstallFilters();
+ server_->ConfigNamedGroups({ssl_grp_ec_secp384r1});
+
+ Handshake();
+ CheckConnected();
+ CheckRecordVersions();
+ CheckHelloVersions();
+ CheckForCCS(true, false);
+}
+
+TEST_F(Tls13CompatTest, EnabledHrrZeroRtt) {
+ SetupForZeroRtt();
+ EnableCompatMode();
+ InstallFilters();
+ server_->ConfigNamedGroups({ssl_grp_ec_secp384r1});
+
+ // With 0-RTT, the client sends CCS immediately. With HRR, the server sends
+ // CCS immediately too.
+ client_->Set0RttEnabled(true);
+ server_->Set0RttEnabled(true);
+ ExpectResumption(RESUME_TICKET);
+ ZeroRttSendReceive(true, false);
+ CheckForCCS(true, true);
+
+ Handshake();
+ ExpectEarlyDataAccepted(false);
+ CheckConnected();
+ CheckForCompatHandshake();
+}
+
+TEST_F(Tls13CompatTest, EnabledAcceptedEch) {
+ EnsureTlsSetup();
+ SetupEch(client_, server_);
+ EnableCompatMode();
+ InstallFilters();
+ Connect();
+ CheckForCompatHandshake();
+}
+
+TEST_F(Tls13CompatTest, EnabledRejectedEch) {
+ EnsureTlsSetup();
+ // Configure ECH on the client only, and expect CCS.
+ SetupEch(client_, server_, HpkeDhKemX25519Sha256, false, true, false);
+ EnableCompatMode();
+ InstallFilters();
+ ExpectAlert(client_, kTlsAlertEchRequired);
+ ConnectExpectFailOneSide(TlsAgent::CLIENT);
+ client_->CheckErrorCode(SSL_ERROR_ECH_RETRY_WITHOUT_ECH);
+ CheckForCompatHandshake();
+ // Reset expectations for the TlsAgent dtor.
+ server_->ExpectReceiveAlert(kTlsAlertCloseNotify, kTlsAlertWarning);
+}
+
+class TlsSessionIDEchoFilter : public TlsHandshakeFilter {
+ public:
+ TlsSessionIDEchoFilter(const std::shared_ptr<TlsAgent>& a)
+ : TlsHandshakeFilter(
+ a, {kTlsHandshakeClientHello, kTlsHandshakeServerHello}) {}
+
+ protected:
+ virtual PacketFilter::Action FilterHandshake(const HandshakeHeader& header,
+ const DataBuffer& input,
+ DataBuffer* output) {
+ TlsParser parser(input);
+
+ // Skip version + random.
+ EXPECT_TRUE(parser.Skip(2 + 32));
+
+ // Capture CH.legacy_session_id.
+ if (header.handshake_type() == kTlsHandshakeClientHello) {
+ EXPECT_TRUE(parser.ReadVariable(&sid_, 1));
+ return KEEP;
+ }
+
+ // Check that server sends one too.
+ uint32_t sid_len = 0;
+ EXPECT_TRUE(parser.Read(&sid_len, 1));
+ EXPECT_EQ(sid_len, sid_.len());
+
+ // Echo the one we captured.
+ *output = input;
+ output->Write(parser.consumed(), sid_.data(), sid_.len());
+
+ return CHANGE;
+ }
+
+ private:
+ DataBuffer sid_;
+};
+
+TEST_F(TlsConnectTest, EchoTLS13CompatibilitySessionID) {
+ ConfigureSessionCache(RESUME_SESSIONID, RESUME_SESSIONID);
+
+ client_->SetOption(SSL_ENABLE_TLS13_COMPAT_MODE, PR_TRUE);
+
+ client_->SetVersionRange(SSL_LIBRARY_VERSION_TLS_1_2,
+ SSL_LIBRARY_VERSION_TLS_1_3);
+
+ server_->SetVersionRange(SSL_LIBRARY_VERSION_TLS_1_2,
+ SSL_LIBRARY_VERSION_TLS_1_2);
+
+ server_->SetFilter(MakeTlsFilter<TlsSessionIDEchoFilter>(client_));
+ ConnectExpectAlert(client_, kTlsAlertIllegalParameter);
+
+ client_->CheckErrorCode(SSL_ERROR_RX_MALFORMED_SERVER_HELLO);
+ server_->CheckErrorCode(SSL_ERROR_ILLEGAL_PARAMETER_ALERT);
+}
+
+class TlsSessionIDInjectFilter : public TlsHandshakeFilter {
+ public:
+ TlsSessionIDInjectFilter(const std::shared_ptr<TlsAgent>& a)
+ : TlsHandshakeFilter(a, {kTlsHandshakeServerHello}) {}
+
+ protected:
+ virtual PacketFilter::Action FilterHandshake(const HandshakeHeader& header,
+ const DataBuffer& input,
+ DataBuffer* output) {
+ TlsParser parser(input);
+
+ // Skip version + random.
+ EXPECT_TRUE(parser.Skip(2 + 32));
+
+ *output = input;
+
+ // Inject a Session ID.
+ const uint8_t fake_sid[SSL3_SESSIONID_BYTES] = {0xff};
+ output->Write(parser.consumed(), sizeof(fake_sid), 1);
+ output->Splice(fake_sid, sizeof(fake_sid), parser.consumed() + 1, 0);
+
+ return CHANGE;
+ }
+};
+
+TEST_F(TlsConnectTest, TLS13NonCompatModeSessionID) {
+ ConfigureVersion(SSL_LIBRARY_VERSION_TLS_1_3);
+
+ MakeTlsFilter<TlsSessionIDInjectFilter>(server_);
+ client_->ExpectSendAlert(kTlsAlertIllegalParameter);
+ server_->ExpectSendAlert(kTlsAlertUnexpectedMessage);
+ ConnectExpectFail();
+
+ client_->CheckErrorCode(SSL_ERROR_RX_MALFORMED_SERVER_HELLO);
+ server_->CheckErrorCode(SSL_ERROR_RX_UNEXPECTED_RECORD_TYPE);
+}
+
+static const uint8_t kCannedCcs[] = {
+ ssl_ct_change_cipher_spec,
+ SSL_LIBRARY_VERSION_TLS_1_2 >> 8,
+ SSL_LIBRARY_VERSION_TLS_1_2 & 0xff,
+ 0,
+ 1, // length
+ 1 // change_cipher_spec_choice
+};
+
+// A ChangeCipherSpec is ignored by a server because we have to tolerate it for
+// compatibility mode. That doesn't mean that we have to tolerate it
+// unconditionally. If we negotiate 1.3, we expect to see a cookie extension.
+TEST_F(TlsConnectStreamTls13, ChangeCipherSpecBeforeClientHello13) {
+ EnsureTlsSetup();
+ server_->SetVersionRange(SSL_LIBRARY_VERSION_TLS_1_2,
+ SSL_LIBRARY_VERSION_TLS_1_3);
+ client_->SetVersionRange(SSL_LIBRARY_VERSION_TLS_1_2,
+ SSL_LIBRARY_VERSION_TLS_1_3);
+ // Client sends CCS before starting the handshake.
+ client_->SendDirect(DataBuffer(kCannedCcs, sizeof(kCannedCcs)));
+ ConnectExpectAlert(server_, kTlsAlertUnexpectedMessage);
+ server_->CheckErrorCode(SSL_ERROR_RX_UNEXPECTED_CHANGE_CIPHER);
+ client_->CheckErrorCode(SSL_ERROR_HANDSHAKE_UNEXPECTED_ALERT);
+}
+
+// A ChangeCipherSpec is ignored by a server because we have to tolerate it for
+// compatibility mode. That doesn't mean that we have to tolerate it
+// unconditionally. If we negotiate 1.3, we expect to see a cookie extension.
+TEST_F(TlsConnectStreamTls13, ChangeCipherSpecBeforeClientHelloTwice) {
+ EnsureTlsSetup();
+ server_->SetVersionRange(SSL_LIBRARY_VERSION_TLS_1_2,
+ SSL_LIBRARY_VERSION_TLS_1_3);
+ client_->SetVersionRange(SSL_LIBRARY_VERSION_TLS_1_2,
+ SSL_LIBRARY_VERSION_TLS_1_3);
+ // Client sends CCS before starting the handshake.
+ client_->SendDirect(DataBuffer(kCannedCcs, sizeof(kCannedCcs)));
+ client_->SendDirect(DataBuffer(kCannedCcs, sizeof(kCannedCcs)));
+ ConnectExpectAlert(server_, kTlsAlertUnexpectedMessage);
+ server_->CheckErrorCode(SSL_ERROR_RX_UNEXPECTED_CHANGE_CIPHER);
+ client_->CheckErrorCode(SSL_ERROR_HANDSHAKE_UNEXPECTED_ALERT);
+}
+
+// The server accepts a ChangeCipherSpec even if the client advertises
+// an empty session ID.
+TEST_F(TlsConnectStreamTls13, ChangeCipherSpecAfterClientHelloEmptySid) {
+ EnsureTlsSetup();
+ ConfigureVersion(SSL_LIBRARY_VERSION_TLS_1_3);
+
+ StartConnect();
+ client_->Handshake(); // Send ClientHello
+ client_->SendDirect(DataBuffer(kCannedCcs, sizeof(kCannedCcs))); // Send CCS
+
+ Handshake();
+ CheckConnected();
+}
+
+// The server rejects multiple ChangeCipherSpec even if the client
+// indicates compatibility mode with non-empty session ID.
+TEST_F(Tls13CompatTest, ChangeCipherSpecAfterClientHelloTwice) {
+ EnsureTlsSetup();
+ ConfigureVersion(SSL_LIBRARY_VERSION_TLS_1_3);
+ EnableCompatMode();
+
+ StartConnect();
+ client_->Handshake(); // Send ClientHello
+ // Send CCS twice in a row
+ client_->SendDirect(DataBuffer(kCannedCcs, sizeof(kCannedCcs)));
+ client_->SendDirect(DataBuffer(kCannedCcs, sizeof(kCannedCcs)));
+
+ server_->ExpectSendAlert(kTlsAlertUnexpectedMessage);
+ server_->Handshake(); // Consume ClientHello and CCS.
+ server_->CheckErrorCode(SSL_ERROR_RX_MALFORMED_CHANGE_CIPHER);
+}
+
+// The client accepts a ChangeCipherSpec even if it advertises an empty
+// session ID.
+TEST_F(TlsConnectStreamTls13, ChangeCipherSpecAfterServerHelloEmptySid) {
+ EnsureTlsSetup();
+ ConfigureVersion(SSL_LIBRARY_VERSION_TLS_1_3);
+
+ // To replace Finished with a CCS below
+ auto filter = MakeTlsFilter<TlsHandshakeDropper>(server_);
+ filter->SetHandshakeTypes({kTlsHandshakeFinished});
+ filter->EnableDecryption();
+
+ StartConnect();
+ client_->Handshake(); // Send ClientHello
+ server_->Handshake(); // Consume ClientHello, and
+ // send ServerHello..CertificateVerify
+ // Send CCS
+ server_->SendDirect(DataBuffer(kCannedCcs, sizeof(kCannedCcs)));
+
+ // No alert is sent from the client. As Finished is dropped, we
+ // can't use Handshake() and CheckConnected().
+ client_->Handshake();
+}
+
+// The client rejects multiple ChangeCipherSpec in a row even if the
+// client indicates compatibility mode with non-empty session ID.
+TEST_F(Tls13CompatTest, ChangeCipherSpecAfterServerHelloTwice) {
+ EnsureTlsSetup();
+ ConfigureVersion(SSL_LIBRARY_VERSION_TLS_1_3);
+ EnableCompatMode();
+
+ // To replace Finished with a CCS below
+ auto filter = MakeTlsFilter<TlsHandshakeDropper>(server_);
+ filter->SetHandshakeTypes({kTlsHandshakeFinished});
+ filter->EnableDecryption();
+
+ StartConnect();
+ client_->Handshake(); // Send ClientHello
+ server_->Handshake(); // Consume ClientHello, and
+ // send ServerHello..CertificateVerify
+ // the ServerHello is followed by CCS
+ // Send another CCS
+ server_->SendDirect(DataBuffer(kCannedCcs, sizeof(kCannedCcs)));
+ client_->ExpectSendAlert(kTlsAlertUnexpectedMessage);
+ client_->Handshake(); // Consume ClientHello and CCS
+ client_->CheckErrorCode(SSL_ERROR_RX_MALFORMED_CHANGE_CIPHER);
+}
+
+// If we negotiate 1.2, we abort.
+TEST_F(TlsConnectStreamTls13, ChangeCipherSpecBeforeClientHello12) {
+ EnsureTlsSetup();
+ server_->SetVersionRange(SSL_LIBRARY_VERSION_TLS_1_2,
+ SSL_LIBRARY_VERSION_TLS_1_3);
+ client_->SetVersionRange(SSL_LIBRARY_VERSION_TLS_1_2,
+ SSL_LIBRARY_VERSION_TLS_1_2);
+ // Client sends CCS before starting the handshake.
+ client_->SendDirect(DataBuffer(kCannedCcs, sizeof(kCannedCcs)));
+ ConnectExpectAlert(server_, kTlsAlertUnexpectedMessage);
+ server_->CheckErrorCode(SSL_ERROR_RX_UNEXPECTED_CHANGE_CIPHER);
+ client_->CheckErrorCode(SSL_ERROR_HANDSHAKE_UNEXPECTED_ALERT);
+}
+
+TEST_F(TlsConnectStreamTls13, ChangeCipherSpecAfterFinished13) {
+ EnsureTlsSetup();
+ ConfigureVersion(SSL_LIBRARY_VERSION_TLS_1_3);
+ Connect();
+ SendReceive(10);
+ // Client sends CCS after the handshake.
+ client_->SendDirect(DataBuffer(kCannedCcs, sizeof(kCannedCcs)));
+ server_->ExpectSendAlert(kTlsAlertUnexpectedMessage);
+ server_->ExpectReadWriteError();
+ server_->ReadBytes();
+ EXPECT_EQ(SSL_ERROR_RX_UNEXPECTED_RECORD_TYPE, server_->error_code());
+}
+
+TEST_F(TlsConnectDatagram13, CompatModeDtlsClient) {
+ EnsureTlsSetup();
+ client_->SetOption(SSL_ENABLE_TLS13_COMPAT_MODE, PR_TRUE);
+ auto client_records = MakeTlsFilter<TlsRecordRecorder>(client_);
+ auto server_records = MakeTlsFilter<TlsRecordRecorder>(server_);
+ Connect();
+
+ ASSERT_EQ(2U, client_records->count()); // CH, Fin
+ EXPECT_EQ(ssl_ct_handshake, client_records->record(0).header.content_type());
+ EXPECT_EQ(kCtDtlsCiphertext,
+ (client_records->record(1).header.content_type() &
+ kCtDtlsCiphertextMask));
+
+ ASSERT_EQ(6U, server_records->count()); // SH, EE, CT, CV, Fin, Ack
+ EXPECT_EQ(ssl_ct_handshake, server_records->record(0).header.content_type());
+ for (size_t i = 1; i < server_records->count(); ++i) {
+ EXPECT_EQ(kCtDtlsCiphertext,
+ (server_records->record(i).header.content_type() &
+ kCtDtlsCiphertextMask));
+ }
+}
+
+class AddSessionIdFilter : public TlsHandshakeFilter {
+ public:
+ AddSessionIdFilter(const std::shared_ptr<TlsAgent>& client)
+ : TlsHandshakeFilter(client, {ssl_hs_client_hello}) {}
+
+ protected:
+ PacketFilter::Action FilterHandshake(const HandshakeHeader& header,
+ const DataBuffer& input,
+ DataBuffer* output) override {
+ uint32_t session_id_len = 0;
+ EXPECT_TRUE(input.Read(2 + 32, 1, &session_id_len));
+ EXPECT_EQ(0U, session_id_len);
+ uint8_t session_id[33] = {32}; // 32 for length, the rest zero.
+ *output = input;
+ output->Splice(session_id, sizeof(session_id), 34, 1);
+ return CHANGE;
+ }
+};
+
+// Adding a session ID to a DTLS ClientHello should not trigger compatibility
+// mode. It should be ignored instead.
+TEST_F(TlsConnectDatagram13, CompatModeDtlsServer) {
+ EnsureTlsSetup();
+ auto client_records = std::make_shared<TlsRecordRecorder>(client_);
+ client_->SetFilter(
+ std::make_shared<ChainedPacketFilter>(ChainedPacketFilterInit(
+ {client_records, std::make_shared<AddSessionIdFilter>(client_)})));
+ auto server_hello =
+ std::make_shared<TlsHandshakeRecorder>(server_, kTlsHandshakeServerHello);
+ auto server_records = std::make_shared<TlsRecordRecorder>(server_);
+ server_->SetFilter(std::make_shared<ChainedPacketFilter>(
+ ChainedPacketFilterInit({server_records, server_hello})));
+ StartConnect();
+ client_->Handshake();
+ server_->Handshake();
+ // The client will consume the ServerHello, but discard everything else
+ // because it doesn't decrypt. And don't wait around for the client to ACK.
+ client_->Handshake();
+
+ ASSERT_EQ(1U, client_records->count());
+ EXPECT_EQ(ssl_ct_handshake, client_records->record(0).header.content_type());
+
+ ASSERT_EQ(5U, server_records->count()); // SH, EE, CT, CV, Fin
+ EXPECT_EQ(ssl_ct_handshake, server_records->record(0).header.content_type());
+ for (size_t i = 1; i < server_records->count(); ++i) {
+ EXPECT_EQ(kCtDtlsCiphertext,
+ (server_records->record(i).header.content_type() &
+ kCtDtlsCiphertextMask));
+ }
+
+ uint32_t session_id_len = 0;
+ EXPECT_TRUE(server_hello->buffer().Read(2 + 32, 1, &session_id_len));
+ EXPECT_EQ(0U, session_id_len);
+}
+
+TEST_F(Tls13CompatTest, ConnectWith12ThenAttemptToResume13CompatMode) {
+ ConfigureSessionCache(RESUME_SESSIONID, RESUME_SESSIONID);
+ ConfigureVersion(SSL_LIBRARY_VERSION_TLS_1_2);
+ Connect();
+
+ Reset();
+ ExpectResumption(RESUME_NONE);
+ version_ = SSL_LIBRARY_VERSION_TLS_1_3;
+ client_->SetVersionRange(SSL_LIBRARY_VERSION_TLS_1_2,
+ SSL_LIBRARY_VERSION_TLS_1_3);
+ server_->SetVersionRange(SSL_LIBRARY_VERSION_TLS_1_2,
+ SSL_LIBRARY_VERSION_TLS_1_3);
+ EnableCompatMode();
+ Connect();
+}
+
+} // namespace nss_test